mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-14 03:43:50 +00:00
feat: count to end (#500)
This commit is contained in:
@@ -130,6 +130,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
||||
>
|
||||
<option value={TimerType.CountDown}>Count down</option>
|
||||
<option value={TimerType.CountUp}>Count up</option>
|
||||
<option value={TimerType.TimeToEnd}>Time to end</option>
|
||||
<option value={TimerType.Clock}>Clock</option>
|
||||
</Select>
|
||||
<label className={style.inputLabel}>End Action</label>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { BiArrowToBottom } from '@react-icons/all-files/bi/BiArrowToBottom';
|
||||
import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown';
|
||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
||||
@@ -196,5 +197,8 @@ function TimerIcon(props: { type: TimerType; className: string }) {
|
||||
if (type === TimerType.Clock) {
|
||||
return <IoTime className={className} />;
|
||||
}
|
||||
if (type === TimerType.TimeToEnd) {
|
||||
return <BiArrowToBottom className={className} />;
|
||||
}
|
||||
return <IoArrowDown className={className} />;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export function getTimerByType(timerObject?: TimerTypeParams): string | number |
|
||||
return timer;
|
||||
}
|
||||
|
||||
if (timerObject.timerType === TimerType.CountDown) {
|
||||
if (timerObject.timerType === TimerType.CountDown || timerObject.timerType === TimerType.TimeToEnd) {
|
||||
timer = timerObject.current;
|
||||
} else if (timerObject.timerType === TimerType.CountUp) {
|
||||
timer = timerObject.elapsed;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EndAction, OntimeEvent, Playback, TimerLifeCycle, TimerState } from 'ontime-types';
|
||||
import { EndAction, OntimeEvent, Playback, TimerLifeCycle, TimerState, TimerType } from 'ontime-types';
|
||||
import { calculateDuration, dayInMs } from 'ontime-utils';
|
||||
|
||||
import { eventStore } from '../stores/EventStore.js';
|
||||
@@ -24,6 +24,9 @@ export class TimerService {
|
||||
timer: TimerState;
|
||||
|
||||
loadedTimerId: string | null;
|
||||
private loadedTimerStart: number | null;
|
||||
private loadedTimerEnd: number | null;
|
||||
|
||||
private pausedTime: number;
|
||||
private pausedAt: number | null;
|
||||
private secondaryTarget: number | null;
|
||||
@@ -61,6 +64,9 @@ export class TimerService {
|
||||
endAction: null,
|
||||
};
|
||||
this.loadedTimerId = null;
|
||||
this.loadedTimerStart = null;
|
||||
this.loadedTimerEnd = null;
|
||||
|
||||
this.pausedTime = 0;
|
||||
this.pausedAt = null;
|
||||
this.secondaryTarget = null;
|
||||
@@ -93,6 +99,8 @@ export class TimerService {
|
||||
this.timer.duration = calculateDuration(timer.timeStart, timer.timeEnd);
|
||||
this.timer.timerType = timer.timerType;
|
||||
this.timer.endAction = timer.endAction;
|
||||
this.loadedTimerStart = timer.timeStart;
|
||||
this.loadedTimerEnd = timer.timeEnd;
|
||||
|
||||
// this might not be ideal
|
||||
this.timer.finishedAt = null;
|
||||
@@ -102,6 +110,8 @@ export class TimerService {
|
||||
this.timer.duration,
|
||||
this.pausedTime,
|
||||
this.timer.addedTime,
|
||||
this.loadedTimerEnd,
|
||||
this.timer.timerType,
|
||||
);
|
||||
if (this.timer.startedAt === null) {
|
||||
this.timer.current = this.timer.duration;
|
||||
@@ -129,14 +139,22 @@ export class TimerService {
|
||||
this._clear();
|
||||
|
||||
this.loadedTimerId = timer.id;
|
||||
this.loadedTimerStart = timer.timeStart;
|
||||
this.loadedTimerEnd = timer.timeEnd;
|
||||
|
||||
this.timer.duration = calculateDuration(timer.timeStart, timer.timeEnd);
|
||||
this.timer.current = this.timer.duration;
|
||||
this.playback = Playback.Armed;
|
||||
this.timer.timerType = timer.timerType;
|
||||
this.timer.endAction = timer.endAction;
|
||||
this.pausedTime = 0;
|
||||
this.pausedAt = 0;
|
||||
|
||||
this.timer.current = this.timer.duration;
|
||||
if (this.timer.timerType === TimerType.TimeToEnd) {
|
||||
const now = clock.timeNow();
|
||||
this.timer.current = getCurrent(now, this.timer.duration, 0, 0, now, timer.timeEnd, this.timer.timerType);
|
||||
}
|
||||
|
||||
if (typeof initialData !== 'undefined') {
|
||||
this.timer = { ...this.timer, ...initialData };
|
||||
}
|
||||
@@ -188,6 +206,8 @@ export class TimerService {
|
||||
this.timer.duration,
|
||||
this.pausedTime,
|
||||
this.timer.addedTime,
|
||||
this.loadedTimerEnd,
|
||||
this.timer.timerType,
|
||||
);
|
||||
this._onStart();
|
||||
}
|
||||
@@ -310,6 +330,8 @@ export class TimerService {
|
||||
this.timer.duration,
|
||||
this.pausedTime,
|
||||
this.timer.addedTime,
|
||||
this.loadedTimerEnd,
|
||||
this.timer.timerType,
|
||||
);
|
||||
}
|
||||
this.timer.current = getCurrent(
|
||||
@@ -318,6 +340,8 @@ export class TimerService {
|
||||
this.timer.addedTime,
|
||||
this.pausedTime,
|
||||
this.timer.clock,
|
||||
this.loadedTimerEnd,
|
||||
this.timer.timerType,
|
||||
);
|
||||
this.timer.elapsed = this.timer.duration - this.timer.current;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { dayInMs } from 'ontime-utils';
|
||||
import { TimerType } from 'ontime-types';
|
||||
|
||||
import { getCurrent, getExpectedFinish } from '../timerUtils.js';
|
||||
|
||||
@@ -9,7 +10,17 @@ describe('getExpectedFinish()', () => {
|
||||
const duration = 10;
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||
const endTime = 10;
|
||||
const timerType = TimerType.CountDown;
|
||||
const calculatedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
expect(calculatedFinish).toBe(null);
|
||||
});
|
||||
it('is finishedAt if defined', () => {
|
||||
@@ -18,7 +29,17 @@ describe('getExpectedFinish()', () => {
|
||||
const duration = 10;
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||
const endTime = 20;
|
||||
const timerType = TimerType.CountDown;
|
||||
const calculatedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
expect(calculatedFinish).toBe(finishedAt);
|
||||
});
|
||||
it('calculates the finish time', () => {
|
||||
@@ -27,7 +48,17 @@ describe('getExpectedFinish()', () => {
|
||||
const duration = 10;
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||
const endTime = 11;
|
||||
const timerType = TimerType.CountDown;
|
||||
const calculatedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
expect(calculatedFinish).toBe(11);
|
||||
});
|
||||
it('adds paused and added times', () => {
|
||||
@@ -36,7 +67,17 @@ describe('getExpectedFinish()', () => {
|
||||
const duration = 10;
|
||||
const pausedTime = 10;
|
||||
const addedTime = 10;
|
||||
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||
const endTime = 11;
|
||||
const timerType = TimerType.CountDown;
|
||||
const calculatedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
expect(calculatedFinish).toBe(31);
|
||||
});
|
||||
it('added time could be negative', () => {
|
||||
@@ -45,7 +86,17 @@ describe('getExpectedFinish()', () => {
|
||||
const duration = 10;
|
||||
const pausedTime = 10;
|
||||
const addedTime = -10;
|
||||
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||
const endTime = 11;
|
||||
const timerType = TimerType.CountDown;
|
||||
const calculatedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
expect(calculatedFinish).toBe(11);
|
||||
});
|
||||
it('user could add enough time for it to be negative', () => {
|
||||
@@ -54,7 +105,17 @@ describe('getExpectedFinish()', () => {
|
||||
const duration = 10;
|
||||
const pausedTime = 0;
|
||||
const addedTime = -100;
|
||||
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||
const endTime = 11;
|
||||
const timerType = TimerType.CountDown;
|
||||
const calculatedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
expect(calculatedFinish).toBe(1);
|
||||
});
|
||||
it('timer can have no duration', () => {
|
||||
@@ -63,7 +124,17 @@ describe('getExpectedFinish()', () => {
|
||||
const duration = 0;
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||
const endTime = 0;
|
||||
const timerType = TimerType.CountDown;
|
||||
const calculatedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
expect(calculatedFinish).toBe(1);
|
||||
});
|
||||
it('finish can be the day after', () => {
|
||||
@@ -72,9 +143,60 @@ describe('getExpectedFinish()', () => {
|
||||
const duration = dayInMs;
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const calculatedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||
const endTime = 10;
|
||||
const timerType = TimerType.CountDown;
|
||||
const calculatedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
expect(calculatedFinish).toBe(10);
|
||||
});
|
||||
describe('on timers of type time-to-end', () => {
|
||||
it('finish time is as schedule + added time', () => {
|
||||
const startedAt = 10;
|
||||
const finishedAt = null;
|
||||
const duration = dayInMs;
|
||||
const pausedTime = 0;
|
||||
const addedTime = 10;
|
||||
const endTime = 30;
|
||||
const timerType = TimerType.TimeToEnd;
|
||||
const calculatedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
expect(calculatedFinish).toBe(40);
|
||||
});
|
||||
it('handles events that finish the day after', () => {
|
||||
const startedAt = 79200000; // 22:00:00
|
||||
const finishedAt = null;
|
||||
const duration = Infinity; // not relevant
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const endTime = 600000; // 00:10:00
|
||||
const timerType = TimerType.TimeToEnd;
|
||||
const calculatedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
// expected finish is not a duration but a timetag
|
||||
expect(calculatedFinish).toBe(600000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCurrent()', () => {
|
||||
@@ -84,7 +206,9 @@ describe('getCurrent()', () => {
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const clock = 0;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||
const endTime = 0;
|
||||
const timerType = TimerType.CountDown;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
expect(current).toBe(null);
|
||||
});
|
||||
it('is the remaining time in clock', () => {
|
||||
@@ -93,7 +217,9 @@ describe('getCurrent()', () => {
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const clock = 1;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||
const endTime = 10;
|
||||
const timerType = TimerType.CountDown;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
expect(current).toBe(9);
|
||||
});
|
||||
it('accounts for added times', () => {
|
||||
@@ -102,7 +228,9 @@ describe('getCurrent()', () => {
|
||||
const pausedTime = 5;
|
||||
const addedTime = 5;
|
||||
const clock = 1;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||
const endTime = 10;
|
||||
const timerType = TimerType.CountDown;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
expect(current).toBe(19);
|
||||
});
|
||||
it('counts over midnight', () => {
|
||||
@@ -111,7 +239,9 @@ describe('getCurrent()', () => {
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const clock = 10;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||
const endTime = 20;
|
||||
const timerType = TimerType.CountDown;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
expect(current).toBe(dayInMs + 10);
|
||||
});
|
||||
it('rolls over midnight', () => {
|
||||
@@ -120,7 +250,9 @@ describe('getCurrent()', () => {
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const clock = 5;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||
const endTime = 20;
|
||||
const timerType = TimerType.CountDown;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
expect(current).toBe(15);
|
||||
});
|
||||
it('midnight holds delays', () => {
|
||||
@@ -129,9 +261,46 @@ describe('getCurrent()', () => {
|
||||
const pausedTime = 10;
|
||||
const addedTime = 10;
|
||||
const clock = 5;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||
const endTime = 20;
|
||||
const timerType = TimerType.CountDown;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
expect(current).toBe(35);
|
||||
});
|
||||
describe('on timers of type time-to-end', () => {
|
||||
it('current time is the time to end', () => {
|
||||
const startedAt = 10;
|
||||
const duration = 100;
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const clock = 30;
|
||||
const endTime = 100;
|
||||
const timerType = TimerType.TimeToEnd;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
expect(current).toBe(70);
|
||||
});
|
||||
it('current time is the time to end + added time', () => {
|
||||
const startedAt = 10;
|
||||
const duration = 100;
|
||||
const pausedTime = 3;
|
||||
const addedTime = 4;
|
||||
const clock = 30;
|
||||
const endTime = 100;
|
||||
const timerType = TimerType.TimeToEnd;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
expect(current).toBe(77);
|
||||
});
|
||||
it('handles events that finish the day after', () => {
|
||||
const startedAt = 79200000; // 22:00:00
|
||||
const duration = Infinity; // not relevant
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const clock = 79500000; // 22:05:00
|
||||
const endTime = 600000; // 00:10:00
|
||||
const timerType = TimerType.TimeToEnd;
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
expect(current).toBe(600000 + dayInMs - clock);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExpectedFinish() and getCurrentTime() combined', () => {
|
||||
@@ -142,8 +311,18 @@ describe('getExpectedFinish() and getCurrentTime() combined', () => {
|
||||
const pausedTime = 0;
|
||||
const addedTime = 0;
|
||||
const clock = 0;
|
||||
const expectedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||
const endTime = 10;
|
||||
const timerType = TimerType.CountDown;
|
||||
const expectedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
const elapsed = duration - current;
|
||||
expect(expectedFinish).toBe(10);
|
||||
expect(elapsed).toBe(0);
|
||||
@@ -157,8 +336,18 @@ describe('getExpectedFinish() and getCurrentTime() combined', () => {
|
||||
const pausedTime = 1;
|
||||
const addedTime = 2;
|
||||
const clock = 5;
|
||||
const expectedFinish = getExpectedFinish(startedAt, finishedAt, duration, pausedTime, addedTime);
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock);
|
||||
const endTime = 10;
|
||||
const timerType = TimerType.CountDown;
|
||||
const expectedFinish = getExpectedFinish(
|
||||
startedAt,
|
||||
finishedAt,
|
||||
duration,
|
||||
pausedTime,
|
||||
addedTime,
|
||||
endTime,
|
||||
timerType,
|
||||
);
|
||||
const current = getCurrent(startedAt, duration, addedTime, pausedTime, clock, endTime, timerType);
|
||||
const elapsed = duration - current;
|
||||
expect(expectedFinish).toBe(13);
|
||||
expect(elapsed).toBe(2);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MaybeNumber } from 'ontime-types';
|
||||
import { MaybeNumber, TimerType } from 'ontime-types';
|
||||
import { dayInMs } from 'ontime-utils';
|
||||
|
||||
/**
|
||||
@@ -10,6 +10,8 @@ export function getExpectedFinish(
|
||||
duration: number,
|
||||
pausedTime: number,
|
||||
addedTime: number,
|
||||
timeEnd: number,
|
||||
timerType: TimerType,
|
||||
) {
|
||||
if (startedAt === null) {
|
||||
return null;
|
||||
@@ -19,6 +21,10 @@ export function getExpectedFinish(
|
||||
return finishedAt;
|
||||
}
|
||||
|
||||
if (timerType === TimerType.TimeToEnd) {
|
||||
return timeEnd + addedTime + pausedTime;
|
||||
}
|
||||
|
||||
// handle events that finish the day after
|
||||
const expectedFinish = startedAt + duration + pausedTime + addedTime;
|
||||
if (expectedFinish > dayInMs) {
|
||||
@@ -38,11 +44,22 @@ export function getCurrent(
|
||||
addedTime: number,
|
||||
pausedTime: number,
|
||||
clock: number,
|
||||
timeEnd: number,
|
||||
timerType: TimerType,
|
||||
) {
|
||||
if (startedAt === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (timerType === TimerType.TimeToEnd) {
|
||||
if (startedAt > timeEnd) {
|
||||
return timeEnd + addedTime + pausedTime + dayInMs - clock;
|
||||
}
|
||||
return timeEnd + addedTime + pausedTime - clock;
|
||||
}
|
||||
|
||||
if (startedAt > clock) {
|
||||
// we are the day after the event was started
|
||||
return startedAt + duration + addedTime + pausedTime - clock - dayInMs;
|
||||
}
|
||||
return startedAt + duration + addedTime + pausedTime - clock;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export enum TimerType {
|
||||
CountDown = 'count-down',
|
||||
CountUp = 'count-up',
|
||||
Clock = 'clock'
|
||||
TimeToEnd = 'time-to-end',
|
||||
Clock = 'clock',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user