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';