diff --git a/src/app/components/chrono/chrono.ts b/src/app/components/chrono/chrono.ts
new file mode 100644
index 000000000..179ad69f8
--- /dev/null
+++ b/src/app/components/chrono/chrono.ts
@@ -0,0 +1,128 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {
+ Component,
+ Input,
+ OnInit,
+ OnChanges,
+ OnDestroy,
+ Output,
+ EventEmitter,
+ SimpleChange,
+ ChangeDetectorRef,
+} from '@angular/core';
+
+/**
+ * This component shows a chronometer in format HH:MM:SS.
+ *
+ * If no startTime is provided, it will start at 00:00:00.
+ * If an endTime is provided, the chrono will stop and emit an event in the onEnd output when that number of milliseconds is
+ * reached. E.g. if startTime=60000 and endTime=120000, the chrono will start at 00:01:00 and end when it reaches 00:02:00.
+ *
+ * This component has 2 boolean inputs to control the timer: running (to start and stop it) and reset.
+ *
+ * Example usage:
+ *
+ */
+@Component({
+ selector: 'core-chrono',
+ templateUrl: 'core-chrono.html',
+})
+export class CoreChronoComponent implements OnInit, OnChanges, OnDestroy {
+
+ @Input() running?: boolean; // Set it to true to start the chrono. Set it to false to stop it.
+ @Input() startTime = 0; // Number of milliseconds to put in the chrono before starting.
+ @Input() endTime?: number; // Number of milliseconds to stop the chrono.
+ @Input() reset?: boolean; // Set it to true to reset the chrono.
+ @Output() onEnd: EventEmitter; // Will emit an event when the endTime is reached.
+
+ time = 0;
+ protected interval?: number;
+
+ constructor(protected changeDetectorRef: ChangeDetectorRef) {
+ this.onEnd = new EventEmitter();
+ }
+
+ /**
+ * Component being initialized.
+ */
+ ngOnInit(): void {
+ this.time = this.startTime || 0;
+ }
+
+ /**
+ * Component being changed.
+ */
+ ngOnChanges(changes: { [name: string]: SimpleChange }): void {
+ if (changes && changes.running) {
+ if (changes.running.currentValue) {
+ this.start();
+ } else {
+ this.stop();
+ }
+ }
+ if (changes && changes.reset && changes.reset.currentValue) {
+ this.resetChrono();
+ }
+ }
+
+ /**
+ * Reset the chrono, stopping it and setting it to startTime.
+ */
+ protected resetChrono(): void {
+ this.stop();
+ this.time = this.startTime || 0;
+ }
+
+ /**
+ * Start the chrono if it isn't running.
+ */
+ protected start(): void {
+ if (this.interval) {
+ // Already setup.
+ return;
+ }
+
+ let lastExecTime = Date.now();
+
+ this.interval = window.setInterval(() => {
+ // Increase the chrono.
+ this.time += Date.now() - lastExecTime;
+ lastExecTime = Date.now();
+
+ if (typeof this.endTime != 'undefined' && this.time > this.endTime) {
+ // End time reached, stop the timer and call the end function.
+ this.stop();
+ this.onEnd.emit();
+ }
+
+ // Force change detection. Angular doesn't detect these async operations.
+ this.changeDetectorRef.detectChanges();
+ }, 200);
+ }
+
+ /**
+ * Stop the chrono, leaving the same time it has.
+ */
+ protected stop(): void {
+ clearInterval(this.interval);
+ delete this.interval;
+ }
+
+ ngOnDestroy(): void {
+ this.stop();
+ }
+
+}
diff --git a/src/app/components/chrono/core-chrono.html b/src/app/components/chrono/core-chrono.html
new file mode 100644
index 000000000..5f0f28ade
--- /dev/null
+++ b/src/app/components/chrono/core-chrono.html
@@ -0,0 +1 @@
+{{ time / 1000 | coreSecondsToHMS }}
\ No newline at end of file
diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts
index 1e314e6d9..b67263e6a 100644
--- a/src/app/components/components.module.ts
+++ b/src/app/components/components.module.ts
@@ -17,6 +17,7 @@ import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
+import { CoreChronoComponent } from './chrono/chrono';
import { CoreDownloadRefreshComponent } from './download-refresh/download-refresh';
import { CoreFileComponent } from './file/file';
import { CoreIconComponent } from './icon/icon';
@@ -35,6 +36,7 @@ import { CorePipesModule } from '@app/pipes/pipes.module';
@NgModule({
declarations: [
+ CoreChronoComponent,
CoreDownloadRefreshComponent,
CoreFileComponent,
CoreIconComponent,
@@ -56,6 +58,7 @@ import { CorePipesModule } from '@app/pipes/pipes.module';
CorePipesModule,
],
exports: [
+ CoreChronoComponent,
CoreDownloadRefreshComponent,
CoreFileComponent,
CoreIconComponent,