Relative mode (#1764)

* feat: add group start time to state

* refactor: calculate expected times when offset mode changes

* feat: show relative data in ui

* fixup! feat: add group start time to state

* fixup! refactor: calculate expected times when offset mode changes

* fixup! feat: show relative data in ui

* try to handle multiday

* show planed/expected rundown end day offset values

* refactor: style tweaks to timers

* refactor: cleanup data use

* day offset

* update tests

* test getExpectedStart multiday

* write start epoch to restore file

* current day in relative mode

* remove todo

* move find day offset to utils

---------

Co-authored-by: arc-alex <ac@omnivox.dk>
This commit is contained in:
Carlos Valente
2025-09-17 11:20:14 +02:00
parent 313590905f
commit 8d2db7ffcd
24 changed files with 364 additions and 79 deletions
@@ -1,7 +1,8 @@
import { dayInMs, millisToString } from 'ontime-utils';
import { dayInMs, MILLIS_PER_HOUR, millisToString } from 'ontime-utils';
import { EndAction, Playback, TimeStrategy, TimerPhase, TimerType } from 'ontime-types';
import {
findDayOffset,
getCurrent,
getExpectedFinish,
getRuntimeOffset,
@@ -726,6 +727,7 @@ describe('getRuntimeOffset()', () => {
eventNow: {
id: '1',
timeStart: 100,
dayOffset: 0,
},
timer: {
startedAt: 150,
@@ -738,7 +740,10 @@ describe('getRuntimeOffset()', () => {
rundown: {
actualStart: 150,
plannedStart: 100,
currentDay: 0,
},
clock: 150,
_startDayOffset: 0,
} as RuntimeState;
const { absolute } = getRuntimeOffset(state);
@@ -750,6 +755,7 @@ describe('getRuntimeOffset()', () => {
eventNow: {
id: '1',
timeStart: 100,
dayOffset: 0,
},
timer: {
startedAt: 150, // we started 50ms delayed
@@ -762,7 +768,9 @@ describe('getRuntimeOffset()', () => {
rundown: {
actualStart: 150,
plannedStart: 100,
currentDay: 0,
},
_startDayOffset: 0,
} as RuntimeState;
const { absolute } = getRuntimeOffset(state);
@@ -775,6 +783,7 @@ describe('getRuntimeOffset()', () => {
id: '1',
timeStart: 100,
timeEnd: 140,
dayOffset: 0,
},
timer: {
startedAt: 100, // we started ontime
@@ -787,7 +796,9 @@ describe('getRuntimeOffset()', () => {
rundown: {
actualStart: 100,
plannedStart: 100,
currentDay: 0,
},
_startDayOffset: 0,
} as RuntimeState;
const { absolute } = getRuntimeOffset(state);
@@ -800,6 +811,7 @@ describe('getRuntimeOffset()', () => {
id: '1',
timeStart: 100,
timeEnd: 150,
dayOffset: 0,
},
clock: 150,
timer: {
@@ -813,7 +825,9 @@ describe('getRuntimeOffset()', () => {
rundown: {
actualStart: 100,
plannedStart: 100,
currentDay: 0,
},
_startDayOffset: 0,
} as RuntimeState;
const { absolute } = getRuntimeOffset(state);
@@ -830,6 +844,7 @@ describe('getRuntimeOffset()', () => {
duration: 3600000,
timeStrategy: 'lock-duration',
linkStart: false,
dayOffset: 0,
},
rundown: {
selectedEventIndex: 0,
@@ -837,6 +852,7 @@ describe('getRuntimeOffset()', () => {
plannedStart: 77400000,
plannedEnd: 84600000,
actualStart: null,
currentDay: 0,
},
offset: {
absolute: -77400000,
@@ -852,6 +868,7 @@ describe('getRuntimeOffset()', () => {
secondaryTimer: null,
startedAt: null,
},
_startDayOffset: 0,
_timer: { pausedAt: null },
} as RuntimeState;
@@ -874,6 +891,7 @@ describe('getRuntimeOffset()', () => {
endAction: EndAction.None,
timerType: TimerType.CountDown,
countToEnd: true,
dayOffset: 0,
skip: false,
note: '',
colour: '',
@@ -890,6 +908,7 @@ describe('getRuntimeOffset()', () => {
plannedStart: 77400000, // 21:30:00
plannedEnd: 81000000, // 22:30:00
actualStart: 78000000, // 21:40:00
currentDay: 0,
},
offset: {
absolute: 0,
@@ -905,6 +924,7 @@ describe('getRuntimeOffset()', () => {
secondaryTimer: null,
startedAt: 78000000,
},
_startDayOffset: 0,
_timer: { pausedAt: null },
} as RuntimeState;
@@ -922,6 +942,7 @@ describe('getRuntimeOffset()', () => {
timeStart: 77400000, // 21:30:00
timeEnd: 81000000, // 22:30:00
duration: 3600000, // 01:00:00
dayOffset: 0,
timeStrategy: TimeStrategy.LockEnd,
linkStart: false,
endAction: EndAction.None,
@@ -943,6 +964,7 @@ describe('getRuntimeOffset()', () => {
plannedStart: 77400000, // 21:30:00
plannedEnd: 81000000, // 22:30:00
actualStart: 78000000, // 21:40:00
currentDay: 0,
},
offset: {
absolute: 0,
@@ -958,6 +980,7 @@ describe('getRuntimeOffset()', () => {
secondaryTimer: null,
startedAt: 78000000,
},
_startDayOffset: 0,
_timer: { pausedAt: null },
} as RuntimeState;
@@ -978,6 +1001,7 @@ describe('getRuntimeOffset()', () => {
endAction: EndAction.None,
timerType: TimerType.CountDown,
countToEnd: true,
dayOffset: 0,
},
rundown: {
selectedEventIndex: 0,
@@ -985,6 +1009,7 @@ describe('getRuntimeOffset()', () => {
plannedStart: 77400000, // 21:30:00
plannedEnd: 81000000, // 22:30:00
actualStart: 82000000, // 22:46:40 <--- started now
currentDay: 0,
},
offset: {
absolute: 0,
@@ -1000,6 +1025,7 @@ describe('getRuntimeOffset()', () => {
secondaryTimer: null,
startedAt: 82000000, // <--- started now
},
_startDayOffset: 0,
_timer: { pausedAt: null },
} as RuntimeState;
@@ -1017,6 +1043,7 @@ describe('getRuntimeOffset() relative', () => {
eventNow: {
id: '1',
timeStart: 150,
dayOffset: 0,
},
timer: {
startedAt: 150,
@@ -1029,7 +1056,9 @@ describe('getRuntimeOffset() relative', () => {
rundown: {
actualStart: 150,
plannedStart: 150,
currentDay: 0,
},
_startDayOffset: 0,
} as RuntimeState;
const { absolute, relative } = getRuntimeOffset(state);
@@ -1041,6 +1070,7 @@ describe('getRuntimeOffset() relative', () => {
eventNow: {
id: '1',
timeStart: 100,
dayOffset: 0,
},
timer: {
startedAt: 150,
@@ -1053,7 +1083,9 @@ describe('getRuntimeOffset() relative', () => {
rundown: {
actualStart: 150,
plannedStart: 100,
currentDay: 0,
},
_startDayOffset: 0,
} as RuntimeState;
const { absolute, relative } = getRuntimeOffset(state);
@@ -1065,6 +1097,7 @@ describe('getRuntimeOffset() relative', () => {
eventNow: {
id: '1',
timeStart: 150,
dayOffset: 0,
},
timer: {
startedAt: 100,
@@ -1077,7 +1110,9 @@ describe('getRuntimeOffset() relative', () => {
rundown: {
actualStart: 100,
plannedStart: 150,
currentDay: 0,
},
_startDayOffset: 0,
} as RuntimeState;
const { absolute, relative } = getRuntimeOffset(state);
@@ -1258,3 +1293,21 @@ describe('getTimerPhase()', () => {
expect(phase).toBe(TimerPhase.Pending);
});
});
describe('findDay()', () => {
test('finds dayOffset', () => {
//both have 1 hour offset but the clock are on different days
expect(findDayOffset(0, 23 * MILLIS_PER_HOUR)).toBe(-1); // -> 23
expect(findDayOffset(0, 13 * MILLIS_PER_HOUR)).toBe(-1); // -> 13
expect(findDayOffset(0, 12 * MILLIS_PER_HOUR)).toBe(-1); // -> 12
expect(findDayOffset(0, 11 * MILLIS_PER_HOUR)).toBe(0); // -> 11
expect(findDayOffset(1 * MILLIS_PER_HOUR, 0)).toBe(0); // -> -1
//both have 1 hour offset but the clock are on different days
expect(findDayOffset(23 * MILLIS_PER_HOUR, 0)).toBe(1); // -> -23
expect(findDayOffset(13 * MILLIS_PER_HOUR, 0)).toBe(1); // -> -13
expect(findDayOffset(12 * MILLIS_PER_HOUR, 0)).toBe(0); // -> -12
expect(findDayOffset(11 * MILLIS_PER_HOUR, 0)).toBe(0); // -> -11
expect(findDayOffset(22 * MILLIS_PER_HOUR, 23 * MILLIS_PER_HOUR)).toBe(0); // -> 1
});
});