From 5323e627fb152bcd0d3e853ad90910555156b92c Mon Sep 17 00:00:00 2001
From: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
Date: Sun, 27 Aug 2023 19:25:40 +0200
Subject: [PATCH] feat: count to end (#500)
---
.../composite/EventEditorTimes.tsx | 1 +
.../rundown/event-block/EventBlockInner.tsx | 4 +
.../features/viewers/common/viewerUtils.ts | 2 +-
apps/server/src/services/TimerService.ts | 28 ++-
.../src/services/__tests__/timerUtils.test.ts | 225 ++++++++++++++++--
apps/server/src/services/timerUtils.ts | 19 +-
.../types/src/definitions/TimerType.type.ts | 3 +-
7 files changed, 259 insertions(+), 23 deletions(-)
diff --git a/apps/client/src/features/event-editor/composite/EventEditorTimes.tsx b/apps/client/src/features/event-editor/composite/EventEditorTimes.tsx
index b568d5166..b3e634f00 100644
--- a/apps/client/src/features/event-editor/composite/EventEditorTimes.tsx
+++ b/apps/client/src/features/event-editor/composite/EventEditorTimes.tsx
@@ -130,6 +130,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
>
+
diff --git a/apps/client/src/features/rundown/event-block/EventBlockInner.tsx b/apps/client/src/features/rundown/event-block/EventBlockInner.tsx
index 3ca8e4749..d15abe82f 100644
--- a/apps/client/src/features/rundown/event-block/EventBlockInner.tsx
+++ b/apps/client/src/features/rundown/event-block/EventBlockInner.tsx
@@ -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 ;
}
+ if (type === TimerType.TimeToEnd) {
+ return ;
+ }
return ;
}
diff --git a/apps/client/src/features/viewers/common/viewerUtils.ts b/apps/client/src/features/viewers/common/viewerUtils.ts
index 637146a70..103ce81e2 100644
--- a/apps/client/src/features/viewers/common/viewerUtils.ts
+++ b/apps/client/src/features/viewers/common/viewerUtils.ts
@@ -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;
diff --git a/apps/server/src/services/TimerService.ts b/apps/server/src/services/TimerService.ts
index 2cad60621..7154d3c99 100644
--- a/apps/server/src/services/TimerService.ts
+++ b/apps/server/src/services/TimerService.ts
@@ -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;
}
diff --git a/apps/server/src/services/__tests__/timerUtils.test.ts b/apps/server/src/services/__tests__/timerUtils.test.ts
index 1d26b9ba3..2a6e15c67 100644
--- a/apps/server/src/services/__tests__/timerUtils.test.ts
+++ b/apps/server/src/services/__tests__/timerUtils.test.ts
@@ -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);
diff --git a/apps/server/src/services/timerUtils.ts b/apps/server/src/services/timerUtils.ts
index 6ee9993b9..8edad2a7f 100644
--- a/apps/server/src/services/timerUtils.ts
+++ b/apps/server/src/services/timerUtils.ts
@@ -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;
diff --git a/packages/types/src/definitions/TimerType.type.ts b/packages/types/src/definitions/TimerType.type.ts
index 25446c941..eb8022109 100644
--- a/packages/types/src/definitions/TimerType.type.ts
+++ b/packages/types/src/definitions/TimerType.type.ts
@@ -1,5 +1,6 @@
export enum TimerType {
CountDown = 'count-down',
CountUp = 'count-up',
- Clock = 'clock'
+ TimeToEnd = 'time-to-end',
+ Clock = 'clock',
}