diff --git a/apps/client/src/common/hooks/useSocket.ts b/apps/client/src/common/hooks/useSocket.ts
index e4cab2010..7c97c4337 100644
--- a/apps/client/src/common/hooks/useSocket.ts
+++ b/apps/client/src/common/hooks/useSocket.ts
@@ -1,4 +1,4 @@
-import { RuntimeStore, SimpleDirection, SimplePlayback, TimerMessage } from 'ontime-types';
+import { OffsetMode, RuntimeStore, SimpleDirection, SimplePlayback, TimerMessage } from 'ontime-types';
import { useRuntimeStore } from '../stores/runtime';
import { socketSendJson } from '../utils/socket';
@@ -189,6 +189,12 @@ export const useIsOnline = createSelector((state: RuntimeStore) => ({
isOnline: state.ping > 0,
}));
+export const useOffsetMode = createSelector((state: RuntimeStore) => ({
+ offsetMode: state.runtime.offsetMode,
+}));
+
+export const setOffsetMode = (payload: OffsetMode) => socketSendJson('offsetmode', payload);
+
export const usePlayback = () => {
const featureSelector = (state: RuntimeStore) => ({
playback: state.timer.playback,
diff --git a/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx b/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx
index 7ca1b20eb..deb5fdaaa 100644
--- a/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx
+++ b/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx
@@ -1,5 +1,7 @@
import { Button, ButtonGroup } from '@chakra-ui/react';
+import { OffsetMode } from 'ontime-types';
+import { setOffsetMode, useOffsetMode } from '../../../common/hooks/useSocket';
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
import RundownMenu from './RundownMenu';
@@ -12,6 +14,8 @@ export default function RundownHeader() {
const setRunMode = () => setAppMode(AppMode.Run);
const setEditMode = () => setAppMode(AppMode.Edit);
+ const { offsetMode } = useOffsetMode();
+
return (
@@ -22,6 +26,22 @@ export default function RundownHeader() {
Edit
+
+
+
+
);
diff --git a/apps/server/src/api-data/db/db.controller.ts b/apps/server/src/api-data/db/db.controller.ts
index 8ed9fc02b..8b505ce06 100644
--- a/apps/server/src/api-data/db/db.controller.ts
+++ b/apps/server/src/api-data/db/db.controller.ts
@@ -19,7 +19,7 @@ import * as projectService from '../../services/project-service/ProjectService.j
export async function patchPartialProjectFile(req: Request, res: Response) {
try {
const { rundown, project, settings, viewSettings, urlPresets, customFields, automation } = req.body;
- const patchDb: DatabaseModel = {
+ const patchDb: Partial = {
rundown,
project,
settings,
diff --git a/apps/server/src/api-integration/integration.controller.ts b/apps/server/src/api-integration/integration.controller.ts
index ded76835e..73a6e3ac1 100644
--- a/apps/server/src/api-integration/integration.controller.ts
+++ b/apps/server/src/api-integration/integration.controller.ts
@@ -1,4 +1,4 @@
-import { MessageState, OntimeEvent, SimpleDirection, SimplePlayback } from 'ontime-types';
+import { MessageState, OffsetMode, OntimeEvent, SimpleDirection, SimplePlayback } from 'ontime-types';
import { MILLIS_PER_HOUR, MILLIS_PER_SECOND } from 'ontime-utils';
import { DeepPartial } from 'ts-essentials';
@@ -17,6 +17,7 @@ import { throttle } from '../utils/throttle.js';
import { willCauseRegeneration } from '../services/rundown-service/rundownCacheUtils.js';
import { handleLegacyMessageConversion } from './integration.legacy.js';
+import { coerceEnum } from '../utils/coerceType.js';
const throttledUpdateEvent = throttle(updateEvent, 20);
let lastRequest: Date | null = null;
@@ -286,6 +287,11 @@ const actionHandlers: Record = {
throw new Error('No matching method provided');
},
+ offsetmode: (payload) => {
+ const mode = coerceEnum(payload, OffsetMode);
+ runtimeService.setOffsetMode(mode);
+ return { payload: 'success' };
+ },
};
/**
diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts
index 5dda9aa1e..83e631964 100644
--- a/apps/server/src/app.ts
+++ b/apps/server/src/app.ts
@@ -170,6 +170,8 @@ export const startServer = async (
socket.init(expressServer, showWelcome, prefix);
+ const offsetMode = getDataProvider().getOffsetMode();
+
/**
* Module initialises the services and provides initial payload for the store
*/
@@ -210,6 +212,7 @@ export const startServer = async (
// TODO: pass event store to rundownservice
runtimeService.init(maybeRestorePoint);
+ runtimeService.setOffsetMode(offsetMode);
const nif = getNetworkInterfaces();
consoleSuccess(`Local: http://localhost:${resultPort}${prefix}/editor`);
diff --git a/apps/server/src/classes/data-provider/DataProvider.ts b/apps/server/src/classes/data-provider/DataProvider.ts
index d9a79ecdc..09711681e 100644
--- a/apps/server/src/classes/data-provider/DataProvider.ts
+++ b/apps/server/src/classes/data-provider/DataProvider.ts
@@ -7,6 +7,7 @@ import {
CustomFields,
URLPreset,
AutomationSettings,
+ OffsetMode,
} from 'ontime-types';
import type { Low } from 'lowdb';
@@ -55,6 +56,8 @@ export function getDataProvider() {
setAutomation,
getRundown,
mergeIntoData,
+ getOffsetMode,
+ setOffsetMode,
};
}
@@ -132,6 +135,17 @@ function getRundown(): Readonly {
return db.data.rundown;
}
+function getOffsetMode(): Readonly {
+ return db.data.offsetMode;
+}
+
+async function setOffsetMode(mode): ReadonlyPromise {
+ db.data.offsetMode = mode;
+ //TODO: should this maybe not be persisted every time
+ await persist();
+ return db.data.offsetMode;
+}
+
async function mergeIntoData(newData: Partial): ReadonlyPromise {
const mergedData = safeMerge(db.data, newData);
db.data.project = mergedData.project;
diff --git a/apps/server/src/classes/data-provider/__tests__/DataProvider.utils.test.ts b/apps/server/src/classes/data-provider/__tests__/DataProvider.utils.test.ts
index 28314cf30..7c4a636dc 100644
--- a/apps/server/src/classes/data-provider/__tests__/DataProvider.utils.test.ts
+++ b/apps/server/src/classes/data-provider/__tests__/DataProvider.utils.test.ts
@@ -1,4 +1,4 @@
-import { DatabaseModel, OntimeRundown, Settings, URLPreset, ViewSettings } from 'ontime-types';
+import { DatabaseModel, OffsetMode, OntimeRundown, Settings, URLPreset, ViewSettings } from 'ontime-types';
import { safeMerge } from '../DataProvider.utils.js';
describe('safeMerge', () => {
@@ -42,6 +42,7 @@ describe('safeMerge', () => {
triggers: [],
automations: {},
},
+ offsetMode: OffsetMode.Absolute,
} as DatabaseModel;
it('returns existing data if new data is not provided', () => {
diff --git a/apps/server/src/models/dataModel.ts b/apps/server/src/models/dataModel.ts
index a4a73a437..fae7687c9 100644
--- a/apps/server/src/models/dataModel.ts
+++ b/apps/server/src/models/dataModel.ts
@@ -1,4 +1,4 @@
-import { DatabaseModel } from 'ontime-types';
+import { DatabaseModel, OffsetMode } from 'ontime-types';
import { ONTIME_VERSION } from '../ONTIME_VERSION.js';
export const dbModel: DatabaseModel = {
@@ -38,4 +38,5 @@ export const dbModel: DatabaseModel = {
triggers: [],
automations: {},
},
+ offsetMode: OffsetMode.Absolute,
};
diff --git a/apps/server/src/models/demoProject.ts b/apps/server/src/models/demoProject.ts
index b57e35dd6..357d3a05b 100644
--- a/apps/server/src/models/demoProject.ts
+++ b/apps/server/src/models/demoProject.ts
@@ -1,4 +1,4 @@
-import { DatabaseModel, EndAction, SupportedEvent, TimeStrategy, TimerType } from 'ontime-types';
+import { DatabaseModel, EndAction, OffsetMode, SupportedEvent, TimeStrategy, TimerType } from 'ontime-types';
export const demoDb: DatabaseModel = {
rundown: [
@@ -457,4 +457,5 @@ export const demoDb: DatabaseModel = {
triggers: [],
automations: {},
},
+ offsetMode: OffsetMode.Absolute,
};
diff --git a/apps/server/src/services/__tests__/timerUtils.test.ts b/apps/server/src/services/__tests__/timerUtils.test.ts
index a418e7fd3..6026004cf 100644
--- a/apps/server/src/services/__tests__/timerUtils.test.ts
+++ b/apps/server/src/services/__tests__/timerUtils.test.ts
@@ -4,6 +4,7 @@ import { EndAction, Playback, TimeStrategy, TimerPhase, TimerType } from 'ontime
import {
getCurrent,
getExpectedFinish,
+ getRelativeOffset,
getRuntimeOffset,
getTimerPhase,
normaliseEndTime,
@@ -1046,6 +1047,84 @@ describe('getRuntimeOffset()', () => {
});
});
+describe('getRelativeOffset()', () => {
+ it('relative offset is 0 when starting at the planed time', () => {
+ const state = {
+ eventNow: {
+ id: '1',
+ timeStart: 150,
+ },
+ timer: {
+ startedAt: 150,
+ addedTime: 0,
+ current: 0,
+ },
+ _timer: {
+ pausedAt: null,
+ },
+ runtime: {
+ actualStart: 150,
+ plannedStart: 150,
+ },
+ } as RuntimeState;
+
+ state.runtime.offset = getRuntimeOffset(state);
+ expect(state.runtime.offset).toBe(0);
+ const relativeOffsetoffset = getRelativeOffset(state);
+ expect(relativeOffsetoffset).toBe(0);
+ });
+ it('relative offset is 0 when starting after the planed time', () => {
+ const state = {
+ eventNow: {
+ id: '1',
+ timeStart: 100,
+ },
+ timer: {
+ startedAt: 150,
+ addedTime: 0,
+ current: 0,
+ },
+ _timer: {
+ pausedAt: null,
+ },
+ runtime: {
+ actualStart: 150,
+ plannedStart: 100,
+ },
+ } as RuntimeState;
+
+ state.runtime.offset = getRuntimeOffset(state);
+ expect(state.runtime.offset).toBe(-50);
+ const relativeOffsetoffset = getRelativeOffset(state);
+ expect(relativeOffsetoffset).toBe(0);
+ });
+ it('relative offset is 0 when starting before the planed time', () => {
+ const state = {
+ eventNow: {
+ id: '1',
+ timeStart: 150,
+ },
+ timer: {
+ startedAt: 100,
+ addedTime: 0,
+ current: 0,
+ },
+ _timer: {
+ pausedAt: null,
+ },
+ runtime: {
+ actualStart: 100,
+ plannedStart: 150,
+ },
+ } as RuntimeState;
+
+ state.runtime.offset = getRuntimeOffset(state);
+ expect(state.runtime.offset).toBe(50);
+ const relativeOffsetoffset = getRelativeOffset(state);
+ expect(relativeOffsetoffset).toBe(0);
+ });
+});
+
describe('getTimerPhase()', () => {
it('should be None if the timer is not running', () => {
const state = {
diff --git a/apps/server/src/services/runtime-service/RuntimeService.ts b/apps/server/src/services/runtime-service/RuntimeService.ts
index 1dc486992..ff6215e88 100644
--- a/apps/server/src/services/runtime-service/RuntimeService.ts
+++ b/apps/server/src/services/runtime-service/RuntimeService.ts
@@ -4,6 +4,7 @@ import {
isPlayableEvent,
LogOrigin,
MaybeNumber,
+ OffsetMode,
OntimeEvent,
Playback,
TimerLifeCycle,
@@ -37,6 +38,7 @@ import {
import { getForceUpdate, getShouldClockUpdate, getShouldTimerUpdate } from './rundownService.utils.js';
import { skippedOutOfEvent } from '../timerUtils.js';
import { triggerAutomations } from '../../api-data/automation/automation.service.js';
+import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
type RuntimeStateEventKeys = keyof Pick;
@@ -68,6 +70,14 @@ class RuntimeService {
RuntimeService.previousState = {} as RuntimeState;
}
+ @broadcastResult
+ setOffsetMode(mode: OffsetMode) {
+ runtimeState.setOffsetMode(mode);
+ process.nextTick(() => {
+ getDataProvider().setOffsetMode(mode);
+ });
+ }
+
/**
* Checks result of an update and notifies integrations as needed
* This is the only exception of a private method that has broadcast result
diff --git a/apps/server/src/services/timerUtils.ts b/apps/server/src/services/timerUtils.ts
index 2cc59c4ea..e69563bd1 100644
--- a/apps/server/src/services/timerUtils.ts
+++ b/apps/server/src/services/timerUtils.ts
@@ -156,6 +156,16 @@ export function getRuntimeOffset(state: RuntimeState): number {
return startOffset - addedTime - pausedTime + overtime;
}
+/**
+ * Calculates relative offset
+ * should always be calculated after the absolute offset
+ */
+export function getRelativeOffset(state: RuntimeState): number {
+ const { actualStart, plannedStart } = state.runtime;
+ const relativeStartOffset = actualStart - plannedStart;
+ return state.runtime.offset + relativeStartOffset;
+}
+
/**
* Calculates the expected end of the rundown
*/
diff --git a/apps/server/src/stores/__mocks__/runtimeState.mocks.ts b/apps/server/src/stores/__mocks__/runtimeState.mocks.ts
index 7c4d96839..02b1eac42 100644
--- a/apps/server/src/stores/__mocks__/runtimeState.mocks.ts
+++ b/apps/server/src/stores/__mocks__/runtimeState.mocks.ts
@@ -1,4 +1,4 @@
-import { TimerPhase, Playback } from 'ontime-types';
+import { TimerPhase, Playback, OffsetMode } from 'ontime-types';
import { deepmerge } from 'ontime-utils';
import type { RuntimeState } from '../runtimeState.js';
@@ -16,10 +16,12 @@ const baseState: RuntimeState = {
selectedEventIndex: null,
numEvents: 0,
offset: 0,
+ relativeOffset: 0,
plannedStart: 0,
plannedEnd: 0,
actualStart: null,
expectedEnd: null,
+ offsetMode: OffsetMode.Absolute,
},
timer: {
addedTime: 0,
diff --git a/apps/server/src/stores/__tests__/runtimeState.test.ts b/apps/server/src/stores/__tests__/runtimeState.test.ts
index 2abae6b89..5351bbf3f 100644
--- a/apps/server/src/stores/__tests__/runtimeState.test.ts
+++ b/apps/server/src/stores/__tests__/runtimeState.test.ts
@@ -4,7 +4,7 @@ import { deepmerge } from 'ontime-utils';
import {
type RuntimeState,
addTime,
- clear,
+ hardClear,
getState,
load,
loadBlock,
@@ -71,7 +71,7 @@ beforeAll(() => {
describe('mutation on runtimeState', () => {
beforeEach(() => {
- clear();
+ hardClear();
vi.mock('../../services/rundown-service/RundownService.js', async (importOriginal) => {
const actual = (await importOriginal()) as object;
@@ -246,7 +246,7 @@ describe('roll mode', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime('jan 1 00:00');
- clear();
+ hardClear();
});
afterEach(() => {
vi.useRealTimers();
diff --git a/apps/server/src/stores/runtimeState.ts b/apps/server/src/stores/runtimeState.ts
index 3c01871e1..5776aad6e 100644
--- a/apps/server/src/stores/runtimeState.ts
+++ b/apps/server/src/stores/runtimeState.ts
@@ -3,6 +3,7 @@ import {
isPlayableEvent,
MaybeNumber,
MaybeString,
+ OffsetMode,
OntimeEvent,
OntimeRundown,
PlayableEvent,
@@ -27,6 +28,7 @@ import {
getCurrent,
getExpectedEnd,
getExpectedFinish,
+ getRelativeOffset,
getRuntimeOffset,
getTimerPhase,
} from '../services/timerUtils.js';
@@ -82,7 +84,32 @@ export function getState(): Readonly {
};
}
-export function clear() {
+//TODO: find other thinbgs that dose not need clearing
+export function softClear() {
+ runtimeState.eventNow = null;
+ runtimeState.publicEventNow = null;
+ runtimeState.eventNext = null;
+ runtimeState.publicEventNext = null;
+
+ // runtimeState.currentBlock.block = null;
+ // runtimeState.currentBlock.startedAt = null;
+
+ runtimeState.runtime.offset = 0;
+ runtimeState.runtime.relativeOffset = 0;
+ runtimeState.runtime.expectedEnd = null;
+ runtimeState.runtime.selectedEventIndex = null;
+
+ runtimeState.timer.playback = Playback.Stop;
+ runtimeState.clock = clock.timeNow();
+ runtimeState.timer = { ...runtimeStorePlaceholder.timer };
+
+ // when clearing, we maintain the total delay from the rundown
+ runtimeState._timer.forceFinish = null;
+ runtimeState._timer.pausedAt = null;
+ runtimeState._timer.secondaryTarget = null;
+}
+
+export function hardClear() {
runtimeState.eventNow = null;
runtimeState.publicEventNow = null;
runtimeState.eventNext = null;
@@ -93,6 +120,7 @@ export function clear() {
runtimeState.publicEventNext = null;
runtimeState.runtime.offset = 0;
+ runtimeState.runtime.relativeOffset = 0;
runtimeState.runtime.actualStart = null;
runtimeState.runtime.expectedEnd = null;
runtimeState.runtime.selectedEventIndex = null;
@@ -155,9 +183,9 @@ export function load(
initialData?: Partial,
): boolean {
// we need to persist the current block state across loads
- const prevCurrentBlock = { ...runtimeState.currentBlock };
- clear();
- runtimeState.currentBlock = prevCurrentBlock;
+ // const prevCurrentBlock = { ...runtimeState.currentBlock };
+ softClear();
+ // runtimeState.currentBlock = prevCurrentBlock;
// filter rundown
const timedEvents = filterTimedEvents(rundown);
@@ -185,6 +213,7 @@ export function load(
if (firstStart === null || typeof firstStart === 'number') {
runtimeState.runtime.actualStart = firstStart;
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
+ runtimeState.runtime.relativeOffset = getRelativeOffset(runtimeState);
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
}
if (typeof initialData.blockStartAt === 'number') {
@@ -390,6 +419,7 @@ export function start(state: RuntimeState = runtimeState): boolean {
// update offset
state.runtime.offset = getRuntimeOffset(state);
+ state.runtime.relativeOffset = getRelativeOffset(state);
state.runtime.expectedEnd = state.runtime.plannedEnd - state.runtime.offset;
return true;
}
@@ -409,9 +439,7 @@ export function stop(state: RuntimeState = runtimeState): boolean {
if (state.timer.playback === Playback.Stop) {
return false;
}
- clear();
- runtimeState.runtime.actualStart = null;
- runtimeState.runtime.expectedEnd = null;
+ hardClear();
return true;
}
@@ -453,6 +481,7 @@ export function addTime(amount: number) {
// update runtime delays: over - under
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
+ runtimeState.runtime.relativeOffset = getRelativeOffset(runtimeState);
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
return true;
@@ -498,6 +527,7 @@ export function update(): UpdateResult {
// update runtime, needs up-to-date timer state
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
+ runtimeState.runtime.relativeOffset = getRelativeOffset(runtimeState);
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
const finishedNow =
@@ -604,9 +634,7 @@ export function roll(rundown: OntimeRundown, offset = 0): { eventId: MaybeString
}
// we need to persist the current block state across loads
- const prevCurrentBlock = { ...runtimeState.currentBlock };
- clear();
- runtimeState.currentBlock = prevCurrentBlock;
+ softClear();
//account for offset but we only keep it if passed to us
runtimeState.runtime.offset = offset;
@@ -696,3 +724,7 @@ export function loadBlock(rundown: OntimeRundown, state = runtimeState) {
// update the block anyway
state.currentBlock.block = newCurrentBlock === null ? null : { ...newCurrentBlock };
}
+
+export function setOffsetMode(mode: OffsetMode) {
+ runtimeState.runtime.offsetMode = mode;
+}
diff --git a/apps/server/src/utils/parser.ts b/apps/server/src/utils/parser.ts
index 11680f5f6..69c027224 100644
--- a/apps/server/src/utils/parser.ts
+++ b/apps/server/src/utils/parser.ts
@@ -27,7 +27,14 @@ import { logger } from '../classes/Logger.js';
import { event as eventDef } from '../models/eventsDefinition.js';
import { makeString } from './parserUtils.js';
-import { parseProject, parseRundown, parseSettings, parseUrlPresets, parseViewSettings } from './parserFunctions.js';
+import {
+ parseOffsetMode,
+ parseProject,
+ parseRundown,
+ parseSettings,
+ parseUrlPresets,
+ parseViewSettings,
+} from './parserFunctions.js';
import { parseExcelDate } from './time.js';
export type ErrorEmitter = (message: string) => void;
@@ -336,6 +343,7 @@ export function parseDatabaseModel(jsonData: Partial): { data: Da
urlPresets: parseUrlPresets(jsonData, makeEmitError('URL Presets')),
customFields,
automation: parseAutomationSettings(jsonData),
+ offsetMode: parseOffsetMode(jsonData, makeEmitError('OffsetMode')),
};
return { data, errors };
diff --git a/apps/server/src/utils/parserFunctions.ts b/apps/server/src/utils/parserFunctions.ts
index 7c8cce9d5..d8093e1a5 100644
--- a/apps/server/src/utils/parserFunctions.ts
+++ b/apps/server/src/utils/parserFunctions.ts
@@ -2,6 +2,7 @@ import {
CustomField,
CustomFields,
DatabaseModel,
+ OffsetMode,
OntimeBlock,
OntimeDelay,
OntimeEvent,
@@ -20,6 +21,7 @@ import { customFieldLabelToKey, generateId, isAlphanumericWithSpace } from 'onti
import { dbModel } from '../models/dataModel.js';
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
import { createEvent, type ErrorEmitter } from './parser.js';
+import { coerceEnum } from './coerceType.js';
/**
* Parse rundown array of an entry
@@ -117,6 +119,25 @@ export function parseProject(data: Partial, emitError?: ErrorEmit
};
}
+/**
+ * Parse offsetmode
+ */
+export function parseOffsetMode(data: Partial, emitError?: ErrorEmitter): OffsetMode {
+ if (!data.offsetMode) {
+ emitError?.('No offsetmode found to import');
+ return dbModel.offsetMode;
+ }
+
+ console.log('Found offsetmode, importing...');
+
+ try {
+ return coerceEnum(data.offsetMode, OffsetMode);
+ } catch (error) {
+ emitError?.('Invalid offsetmode found');
+ return dbModel.offsetMode;
+ }
+}
+
/**
* Parse settings portion of an entry
*/
diff --git a/packages/types/src/definitions/DataModel.type.ts b/packages/types/src/definitions/DataModel.type.ts
index 0f8d31878..1bc936d7e 100644
--- a/packages/types/src/definitions/DataModel.type.ts
+++ b/packages/types/src/definitions/DataModel.type.ts
@@ -1,6 +1,7 @@
import type {
AutomationSettings,
CustomFields,
+ OffsetMode,
OntimeRundown,
ProjectData,
Settings,
@@ -16,4 +17,5 @@ export type DatabaseModel = {
urlPresets: URLPreset[];
customFields: CustomFields;
automation: AutomationSettings;
+ offsetMode: OffsetMode;
};
diff --git a/packages/types/src/definitions/runtime/Runtime.type.ts b/packages/types/src/definitions/runtime/Runtime.type.ts
index 1fc669d70..dc9315317 100644
--- a/packages/types/src/definitions/runtime/Runtime.type.ts
+++ b/packages/types/src/definitions/runtime/Runtime.type.ts
@@ -1,11 +1,18 @@
import type { MaybeNumber } from '../../utils/utils.type.js';
+export enum OffsetMode {
+ Absolute = 'absolute',
+ Relative = 'relative',
+}
+
export type Runtime = {
numEvents: number;
selectedEventIndex: MaybeNumber;
offset: number;
+ relativeOffset: number;
plannedStart: MaybeNumber;
actualStart: MaybeNumber;
plannedEnd: MaybeNumber;
expectedEnd: MaybeNumber;
+ offsetMode: OffsetMode; // TODO: get proper names
};
diff --git a/packages/types/src/definitions/runtime/RuntimeStore.ts b/packages/types/src/definitions/runtime/RuntimeStore.ts
index e9674ace4..1180405c9 100644
--- a/packages/types/src/definitions/runtime/RuntimeStore.ts
+++ b/packages/types/src/definitions/runtime/RuntimeStore.ts
@@ -1,5 +1,6 @@
import { SimpleDirection, SimplePlayback } from './AuxTimer.type.js';
import { Playback } from './Playback.type.js';
+import { OffsetMode } from './Runtime.type.js';
import type { RuntimeStore } from './RuntimeStore.type.js';
import { TimerPhase } from './TimerState.type.js';
@@ -32,10 +33,12 @@ export const runtimeStorePlaceholder: RuntimeStore = {
selectedEventIndex: null, // changes if rundown changes or we load a new event
numEvents: 0, // change initiated by user
offset: 0, // changes at runtime
+ relativeOffset: 0, // changes at runtime
plannedStart: 0, // only changes if event changes
plannedEnd: 0, // only changes if event changes, overflows over dayInMs
actualStart: null, // set once we start the timer
expectedEnd: null, // changes with runtime, based on offset, overflows over dayInMs
+ offsetMode: OffsetMode.Absolute,
},
currentBlock: {
block: null,
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index f7a216a61..1e19a9cf6 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -87,6 +87,7 @@ export { TimerLifeCycle, timerLifecycleValues } from './definitions/core/TimerLi
export type { TimerMessage, MessageState, SecondarySource } from './definitions/runtime/MessageControl.type.js';
export type { Runtime } from './definitions/runtime/Runtime.type.js';
+export { OffsetMode } from './definitions/runtime/Runtime.type.js';
export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
export { runtimeStorePlaceholder } from './definitions/runtime/RuntimeStore.js';
export { type TimerState, TimerPhase } from './definitions/runtime/TimerState.type.js';