mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-07 15:29:10 +00:00
V2 fix timer (#304)
This commit is contained in:
@@ -20,8 +20,7 @@ export function getTimerByType(timerObject?: TimerTypeParams): string | number |
|
|||||||
if (timerObject.timerType === TimerType.CountDown) {
|
if (timerObject.timerType === TimerType.CountDown) {
|
||||||
timer = timerObject.current;
|
timer = timerObject.current;
|
||||||
} else if (timerObject.timerType === TimerType.CountUp) {
|
} else if (timerObject.timerType === TimerType.CountUp) {
|
||||||
//todo: fix counting down issue in backend later
|
timer = timerObject.elapsed;
|
||||||
timer = (timerObject.elapsed ?? 0) < 0 ? 0 : timerObject.elapsed;
|
|
||||||
} else if (timerObject.timerType === TimerType.Clock) {
|
} else if (timerObject.timerType === TimerType.Clock) {
|
||||||
timer = formatTime(timerObject.clock, formatOptions);
|
timer = formatTime(timerObject.clock, formatOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,8 +142,8 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
|||||||
const stageTimerCharacters = display.replace('/:/g', '').length;
|
const stageTimerCharacters = display.replace('/:/g', '').length;
|
||||||
|
|
||||||
const timerFontSize = (89 / (stageTimerCharacters - 1)) * (userOptions.size || 1);
|
const timerFontSize = (89 / (stageTimerCharacters - 1)) * (userOptions.size || 1);
|
||||||
const timerClasseNames = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
|
|
||||||
|
|
||||||
|
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
|
||||||
const baseClasses = `minimal-timer ${isMirrored ? 'mirror' : ''}`;
|
const baseClasses = `minimal-timer ${isMirrored ? 'mirror' : ''}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -166,7 +166,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
|||||||
<div className='end-message'>{general.endMessage}</div>
|
<div className='end-message'>{general.endMessage}</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={timerClasseNames}
|
className={timerClasses}
|
||||||
style={{
|
style={{
|
||||||
color: userOptions.textColour,
|
color: userOptions.textColour,
|
||||||
fontSize: `${timerFontSize}vw`,
|
fontSize: `${timerFontSize}vw`,
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ export default function Timer(props) {
|
|||||||
display = `-${display}`;
|
display = `-${display}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timerFontSize = 100 / stageTimerCharacters;
|
const timerFontSize = 89 / (stageTimerCharacters - 1);
|
||||||
const timerClasseNames = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
|
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={showFinished ? `${baseClasses} stage-timer--finished` : baseClasses} data-testid='timer-view'>
|
<div className={showFinished ? `${baseClasses} stage-timer--finished` : baseClasses} data-testid='timer-view'>
|
||||||
@@ -98,7 +98,7 @@ export default function Timer(props) {
|
|||||||
<div className='end-message'>{general.endMessage}</div>
|
<div className='end-message'>{general.endMessage}</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={timerClasseNames}
|
className={timerClasses}
|
||||||
style={{
|
style={{
|
||||||
fontSize: `${timerFontSize}vw`,
|
fontSize: `${timerFontSize}vw`,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import { stringFromMillis } from '../../utils/time.js';
|
|||||||
import { messageManager } from '../message-manager/MessageManager.js';
|
import { messageManager } from '../message-manager/MessageManager.js';
|
||||||
import { PlaybackService } from '../../services/PlaybackService.js';
|
import { PlaybackService } from '../../services/PlaybackService.js';
|
||||||
|
|
||||||
import { eventTimer, TimerService } from '../../services/TimerService.js';
|
import { eventTimer } from '../../services/TimerService.js';
|
||||||
import { EventLoader, eventLoader } from '../event-loader/EventLoader.js';
|
import { EventLoader, eventLoader } from '../event-loader/EventLoader.js';
|
||||||
|
import { clock } from '../../services/Clock.js';
|
||||||
|
|
||||||
class SocketController {
|
class SocketController {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -329,7 +330,7 @@ class SocketController {
|
|||||||
level,
|
level,
|
||||||
origin,
|
origin,
|
||||||
text,
|
text,
|
||||||
time: stringFromMillis(TimerService.getCurrentTime() || 0),
|
time: stringFromMillis(clock.getSystemTime() || 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.messageStack.unshift(logMessage);
|
this.messageStack.unshift(logMessage);
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
enum Source {
|
||||||
|
System = 'system',
|
||||||
|
MIDI = 'MIDI',
|
||||||
|
}
|
||||||
|
|
||||||
|
class Clock {
|
||||||
|
private static instance: Clock;
|
||||||
|
private readonly source: Source;
|
||||||
|
|
||||||
|
constructor(source?: Source) {
|
||||||
|
if (Clock.instance) {
|
||||||
|
return Clock.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Clock.instance = this;
|
||||||
|
|
||||||
|
this.source = source || Source.System;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current time from source
|
||||||
|
*/
|
||||||
|
timeNow(): number {
|
||||||
|
switch (this.source) {
|
||||||
|
case Source.System:
|
||||||
|
return this.getSystemTime();
|
||||||
|
case Source.MIDI:
|
||||||
|
// @ts-expect-error -- not implemented
|
||||||
|
return this.getMidiTime();
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid time source');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current time from system
|
||||||
|
*/
|
||||||
|
getSystemTime() {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// extract milliseconds since midnight
|
||||||
|
let elapsed = now.getHours() * 3600000;
|
||||||
|
elapsed += now.getMinutes() * 60000;
|
||||||
|
elapsed += now.getSeconds() * 1000;
|
||||||
|
elapsed += now.getMilliseconds();
|
||||||
|
return elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current time from MIDI
|
||||||
|
*/
|
||||||
|
getMidiTime() {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clock = new Clock();
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
import { socketProvider } from '../classes/socket/SocketController.js';
|
import { socketProvider } from '../classes/socket/SocketController.js';
|
||||||
import { eventLoader, EventLoader } from '../classes/event-loader/EventLoader.js';
|
import { eventLoader, EventLoader } from '../classes/event-loader/EventLoader.js';
|
||||||
import { eventTimer, TimerService } from './TimerService.js';
|
|
||||||
import { eventStore } from '../stores/EventStore.js';
|
import { eventStore } from '../stores/EventStore.js';
|
||||||
|
import { eventTimer } from './TimerService.js';
|
||||||
|
import { clock } from './Clock.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service manages playback status of app
|
* Service manages playback status of app
|
||||||
@@ -163,7 +164,7 @@ export class PlaybackService {
|
|||||||
*/
|
*/
|
||||||
static roll() {
|
static roll() {
|
||||||
if (EventLoader.getPlayableEvents()) {
|
if (EventLoader.getPlayableEvents()) {
|
||||||
const rollTimers = eventLoader.findRoll(TimerService.getCurrentTime());
|
const rollTimers = eventLoader.findRoll(clock.timeNow());
|
||||||
|
|
||||||
// nothing to play
|
// nothing to play
|
||||||
if (rollTimers === null) {
|
if (rollTimers === null) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { PlaybackService } from './PlaybackService.js';
|
|||||||
import { updateRoll } from './rollUtils.js';
|
import { updateRoll } from './rollUtils.js';
|
||||||
import { DAY_TO_MS } from '../utils/time.js';
|
import { DAY_TO_MS } from '../utils/time.js';
|
||||||
import { integrationService } from './integration-service/IntegrationService.js';
|
import { integrationService } from './integration-service/IntegrationService.js';
|
||||||
|
import { getCurrent, getElapsed, getExpectedFinish } from './timerUtils.js';
|
||||||
|
import { clock } from './Clock.js';
|
||||||
|
|
||||||
export class TimerService {
|
export class TimerService {
|
||||||
private readonly _interval: NodeJS.Timer;
|
private readonly _interval: NodeJS.Timer;
|
||||||
@@ -12,19 +14,19 @@ export class TimerService {
|
|||||||
playback: string;
|
playback: string;
|
||||||
|
|
||||||
loadedTimerId: null;
|
loadedTimerId: null;
|
||||||
private _pausedInterval: number;
|
private pausedTime: number;
|
||||||
private _pausedAt: number | null;
|
private pausedAt: number | null;
|
||||||
private _secondaryTarget: number | null;
|
private secondaryTarget: number | null;
|
||||||
|
|
||||||
timer: {
|
timer: {
|
||||||
clock: number;
|
clock: number; // realtime clock
|
||||||
current: number | null;
|
current: number | null; // running countdown
|
||||||
elapsed: number | null;
|
elapsed: number | null; // elapsed time in current timer
|
||||||
expectedFinish: number | null;
|
expectedFinish: number | null;
|
||||||
addedTime: number;
|
addedTime: number; // time added by user, can be negative
|
||||||
startedAt: number | null;
|
startedAt: number | null;
|
||||||
finishedAt: number | null;
|
finishedAt: number | null; // only if timer has already finished
|
||||||
secondaryTimer: number | null;
|
secondaryTimer: number | null; // used for roll mode
|
||||||
selectedEventId: string | null;
|
selectedEventId: string | null;
|
||||||
duration: number | null;
|
duration: number | null;
|
||||||
timerType: TimerType | null;
|
timerType: TimerType | null;
|
||||||
@@ -40,41 +42,6 @@ export class TimerService {
|
|||||||
this._interval = setInterval(() => this.update(), timerConfig?.refresh || 1000);
|
this._interval = setInterval(() => this.update(), timerConfig?.refresh || 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current time in ms from midnight
|
|
||||||
* @static
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
static getCurrentTime() {
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
// extract milliseconds since midnight
|
|
||||||
let elapsed = now.getHours() * 3600000;
|
|
||||||
elapsed += now.getMinutes() * 60000;
|
|
||||||
elapsed += now.getSeconds() * 1000;
|
|
||||||
elapsed += now.getMilliseconds();
|
|
||||||
return elapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns expected time finish
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_getExpectedFinish() {
|
|
||||||
if (this.timer.startedAt === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.timer.finishedAt) {
|
|
||||||
return this.timer.finishedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.max(
|
|
||||||
this.timer.startedAt + this.timer.duration + this._pausedInterval + this.timer.addedTime,
|
|
||||||
this.timer.startedAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears internal state
|
* Clears internal state
|
||||||
* @private
|
* @private
|
||||||
@@ -82,7 +49,7 @@ export class TimerService {
|
|||||||
_clear() {
|
_clear() {
|
||||||
this.playback = 'stop';
|
this.playback = 'stop';
|
||||||
this.timer = {
|
this.timer = {
|
||||||
clock: TimerService.getCurrentTime(),
|
clock: clock.timeNow(),
|
||||||
current: null,
|
current: null,
|
||||||
elapsed: null,
|
elapsed: null,
|
||||||
expectedFinish: null,
|
expectedFinish: null,
|
||||||
@@ -95,9 +62,9 @@ export class TimerService {
|
|||||||
timerType: null,
|
timerType: null,
|
||||||
};
|
};
|
||||||
this.loadedTimerId = null;
|
this.loadedTimerId = null;
|
||||||
this._pausedInterval = 0;
|
this.pausedTime = 0;
|
||||||
this._pausedAt = null;
|
this.pausedAt = null;
|
||||||
this._secondaryTarget = null;
|
this.secondaryTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +94,13 @@ export class TimerService {
|
|||||||
|
|
||||||
// this might not be ideal
|
// this might not be ideal
|
||||||
this.timer.finishedAt = null;
|
this.timer.finishedAt = null;
|
||||||
this.timer.expectedFinish = this._getExpectedFinish();
|
this.timer.expectedFinish = getExpectedFinish(
|
||||||
|
this.timer.startedAt,
|
||||||
|
this.timer.finishedAt,
|
||||||
|
this.timer.duration,
|
||||||
|
this.pausedTime,
|
||||||
|
this.timer.addedTime,
|
||||||
|
);
|
||||||
if (this.timer.startedAt === null) {
|
if (this.timer.startedAt === null) {
|
||||||
this.timer.current = timer.duration;
|
this.timer.current = timer.duration;
|
||||||
}
|
}
|
||||||
@@ -157,8 +130,8 @@ export class TimerService {
|
|||||||
this.timer.current = timer.duration;
|
this.timer.current = timer.duration;
|
||||||
this.playback = 'armed';
|
this.playback = 'armed';
|
||||||
this.timer.timerType = timer.timerType;
|
this.timer.timerType = timer.timerType;
|
||||||
this._pausedInterval = 0;
|
this.pausedTime = 0;
|
||||||
this._pausedAt = 0;
|
this.pausedAt = 0;
|
||||||
|
|
||||||
this._onLoad();
|
this._onLoad();
|
||||||
}
|
}
|
||||||
@@ -182,19 +155,25 @@ export class TimerService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timer.clock = TimerService.getCurrentTime();
|
this.timer.clock = clock.timeNow();
|
||||||
|
|
||||||
// add paused time
|
// add paused time
|
||||||
if (this._pausedInterval) {
|
if (this.pausedTime) {
|
||||||
this.timer.addedTime += this._pausedInterval;
|
this.timer.addedTime += this.pausedTime;
|
||||||
this._pausedAt = null;
|
this.pausedAt = null;
|
||||||
this._pausedInterval = 0;
|
this.pausedTime = 0;
|
||||||
} else {
|
} else {
|
||||||
this.timer.startedAt = this.timer.clock;
|
this.timer.startedAt = this.timer.clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.playback = 'play';
|
this.playback = 'play';
|
||||||
this.timer.expectedFinish = this._getExpectedFinish();
|
this.timer.expectedFinish = getExpectedFinish(
|
||||||
|
this.timer.startedAt,
|
||||||
|
this.timer.finishedAt,
|
||||||
|
this.timer.duration,
|
||||||
|
this.pausedTime,
|
||||||
|
this.timer.addedTime,
|
||||||
|
);
|
||||||
this._onStart();
|
this._onStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,8 +193,8 @@ export class TimerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.playback = 'pause';
|
this.playback = 'pause';
|
||||||
this.timer.clock = TimerService.getCurrentTime();
|
this.timer.clock = clock.timeNow();
|
||||||
this._pausedAt = this.timer.clock;
|
this.pausedAt = this.timer.clock;
|
||||||
this._onPause();
|
this._onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,14 +229,12 @@ export class TimerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.timer.addedTime += amount;
|
this.timer.addedTime += amount;
|
||||||
this.timer.current += amount;
|
|
||||||
this.timer.elapsed += amount;
|
|
||||||
|
|
||||||
// handle edge cases
|
// handle edge cases
|
||||||
if (amount < 0 && Math.abs(amount) > this.timer.current) {
|
if (amount < 0 && Math.abs(amount) > this.timer.current) {
|
||||||
if (this.timer.finishedAt === null) {
|
if (this.timer.finishedAt === null) {
|
||||||
// if we will make the clock negative
|
// if we will make the clock negative
|
||||||
this.timer.finishedAt = TimerService.getCurrentTime();
|
this.timer.finishedAt = clock.timeNow();
|
||||||
}
|
}
|
||||||
} else if (this.timer.current < 0 && this.timer.current + amount > 0) {
|
} else if (this.timer.current < 0 && this.timer.current + amount > 0) {
|
||||||
// clock will go from negative to positive
|
// clock will go from negative to positive
|
||||||
@@ -269,7 +246,7 @@ export class TimerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
this.timer.clock = TimerService.getCurrentTime();
|
this.timer.clock = clock.timeNow();
|
||||||
|
|
||||||
if (this.playback === 'roll') {
|
if (this.playback === 'roll') {
|
||||||
const tempCurrentTimer = {
|
const tempCurrentTimer = {
|
||||||
@@ -283,7 +260,7 @@ export class TimerService {
|
|||||||
|
|
||||||
clock: this.timer.clock,
|
clock: this.timer.clock,
|
||||||
secondaryTimer: this.timer.secondaryTimer,
|
secondaryTimer: this.timer.secondaryTimer,
|
||||||
_secondaryTarget: this._secondaryTarget,
|
_secondaryTarget: this.secondaryTarget,
|
||||||
};
|
};
|
||||||
const { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished } = updateRoll(tempCurrentTimer);
|
const { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished } = updateRoll(tempCurrentTimer);
|
||||||
|
|
||||||
@@ -303,45 +280,52 @@ export class TimerService {
|
|||||||
// we only update timer if a timer has been started
|
// we only update timer if a timer has been started
|
||||||
if (this.timer.startedAt !== null) {
|
if (this.timer.startedAt !== null) {
|
||||||
if (this.playback === 'pause') {
|
if (this.playback === 'pause') {
|
||||||
this._pausedInterval = this.timer.clock - this._pausedAt;
|
this.pausedTime = this.timer.clock - this.pausedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timer.current =
|
|
||||||
this.timer.startedAt + this.timer.duration + this.timer.addedTime + this._pausedInterval - this.timer.clock;
|
|
||||||
this.timer.elapsed = this.timer.duration - this.timer.current;
|
|
||||||
|
|
||||||
if (this.playback === 'play' && this.timer.current <= 0 && this.timer.finishedAt === null) {
|
if (this.playback === 'play' && this.timer.current <= 0 && this.timer.finishedAt === null) {
|
||||||
this.timer.finishedAt = this.timer.clock;
|
this.timer.finishedAt = this.timer.clock;
|
||||||
this._onFinish();
|
this._onFinish();
|
||||||
} else {
|
} else {
|
||||||
this.timer.finishedAt = null;
|
this.timer.expectedFinish = getExpectedFinish(
|
||||||
|
this.timer.startedAt,
|
||||||
|
this.timer.finishedAt,
|
||||||
|
this.timer.duration,
|
||||||
|
this.pausedTime,
|
||||||
|
this.timer.addedTime,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.timer.expectedFinish = this._getExpectedFinish();
|
this.timer.current = getCurrent(
|
||||||
|
this.timer.startedAt,
|
||||||
|
this.timer.duration,
|
||||||
|
this.timer.addedTime,
|
||||||
|
this.pausedTime,
|
||||||
|
this.timer.clock,
|
||||||
|
);
|
||||||
|
this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._onUpdate();
|
this._onUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUpdate() {
|
_onUpdate() {
|
||||||
eventStore.set('playback', this.playback);
|
|
||||||
eventStore.set('timer', this.timer);
|
eventStore.set('timer', this.timer);
|
||||||
integrationService.dispatch(TimerLifeCycle.onUpdate);
|
integrationService.dispatch(TimerLifeCycle.onUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFinish() {
|
_onFinish() {
|
||||||
eventStore.set('playback', this.playback);
|
|
||||||
eventStore.set('timer', this.timer);
|
eventStore.set('timer', this.timer);
|
||||||
integrationService.dispatch(TimerLifeCycle.onFinish);
|
integrationService.dispatch(TimerLifeCycle.onFinish);
|
||||||
}
|
}
|
||||||
|
|
||||||
roll(currentEvent, nextEvent, timers) {
|
roll(currentEvent, nextEvent, timers) {
|
||||||
this._clear();
|
this._clear();
|
||||||
this.timer.clock = TimerService.getCurrentTime();
|
this.timer.clock = clock.timeNow();
|
||||||
|
|
||||||
if (currentEvent) {
|
if (currentEvent) {
|
||||||
// there is something running, load
|
// there is something running, load
|
||||||
this.timer.secondaryTimer = null;
|
this.timer.secondaryTimer = null;
|
||||||
this._secondaryTarget = null;
|
this.secondaryTarget = null;
|
||||||
|
|
||||||
this.loadedTimerId = currentEvent.id;
|
this.loadedTimerId = currentEvent.id;
|
||||||
this.timer.startedAt = currentEvent.timeStart;
|
this.timer.startedAt = currentEvent.timeStart;
|
||||||
@@ -351,7 +335,7 @@ export class TimerService {
|
|||||||
} else if (nextEvent) {
|
} else if (nextEvent) {
|
||||||
// nothing now, but something coming up
|
// nothing now, but something coming up
|
||||||
this.timer.secondaryTimer = nextEvent.timeStart - this.timer.clock;
|
this.timer.secondaryTimer = nextEvent.timeStart - this.timer.clock;
|
||||||
this._secondaryTarget = nextEvent.timeStart;
|
this.secondaryTarget = nextEvent.timeStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.playback = 'roll';
|
this.playback = 'roll';
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
import { getCurrent, getElapsed, getExpectedFinish } from '../timerUtils.js';
|
||||||
|
|
||||||
|
describe('getExpectedFinish()', () => {
|
||||||
|
it('is null if we havent started', () => {
|
||||||
|
const startedAt = null;
|
||||||
|
const finishedAt = null;
|
||||||
|
const duration = 10;
|
||||||
|
const pausedTime = 0;
|
||||||
|
const addedTime = 0;
|
||||||
|
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||||
|
expect(calculatedFinish).toBe(null);
|
||||||
|
});
|
||||||
|
it('is finishedAt if defined', () => {
|
||||||
|
const startedAt = 10;
|
||||||
|
const finishedAt = 20;
|
||||||
|
const duration = 10;
|
||||||
|
const pausedTime = 0;
|
||||||
|
const addedTime = 0;
|
||||||
|
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||||
|
expect(calculatedFinish).toBe(finishedAt);
|
||||||
|
});
|
||||||
|
it('calculates the finish time', () => {
|
||||||
|
const startedAt = 1;
|
||||||
|
const finishedAt = null;
|
||||||
|
const duration = 10;
|
||||||
|
const pausedTime = 0;
|
||||||
|
const addedTime = 0;
|
||||||
|
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||||
|
expect(calculatedFinish).toBe(11);
|
||||||
|
});
|
||||||
|
it('adds paused and added times', () => {
|
||||||
|
const startedAt = 1;
|
||||||
|
const finishedAt = null;
|
||||||
|
const duration = 10;
|
||||||
|
const pausedTime = 10;
|
||||||
|
const addedTime = 10;
|
||||||
|
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||||
|
expect(calculatedFinish).toBe(31);
|
||||||
|
});
|
||||||
|
it('added time could be negative', () => {
|
||||||
|
const startedAt = 1;
|
||||||
|
const finishedAt = null;
|
||||||
|
const duration = 10;
|
||||||
|
const pausedTime = 10;
|
||||||
|
const addedTime = -10;
|
||||||
|
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||||
|
expect(calculatedFinish).toBe(11);
|
||||||
|
});
|
||||||
|
it('user could add enough time for it to be negative', () => {
|
||||||
|
const startedAt = 1;
|
||||||
|
const finishedAt = null;
|
||||||
|
const duration = 10;
|
||||||
|
const pausedTime = 0;
|
||||||
|
const addedTime = -100;
|
||||||
|
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||||
|
expect(calculatedFinish).toBe(1);
|
||||||
|
});
|
||||||
|
it('timer can have no duration', () => {
|
||||||
|
const startedAt = 1;
|
||||||
|
const finishedAt = null;
|
||||||
|
const duration = 0;
|
||||||
|
const pausedTime = 0;
|
||||||
|
const addedTime = 0;
|
||||||
|
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||||
|
expect(calculatedFinish).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCurrent()', () => {
|
||||||
|
it('is null if it hasnt started', () => {
|
||||||
|
const startedAt = null;
|
||||||
|
const duration = 0;
|
||||||
|
const pausedTime = 0;
|
||||||
|
const addedTime = 0;
|
||||||
|
const clock = 0;
|
||||||
|
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||||
|
expect(current).toBe(null);
|
||||||
|
});
|
||||||
|
it('is the remaining time in clock', () => {
|
||||||
|
const startedAt = 0;
|
||||||
|
const duration = 10;
|
||||||
|
const pausedTime = 0;
|
||||||
|
const addedTime = 0;
|
||||||
|
const clock = 1;
|
||||||
|
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||||
|
expect(current).toBe(9);
|
||||||
|
});
|
||||||
|
it('accounts for added times', () => {
|
||||||
|
const startedAt = 0;
|
||||||
|
const duration = 10;
|
||||||
|
const pausedTime = 5;
|
||||||
|
const addedTime = 5;
|
||||||
|
const clock = 1;
|
||||||
|
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||||
|
expect(current).toBe(19);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getElapsedTime()', () => {
|
||||||
|
it('time since we started', () => {
|
||||||
|
const startedAt = 0;
|
||||||
|
const clock = 5;
|
||||||
|
const elapsed = getElapsed(startedAt, clock);
|
||||||
|
expect(elapsed).toBe(5);
|
||||||
|
});
|
||||||
|
it('clock cannot be lower than started time', () => {
|
||||||
|
const startedAt = 10;
|
||||||
|
const clock = 5;
|
||||||
|
expect(() => getElapsed(startedAt, clock)).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getExpectedFinish() getElapsedTime() and getCurrentTime() combined', () => {
|
||||||
|
it('without added times, they combine to be duration', () => {
|
||||||
|
const startedAt = 0;
|
||||||
|
const duration = 10;
|
||||||
|
const finishedAt = null;
|
||||||
|
const pausedTime = 0;
|
||||||
|
const addedTime = 0;
|
||||||
|
const clock = 0;
|
||||||
|
const expectedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||||
|
const elapsed = getElapsed(startedAt, clock);
|
||||||
|
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||||
|
expect(expectedFinish).toBe(10);
|
||||||
|
expect(elapsed).toBe(0);
|
||||||
|
expect(current).toBe(10);
|
||||||
|
expect(elapsed + current).toBe(10);
|
||||||
|
});
|
||||||
|
it('added times influence expected finish', () => {
|
||||||
|
const startedAt = 0;
|
||||||
|
const duration = 10;
|
||||||
|
const finishedAt = null;
|
||||||
|
const pausedTime = 1;
|
||||||
|
const addedTime = 2;
|
||||||
|
const clock = 5;
|
||||||
|
const expectedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||||
|
const elapsed = getElapsed(startedAt, clock);
|
||||||
|
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||||
|
expect(expectedFinish).toBe(13);
|
||||||
|
expect(elapsed).toBe(5);
|
||||||
|
expect(current).toBe(8);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
type MaybeNumber = number | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates expected finish time of a running timer
|
||||||
|
*/
|
||||||
|
export function getExpectedFinish(
|
||||||
|
startedAt: MaybeNumber,
|
||||||
|
finishedAt: MaybeNumber,
|
||||||
|
duration: number,
|
||||||
|
pausedTime: number,
|
||||||
|
addedTime: number,
|
||||||
|
) {
|
||||||
|
if (startedAt === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finishedAt !== null) {
|
||||||
|
return finishedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(startedAt + duration + pausedTime + addedTime, startedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates running countdown
|
||||||
|
*/
|
||||||
|
export function getCurrent(
|
||||||
|
startedAt: MaybeNumber,
|
||||||
|
duration: number,
|
||||||
|
addedTime: number,
|
||||||
|
pausedTime: number,
|
||||||
|
clock: number,
|
||||||
|
) {
|
||||||
|
if (startedAt === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return startedAt + duration + addedTime + pausedTime - clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates elapsed time
|
||||||
|
*/
|
||||||
|
export function getElapsed(startedAt: number, clock: number) {
|
||||||
|
if (startedAt > clock) {
|
||||||
|
throw new Error('clock cannot be higher than startedAt');
|
||||||
|
}
|
||||||
|
return clock - startedAt;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user