diff --git a/src/components/chrono/chrono.ts b/src/components/chrono/chrono.ts
new file mode 100644
index 000000000..ee0d421bf
--- /dev/null
+++ b/src/components/chrono/chrono.ts
@@ -0,0 +1,116 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// 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, 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',
+ template: '{{ time / 1000 | coreSecondsToHMS }}'
+})
+export class CoreChronoComponent implements OnChanges, OnDestroy {
+ @Input() running: boolean; // Set it to true to start the chrono. Set it to false to stop it.
+ @Input() startTime?: number = 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: number = 0;
+ protected interval;
+
+ constructor(private cdr: ChangeDetectorRef) {
+ this.onEnd = new EventEmitter();
+ }
+
+ /**
+ * Component being initialized.
+ */
+ ngOnInit() {
+ this.time = this.startTime || 0;
+ }
+
+ /**
+ * Component being initialized.
+ */
+ ngOnChanges(changes: {[name: string]: SimpleChange}) {
+ 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 = 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.cdr.detectChanges();
+ }, 200);
+ }
+
+ /**
+ * Stop the chrono, leaving the same time it has.
+ */
+ protected stop() : void {
+ clearInterval(this.interval);
+ delete this.interval;
+ }
+
+ ngOnDestroy() {
+ this.stop();
+ }
+}
diff --git a/src/components/components.module.ts b/src/components/components.module.ts
index 8be44288c..c8c96e009 100644
--- a/src/components/components.module.ts
+++ b/src/components/components.module.ts
@@ -16,6 +16,7 @@ import { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreDirectivesModule } from '../directives/directives.module';
+import { CorePipesModule } from '../pipes/pipes.module';
import { CoreLoadingComponent } from './loading/loading';
import { CoreMarkRequiredComponent } from './mark-required/mark-required';
import { CoreInputErrorsComponent } from './input-errors/input-errors';
@@ -28,6 +29,7 @@ import { CoreFileComponent } from './file/file';
import { CoreContextMenuComponent } from './context-menu/context-menu';
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
+import { CoreChronoComponent } from './chrono/chrono';
@NgModule({
declarations: [
@@ -42,7 +44,8 @@ import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-pop
CoreFileComponent,
CoreContextMenuComponent,
CoreContextMenuItemComponent,
- CoreContextMenuPopoverComponent
+ CoreContextMenuPopoverComponent,
+ CoreChronoComponent
],
entryComponents: [
CoreContextMenuPopoverComponent
@@ -50,7 +53,8 @@ import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-pop
imports: [
IonicModule,
TranslateModule.forChild(),
- CoreDirectivesModule
+ CoreDirectivesModule,
+ CorePipesModule
],
exports: [
CoreLoadingComponent,
@@ -63,7 +67,8 @@ import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-pop
CoreSearchBoxComponent,
CoreFileComponent,
CoreContextMenuComponent,
- CoreContextMenuItemComponent
+ CoreContextMenuItemComponent,
+ CoreChronoComponent
]
})
export class CoreComponentsModule {}
diff --git a/src/pipes/seconds-to-hms.ts b/src/pipes/seconds-to-hms.ts
index c88164cbc..8160522a2 100644
--- a/src/pipes/seconds-to-hms.ts
+++ b/src/pipes/seconds-to-hms.ts
@@ -54,6 +54,9 @@ export class CoreSecondsToHMSPipe implements PipeTransform {
seconds = numberSeconds;
}
+ // Don't allow decimals.
+ seconds = Math.floor(seconds);
+
hours = Math.floor(seconds / CoreConstants.secondsHour);
seconds -= hours * CoreConstants.secondsHour;
minutes = Math.floor(seconds / CoreConstants.secondsMinute);