mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-10 00:29:41 +00:00
inti relative mode
This commit is contained in:
@@ -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 { useRuntimeStore } from '../stores/runtime';
|
||||||
import { socketSendJson } from '../utils/socket';
|
import { socketSendJson } from '../utils/socket';
|
||||||
@@ -189,6 +189,12 @@ export const useIsOnline = createSelector((state: RuntimeStore) => ({
|
|||||||
isOnline: state.ping > 0,
|
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 = () => {
|
export const usePlayback = () => {
|
||||||
const featureSelector = (state: RuntimeStore) => ({
|
const featureSelector = (state: RuntimeStore) => ({
|
||||||
playback: state.timer.playback,
|
playback: state.timer.playback,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Button, ButtonGroup } from '@chakra-ui/react';
|
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 { AppMode, useAppMode } from '../../../common/stores/appModeStore';
|
||||||
|
|
||||||
import RundownMenu from './RundownMenu';
|
import RundownMenu from './RundownMenu';
|
||||||
@@ -12,6 +14,8 @@ export default function RundownHeader() {
|
|||||||
const setRunMode = () => setAppMode(AppMode.Run);
|
const setRunMode = () => setAppMode(AppMode.Run);
|
||||||
const setEditMode = () => setAppMode(AppMode.Edit);
|
const setEditMode = () => setAppMode(AppMode.Edit);
|
||||||
|
|
||||||
|
const { offsetMode } = useOffsetMode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.header}>
|
<div className={style.header}>
|
||||||
<ButtonGroup isAttached>
|
<ButtonGroup isAttached>
|
||||||
@@ -22,6 +26,22 @@ export default function RundownHeader() {
|
|||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
<ButtonGroup isAttached>
|
||||||
|
<Button
|
||||||
|
size='sm'
|
||||||
|
variant={offsetMode === OffsetMode.Absolute ? 'ontime-filled' : 'ontime-subtle'}
|
||||||
|
onClick={() => setOffsetMode(OffsetMode.Absolute)}
|
||||||
|
>
|
||||||
|
Absolute
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size='sm'
|
||||||
|
variant={offsetMode === OffsetMode.Relative ? 'ontime-filled' : 'ontime-subtle'}
|
||||||
|
onClick={() => setOffsetMode(OffsetMode.Relative)}
|
||||||
|
>
|
||||||
|
Relative
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
<RundownMenu />
|
<RundownMenu />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import * as projectService from '../../services/project-service/ProjectService.j
|
|||||||
export async function patchPartialProjectFile(req: Request, res: Response<DatabaseModel | ErrorResponse>) {
|
export async function patchPartialProjectFile(req: Request, res: Response<DatabaseModel | ErrorResponse>) {
|
||||||
try {
|
try {
|
||||||
const { rundown, project, settings, viewSettings, urlPresets, customFields, automation } = req.body;
|
const { rundown, project, settings, viewSettings, urlPresets, customFields, automation } = req.body;
|
||||||
const patchDb: DatabaseModel = {
|
const patchDb: Partial<DatabaseModel> = {
|
||||||
rundown,
|
rundown,
|
||||||
project,
|
project,
|
||||||
settings,
|
settings,
|
||||||
|
|||||||
@@ -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 { MILLIS_PER_HOUR, MILLIS_PER_SECOND } from 'ontime-utils';
|
||||||
|
|
||||||
import { DeepPartial } from 'ts-essentials';
|
import { DeepPartial } from 'ts-essentials';
|
||||||
@@ -17,6 +17,7 @@ import { throttle } from '../utils/throttle.js';
|
|||||||
import { willCauseRegeneration } from '../services/rundown-service/rundownCacheUtils.js';
|
import { willCauseRegeneration } from '../services/rundown-service/rundownCacheUtils.js';
|
||||||
|
|
||||||
import { handleLegacyMessageConversion } from './integration.legacy.js';
|
import { handleLegacyMessageConversion } from './integration.legacy.js';
|
||||||
|
import { coerceEnum } from '../utils/coerceType.js';
|
||||||
|
|
||||||
const throttledUpdateEvent = throttle(updateEvent, 20);
|
const throttledUpdateEvent = throttle(updateEvent, 20);
|
||||||
let lastRequest: Date | null = null;
|
let lastRequest: Date | null = null;
|
||||||
@@ -286,6 +287,11 @@ const actionHandlers: Record<string, ActionHandler> = {
|
|||||||
|
|
||||||
throw new Error('No matching method provided');
|
throw new Error('No matching method provided');
|
||||||
},
|
},
|
||||||
|
offsetmode: (payload) => {
|
||||||
|
const mode = coerceEnum<OffsetMode>(payload, OffsetMode);
|
||||||
|
runtimeService.setOffsetMode(mode);
|
||||||
|
return { payload: 'success' };
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ export const startServer = async (
|
|||||||
|
|
||||||
socket.init(expressServer, showWelcome, prefix);
|
socket.init(expressServer, showWelcome, prefix);
|
||||||
|
|
||||||
|
const offsetMode = getDataProvider().getOffsetMode();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module initialises the services and provides initial payload for the store
|
* 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
|
// TODO: pass event store to rundownservice
|
||||||
runtimeService.init(maybeRestorePoint);
|
runtimeService.init(maybeRestorePoint);
|
||||||
|
runtimeService.setOffsetMode(offsetMode);
|
||||||
|
|
||||||
const nif = getNetworkInterfaces();
|
const nif = getNetworkInterfaces();
|
||||||
consoleSuccess(`Local: http://localhost:${resultPort}${prefix}/editor`);
|
consoleSuccess(`Local: http://localhost:${resultPort}${prefix}/editor`);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
CustomFields,
|
CustomFields,
|
||||||
URLPreset,
|
URLPreset,
|
||||||
AutomationSettings,
|
AutomationSettings,
|
||||||
|
OffsetMode,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
|
|
||||||
import type { Low } from 'lowdb';
|
import type { Low } from 'lowdb';
|
||||||
@@ -55,6 +56,8 @@ export function getDataProvider() {
|
|||||||
setAutomation,
|
setAutomation,
|
||||||
getRundown,
|
getRundown,
|
||||||
mergeIntoData,
|
mergeIntoData,
|
||||||
|
getOffsetMode,
|
||||||
|
setOffsetMode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +135,17 @@ function getRundown(): Readonly<OntimeRundown> {
|
|||||||
return db.data.rundown;
|
return db.data.rundown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOffsetMode(): Readonly<OffsetMode> {
|
||||||
|
return db.data.offsetMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setOffsetMode(mode): ReadonlyPromise<OffsetMode> {
|
||||||
|
db.data.offsetMode = mode;
|
||||||
|
//TODO: should this maybe not be persisted every time
|
||||||
|
await persist();
|
||||||
|
return db.data.offsetMode;
|
||||||
|
}
|
||||||
|
|
||||||
async function mergeIntoData(newData: Partial<DatabaseModel>): ReadonlyPromise<DatabaseModel> {
|
async function mergeIntoData(newData: Partial<DatabaseModel>): ReadonlyPromise<DatabaseModel> {
|
||||||
const mergedData = safeMerge(db.data, newData);
|
const mergedData = safeMerge(db.data, newData);
|
||||||
db.data.project = mergedData.project;
|
db.data.project = mergedData.project;
|
||||||
|
|||||||
@@ -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';
|
import { safeMerge } from '../DataProvider.utils.js';
|
||||||
|
|
||||||
describe('safeMerge', () => {
|
describe('safeMerge', () => {
|
||||||
@@ -42,6 +42,7 @@ describe('safeMerge', () => {
|
|||||||
triggers: [],
|
triggers: [],
|
||||||
automations: {},
|
automations: {},
|
||||||
},
|
},
|
||||||
|
offsetMode: OffsetMode.Absolute,
|
||||||
} as DatabaseModel;
|
} as DatabaseModel;
|
||||||
|
|
||||||
it('returns existing data if new data is not provided', () => {
|
it('returns existing data if new data is not provided', () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DatabaseModel } from 'ontime-types';
|
import { DatabaseModel, OffsetMode } from 'ontime-types';
|
||||||
import { ONTIME_VERSION } from '../ONTIME_VERSION.js';
|
import { ONTIME_VERSION } from '../ONTIME_VERSION.js';
|
||||||
|
|
||||||
export const dbModel: DatabaseModel = {
|
export const dbModel: DatabaseModel = {
|
||||||
@@ -38,4 +38,5 @@ export const dbModel: DatabaseModel = {
|
|||||||
triggers: [],
|
triggers: [],
|
||||||
automations: {},
|
automations: {},
|
||||||
},
|
},
|
||||||
|
offsetMode: OffsetMode.Absolute,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 = {
|
export const demoDb: DatabaseModel = {
|
||||||
rundown: [
|
rundown: [
|
||||||
@@ -457,4 +457,5 @@ export const demoDb: DatabaseModel = {
|
|||||||
triggers: [],
|
triggers: [],
|
||||||
automations: {},
|
automations: {},
|
||||||
},
|
},
|
||||||
|
offsetMode: OffsetMode.Absolute,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { EndAction, Playback, TimeStrategy, TimerPhase, TimerType } from 'ontime
|
|||||||
import {
|
import {
|
||||||
getCurrent,
|
getCurrent,
|
||||||
getExpectedFinish,
|
getExpectedFinish,
|
||||||
|
getRelativeOffset,
|
||||||
getRuntimeOffset,
|
getRuntimeOffset,
|
||||||
getTimerPhase,
|
getTimerPhase,
|
||||||
normaliseEndTime,
|
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()', () => {
|
describe('getTimerPhase()', () => {
|
||||||
it('should be None if the timer is not running', () => {
|
it('should be None if the timer is not running', () => {
|
||||||
const state = {
|
const state = {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
isPlayableEvent,
|
isPlayableEvent,
|
||||||
LogOrigin,
|
LogOrigin,
|
||||||
MaybeNumber,
|
MaybeNumber,
|
||||||
|
OffsetMode,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
Playback,
|
Playback,
|
||||||
TimerLifeCycle,
|
TimerLifeCycle,
|
||||||
@@ -37,6 +38,7 @@ import {
|
|||||||
import { getForceUpdate, getShouldClockUpdate, getShouldTimerUpdate } from './rundownService.utils.js';
|
import { getForceUpdate, getShouldClockUpdate, getShouldTimerUpdate } from './rundownService.utils.js';
|
||||||
import { skippedOutOfEvent } from '../timerUtils.js';
|
import { skippedOutOfEvent } from '../timerUtils.js';
|
||||||
import { triggerAutomations } from '../../api-data/automation/automation.service.js';
|
import { triggerAutomations } from '../../api-data/automation/automation.service.js';
|
||||||
|
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
||||||
|
|
||||||
type RuntimeStateEventKeys = keyof Pick<RuntimeState, 'eventNext' | 'eventNow' | 'publicEventNow' | 'publicEventNext'>;
|
type RuntimeStateEventKeys = keyof Pick<RuntimeState, 'eventNext' | 'eventNow' | 'publicEventNow' | 'publicEventNext'>;
|
||||||
|
|
||||||
@@ -68,6 +70,14 @@ class RuntimeService {
|
|||||||
RuntimeService.previousState = {} as RuntimeState;
|
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
|
* Checks result of an update and notifies integrations as needed
|
||||||
* This is the only exception of a private method that has broadcast result
|
* This is the only exception of a private method that has broadcast result
|
||||||
|
|||||||
@@ -156,6 +156,16 @@ export function getRuntimeOffset(state: RuntimeState): number {
|
|||||||
return startOffset - addedTime - pausedTime + overtime;
|
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
|
* Calculates the expected end of the rundown
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TimerPhase, Playback } from 'ontime-types';
|
import { TimerPhase, Playback, OffsetMode } from 'ontime-types';
|
||||||
import { deepmerge } from 'ontime-utils';
|
import { deepmerge } from 'ontime-utils';
|
||||||
import type { RuntimeState } from '../runtimeState.js';
|
import type { RuntimeState } from '../runtimeState.js';
|
||||||
|
|
||||||
@@ -16,10 +16,12 @@ const baseState: RuntimeState = {
|
|||||||
selectedEventIndex: null,
|
selectedEventIndex: null,
|
||||||
numEvents: 0,
|
numEvents: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
relativeOffset: 0,
|
||||||
plannedStart: 0,
|
plannedStart: 0,
|
||||||
plannedEnd: 0,
|
plannedEnd: 0,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
expectedEnd: null,
|
expectedEnd: null,
|
||||||
|
offsetMode: OffsetMode.Absolute,
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { deepmerge } from 'ontime-utils';
|
|||||||
import {
|
import {
|
||||||
type RuntimeState,
|
type RuntimeState,
|
||||||
addTime,
|
addTime,
|
||||||
clear,
|
hardClear,
|
||||||
getState,
|
getState,
|
||||||
load,
|
load,
|
||||||
loadBlock,
|
loadBlock,
|
||||||
@@ -71,7 +71,7 @@ beforeAll(() => {
|
|||||||
|
|
||||||
describe('mutation on runtimeState', () => {
|
describe('mutation on runtimeState', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
clear();
|
hardClear();
|
||||||
|
|
||||||
vi.mock('../../services/rundown-service/RundownService.js', async (importOriginal) => {
|
vi.mock('../../services/rundown-service/RundownService.js', async (importOriginal) => {
|
||||||
const actual = (await importOriginal()) as object;
|
const actual = (await importOriginal()) as object;
|
||||||
@@ -246,7 +246,7 @@ describe('roll mode', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
vi.setSystemTime('jan 1 00:00');
|
vi.setSystemTime('jan 1 00:00');
|
||||||
clear();
|
hardClear();
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
isPlayableEvent,
|
isPlayableEvent,
|
||||||
MaybeNumber,
|
MaybeNumber,
|
||||||
MaybeString,
|
MaybeString,
|
||||||
|
OffsetMode,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
OntimeRundown,
|
OntimeRundown,
|
||||||
PlayableEvent,
|
PlayableEvent,
|
||||||
@@ -27,6 +28,7 @@ import {
|
|||||||
getCurrent,
|
getCurrent,
|
||||||
getExpectedEnd,
|
getExpectedEnd,
|
||||||
getExpectedFinish,
|
getExpectedFinish,
|
||||||
|
getRelativeOffset,
|
||||||
getRuntimeOffset,
|
getRuntimeOffset,
|
||||||
getTimerPhase,
|
getTimerPhase,
|
||||||
} from '../services/timerUtils.js';
|
} from '../services/timerUtils.js';
|
||||||
@@ -82,7 +84,32 @@ export function getState(): Readonly<RuntimeState> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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.eventNow = null;
|
||||||
runtimeState.publicEventNow = null;
|
runtimeState.publicEventNow = null;
|
||||||
runtimeState.eventNext = null;
|
runtimeState.eventNext = null;
|
||||||
@@ -93,6 +120,7 @@ export function clear() {
|
|||||||
runtimeState.publicEventNext = null;
|
runtimeState.publicEventNext = null;
|
||||||
|
|
||||||
runtimeState.runtime.offset = 0;
|
runtimeState.runtime.offset = 0;
|
||||||
|
runtimeState.runtime.relativeOffset = 0;
|
||||||
runtimeState.runtime.actualStart = null;
|
runtimeState.runtime.actualStart = null;
|
||||||
runtimeState.runtime.expectedEnd = null;
|
runtimeState.runtime.expectedEnd = null;
|
||||||
runtimeState.runtime.selectedEventIndex = null;
|
runtimeState.runtime.selectedEventIndex = null;
|
||||||
@@ -155,9 +183,9 @@ export function load(
|
|||||||
initialData?: Partial<TimerState & RestorePoint>,
|
initialData?: Partial<TimerState & RestorePoint>,
|
||||||
): boolean {
|
): boolean {
|
||||||
// we need to persist the current block state across loads
|
// we need to persist the current block state across loads
|
||||||
const prevCurrentBlock = { ...runtimeState.currentBlock };
|
// const prevCurrentBlock = { ...runtimeState.currentBlock };
|
||||||
clear();
|
softClear();
|
||||||
runtimeState.currentBlock = prevCurrentBlock;
|
// runtimeState.currentBlock = prevCurrentBlock;
|
||||||
|
|
||||||
// filter rundown
|
// filter rundown
|
||||||
const timedEvents = filterTimedEvents(rundown);
|
const timedEvents = filterTimedEvents(rundown);
|
||||||
@@ -185,6 +213,7 @@ export function load(
|
|||||||
if (firstStart === null || typeof firstStart === 'number') {
|
if (firstStart === null || typeof firstStart === 'number') {
|
||||||
runtimeState.runtime.actualStart = firstStart;
|
runtimeState.runtime.actualStart = firstStart;
|
||||||
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
||||||
|
runtimeState.runtime.relativeOffset = getRelativeOffset(runtimeState);
|
||||||
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
||||||
}
|
}
|
||||||
if (typeof initialData.blockStartAt === 'number') {
|
if (typeof initialData.blockStartAt === 'number') {
|
||||||
@@ -390,6 +419,7 @@ export function start(state: RuntimeState = runtimeState): boolean {
|
|||||||
|
|
||||||
// update offset
|
// update offset
|
||||||
state.runtime.offset = getRuntimeOffset(state);
|
state.runtime.offset = getRuntimeOffset(state);
|
||||||
|
state.runtime.relativeOffset = getRelativeOffset(state);
|
||||||
state.runtime.expectedEnd = state.runtime.plannedEnd - state.runtime.offset;
|
state.runtime.expectedEnd = state.runtime.plannedEnd - state.runtime.offset;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -409,9 +439,7 @@ export function stop(state: RuntimeState = runtimeState): boolean {
|
|||||||
if (state.timer.playback === Playback.Stop) {
|
if (state.timer.playback === Playback.Stop) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
clear();
|
hardClear();
|
||||||
runtimeState.runtime.actualStart = null;
|
|
||||||
runtimeState.runtime.expectedEnd = null;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +481,7 @@ export function addTime(amount: number) {
|
|||||||
|
|
||||||
// update runtime delays: over - under
|
// update runtime delays: over - under
|
||||||
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
||||||
|
runtimeState.runtime.relativeOffset = getRelativeOffset(runtimeState);
|
||||||
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -498,6 +527,7 @@ export function update(): UpdateResult {
|
|||||||
|
|
||||||
// update runtime, needs up-to-date timer state
|
// update runtime, needs up-to-date timer state
|
||||||
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
||||||
|
runtimeState.runtime.relativeOffset = getRelativeOffset(runtimeState);
|
||||||
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
||||||
|
|
||||||
const finishedNow =
|
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
|
// we need to persist the current block state across loads
|
||||||
const prevCurrentBlock = { ...runtimeState.currentBlock };
|
softClear();
|
||||||
clear();
|
|
||||||
runtimeState.currentBlock = prevCurrentBlock;
|
|
||||||
|
|
||||||
//account for offset but we only keep it if passed to us
|
//account for offset but we only keep it if passed to us
|
||||||
runtimeState.runtime.offset = offset;
|
runtimeState.runtime.offset = offset;
|
||||||
@@ -696,3 +724,7 @@ export function loadBlock(rundown: OntimeRundown, state = runtimeState) {
|
|||||||
// update the block anyway
|
// update the block anyway
|
||||||
state.currentBlock.block = newCurrentBlock === null ? null : { ...newCurrentBlock };
|
state.currentBlock.block = newCurrentBlock === null ? null : { ...newCurrentBlock };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setOffsetMode(mode: OffsetMode) {
|
||||||
|
runtimeState.runtime.offsetMode = mode;
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,7 +27,14 @@ import { logger } from '../classes/Logger.js';
|
|||||||
import { event as eventDef } from '../models/eventsDefinition.js';
|
import { event as eventDef } from '../models/eventsDefinition.js';
|
||||||
|
|
||||||
import { makeString } from './parserUtils.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';
|
import { parseExcelDate } from './time.js';
|
||||||
|
|
||||||
export type ErrorEmitter = (message: string) => void;
|
export type ErrorEmitter = (message: string) => void;
|
||||||
@@ -336,6 +343,7 @@ export function parseDatabaseModel(jsonData: Partial<DatabaseModel>): { data: Da
|
|||||||
urlPresets: parseUrlPresets(jsonData, makeEmitError('URL Presets')),
|
urlPresets: parseUrlPresets(jsonData, makeEmitError('URL Presets')),
|
||||||
customFields,
|
customFields,
|
||||||
automation: parseAutomationSettings(jsonData),
|
automation: parseAutomationSettings(jsonData),
|
||||||
|
offsetMode: parseOffsetMode(jsonData, makeEmitError('OffsetMode')),
|
||||||
};
|
};
|
||||||
|
|
||||||
return { data, errors };
|
return { data, errors };
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
CustomField,
|
CustomField,
|
||||||
CustomFields,
|
CustomFields,
|
||||||
DatabaseModel,
|
DatabaseModel,
|
||||||
|
OffsetMode,
|
||||||
OntimeBlock,
|
OntimeBlock,
|
||||||
OntimeDelay,
|
OntimeDelay,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
@@ -20,6 +21,7 @@ import { customFieldLabelToKey, generateId, isAlphanumericWithSpace } from 'onti
|
|||||||
import { dbModel } from '../models/dataModel.js';
|
import { dbModel } from '../models/dataModel.js';
|
||||||
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
||||||
import { createEvent, type ErrorEmitter } from './parser.js';
|
import { createEvent, type ErrorEmitter } from './parser.js';
|
||||||
|
import { coerceEnum } from './coerceType.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse rundown array of an entry
|
* Parse rundown array of an entry
|
||||||
@@ -117,6 +119,25 @@ export function parseProject(data: Partial<DatabaseModel>, emitError?: ErrorEmit
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse offsetmode
|
||||||
|
*/
|
||||||
|
export function parseOffsetMode(data: Partial<DatabaseModel>, emitError?: ErrorEmitter): OffsetMode {
|
||||||
|
if (!data.offsetMode) {
|
||||||
|
emitError?.('No offsetmode found to import');
|
||||||
|
return dbModel.offsetMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Found offsetmode, importing...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
return coerceEnum<OffsetMode>(data.offsetMode, OffsetMode);
|
||||||
|
} catch (error) {
|
||||||
|
emitError?.('Invalid offsetmode found');
|
||||||
|
return dbModel.offsetMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse settings portion of an entry
|
* Parse settings portion of an entry
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
AutomationSettings,
|
AutomationSettings,
|
||||||
CustomFields,
|
CustomFields,
|
||||||
|
OffsetMode,
|
||||||
OntimeRundown,
|
OntimeRundown,
|
||||||
ProjectData,
|
ProjectData,
|
||||||
Settings,
|
Settings,
|
||||||
@@ -16,4 +17,5 @@ export type DatabaseModel = {
|
|||||||
urlPresets: URLPreset[];
|
urlPresets: URLPreset[];
|
||||||
customFields: CustomFields;
|
customFields: CustomFields;
|
||||||
automation: AutomationSettings;
|
automation: AutomationSettings;
|
||||||
|
offsetMode: OffsetMode;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import type { MaybeNumber } from '../../utils/utils.type.js';
|
import type { MaybeNumber } from '../../utils/utils.type.js';
|
||||||
|
|
||||||
|
export enum OffsetMode {
|
||||||
|
Absolute = 'absolute',
|
||||||
|
Relative = 'relative',
|
||||||
|
}
|
||||||
|
|
||||||
export type Runtime = {
|
export type Runtime = {
|
||||||
numEvents: number;
|
numEvents: number;
|
||||||
selectedEventIndex: MaybeNumber;
|
selectedEventIndex: MaybeNumber;
|
||||||
offset: number;
|
offset: number;
|
||||||
|
relativeOffset: number;
|
||||||
plannedStart: MaybeNumber;
|
plannedStart: MaybeNumber;
|
||||||
actualStart: MaybeNumber;
|
actualStart: MaybeNumber;
|
||||||
plannedEnd: MaybeNumber;
|
plannedEnd: MaybeNumber;
|
||||||
expectedEnd: MaybeNumber;
|
expectedEnd: MaybeNumber;
|
||||||
|
offsetMode: OffsetMode; // TODO: get proper names
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { SimpleDirection, SimplePlayback } from './AuxTimer.type.js';
|
import { SimpleDirection, SimplePlayback } from './AuxTimer.type.js';
|
||||||
import { Playback } from './Playback.type.js';
|
import { Playback } from './Playback.type.js';
|
||||||
|
import { OffsetMode } from './Runtime.type.js';
|
||||||
import type { RuntimeStore } from './RuntimeStore.type.js';
|
import type { RuntimeStore } from './RuntimeStore.type.js';
|
||||||
import { TimerPhase } from './TimerState.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
|
selectedEventIndex: null, // changes if rundown changes or we load a new event
|
||||||
numEvents: 0, // change initiated by user
|
numEvents: 0, // change initiated by user
|
||||||
offset: 0, // changes at runtime
|
offset: 0, // changes at runtime
|
||||||
|
relativeOffset: 0, // changes at runtime
|
||||||
plannedStart: 0, // only changes if event changes
|
plannedStart: 0, // only changes if event changes
|
||||||
plannedEnd: 0, // only changes if event changes, overflows over dayInMs
|
plannedEnd: 0, // only changes if event changes, overflows over dayInMs
|
||||||
actualStart: null, // set once we start the timer
|
actualStart: null, // set once we start the timer
|
||||||
expectedEnd: null, // changes with runtime, based on offset, overflows over dayInMs
|
expectedEnd: null, // changes with runtime, based on offset, overflows over dayInMs
|
||||||
|
offsetMode: OffsetMode.Absolute,
|
||||||
},
|
},
|
||||||
currentBlock: {
|
currentBlock: {
|
||||||
block: null,
|
block: null,
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export { TimerLifeCycle, timerLifecycleValues } from './definitions/core/TimerLi
|
|||||||
export type { TimerMessage, MessageState, SecondarySource } from './definitions/runtime/MessageControl.type.js';
|
export type { TimerMessage, MessageState, SecondarySource } from './definitions/runtime/MessageControl.type.js';
|
||||||
|
|
||||||
export type { Runtime } from './definitions/runtime/Runtime.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 type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
|
||||||
export { runtimeStorePlaceholder } from './definitions/runtime/RuntimeStore.js';
|
export { runtimeStorePlaceholder } from './definitions/runtime/RuntimeStore.js';
|
||||||
export { type TimerState, TimerPhase } from './definitions/runtime/TimerState.type.js';
|
export { type TimerState, TimerPhase } from './definitions/runtime/TimerState.type.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user