mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-24 00:19:21 +00:00
refactor: address review feedback on aux timer naming
- Revert the automation action labels back to "Aux N: action" (no functional reason to change this wording). - Simplify the aux timers header in the playback control: the flex container now only handles layout, and the label text reuses the same font-size/color as the per-timer labels below it, instead of a bespoke style block duplicating those values. - Replace the generic, length-driven normaliser with an explicit sanitiseAuxTimerNames() that always deals with exactly three timers, and rename it away from "normalise" (which didn't convey that it trims, caps length and fills in missing entries). Static defaults now use a plain ['', '', ''] literal instead of calling the sanitiser with no input to sanitise. - Settings.type.ts and AuxTimerSettings.tsx no longer generate their three fields from a loop; the form mirrors the same explicit, one-field-per-row style already used by GeneralSettings.tsx. - Trim comments that only restated what the following line already says, keeping the ones that explain non-obvious behaviour (why AuxTimerService needs to be resynced separately from the data provider, why SimpleTimer.reset() preserves the name). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WCZejVTzuAY3tHTE6nB3JH
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import { Settings } from 'ontime-types';
|
import { Settings } from 'ontime-types';
|
||||||
import { normaliseAuxTimerNames } from 'ontime-utils';
|
|
||||||
|
|
||||||
export const ontimePlaceholderSettings: Settings = {
|
export const ontimePlaceholderSettings: Settings = {
|
||||||
version: '4.0.0',
|
version: '4.0.0',
|
||||||
@@ -7,5 +6,5 @@ export const ontimePlaceholderSettings: Settings = {
|
|||||||
operatorKey: null,
|
operatorKey: null,
|
||||||
timeFormat: '24',
|
timeFormat: '24',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
auxTimerNames: normaliseAuxTimerNames(),
|
auxTimerNames: ['', '', ''],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/**
|
|
||||||
* Resolves the display label for an aux timer.
|
|
||||||
* Falls back to the provided default when no custom name is set.
|
|
||||||
* @param name - the aux timer's custom name (from the runtime store)
|
|
||||||
* @param fallback - label to use when no custom name is set
|
|
||||||
*/
|
|
||||||
export function getAuxTimerLabel(name: string | undefined, fallback: string): string {
|
export function getAuxTimerLabel(name: string | undefined, fallback: string): string {
|
||||||
const custom = name?.trim();
|
const custom = name?.trim();
|
||||||
return custom ? custom : fallback;
|
return custom ? custom : fallback;
|
||||||
|
|||||||
+12
-12
@@ -51,21 +51,21 @@ export default function OntimeActionForm({
|
|||||||
}}
|
}}
|
||||||
value={watch(`outputs.${index}.action`)}
|
value={watch(`outputs.${index}.action`)}
|
||||||
options={[
|
options={[
|
||||||
{ value: 'aux1-pause', label: 'Aux timer 1: pause' },
|
{ value: 'aux1-pause', label: 'Aux 1: pause' },
|
||||||
{ value: 'aux2-pause', label: 'Aux timer 2: pause' },
|
{ value: 'aux2-pause', label: 'Aux 2: pause' },
|
||||||
{ value: 'aux3-pause', label: 'Aux timer 3: pause' },
|
{ value: 'aux3-pause', label: 'Aux 3: pause' },
|
||||||
|
|
||||||
{ value: 'aux1-start', label: 'Aux timer 1: start' },
|
{ value: 'aux1-start', label: 'Aux 1: start' },
|
||||||
{ value: 'aux2-start', label: 'Aux timer 2: start' },
|
{ value: 'aux2-start', label: 'Aux 2: start' },
|
||||||
{ value: 'aux3-start', label: 'Aux timer 3: start' },
|
{ value: 'aux3-start', label: 'Aux 3: start' },
|
||||||
|
|
||||||
{ value: 'aux1-stop', label: 'Aux timer 1: stop' },
|
{ value: 'aux1-stop', label: 'Aux 1: stop' },
|
||||||
{ value: 'aux2-stop', label: 'Aux timer 2: stop' },
|
{ value: 'aux2-stop', label: 'Aux 2: stop' },
|
||||||
{ value: 'aux3-stop', label: 'Aux timer 3: stop' },
|
{ value: 'aux3-stop', label: 'Aux 3: stop' },
|
||||||
|
|
||||||
{ value: 'aux1-set', label: 'Aux timer 1: set' },
|
{ value: 'aux1-set', label: 'Aux 1: set' },
|
||||||
{ value: 'aux2-set', label: 'Aux timer 2: set' },
|
{ value: 'aux2-set', label: 'Aux 2: set' },
|
||||||
{ value: 'aux3-set', label: 'Aux timer 3: set' },
|
{ value: 'aux3-set', label: 'Aux 3: set' },
|
||||||
|
|
||||||
{ value: 'playback-start', label: 'Playback: start' },
|
{ value: 'playback-start', label: 'Playback: start' },
|
||||||
{ value: 'playback-stop', label: 'Playback: stop' },
|
{ value: 'playback-stop', label: 'Playback: stop' },
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Settings } from 'ontime-types';
|
import { Settings } from 'ontime-types';
|
||||||
import { auxTimerNameMaxLength, numberOfAuxTimers } from 'ontime-utils';
|
import { auxTimerNameMaxLength } from 'ontime-utils';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
@@ -12,9 +12,6 @@ import useSettings from '../../../../common/hooks-query/useSettings';
|
|||||||
import { preventEscape } from '../../../../common/utils/keyEvent';
|
import { preventEscape } from '../../../../common/utils/keyEvent';
|
||||||
import * as Panel from '../../panel-utils/PanelUtils';
|
import * as Panel from '../../panel-utils/PanelUtils';
|
||||||
|
|
||||||
/** zero based index of each aux timer, used to address the auxTimerNames array */
|
|
||||||
const auxTimerIndexes = Array.from({ length: numberOfAuxTimers }, (_, index) => index);
|
|
||||||
|
|
||||||
export default function AuxTimerSettings() {
|
export default function AuxTimerSettings() {
|
||||||
const { data, status, refetch } = useSettings();
|
const { data, status, refetch } = useSettings();
|
||||||
const {
|
const {
|
||||||
@@ -30,7 +27,6 @@ export default function AuxTimerSettings() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// update form if we get new data from server
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
reset(data);
|
reset(data);
|
||||||
@@ -79,16 +75,18 @@ export default function AuxTimerSettings() {
|
|||||||
<Panel.Loader isLoading={isLoading} />
|
<Panel.Loader isLoading={isLoading} />
|
||||||
<Panel.Error>{errors.root?.message}</Panel.Error>
|
<Panel.Error>{errors.root?.message}</Panel.Error>
|
||||||
<Panel.ListGroup>
|
<Panel.ListGroup>
|
||||||
{auxTimerIndexes.map((index) => (
|
<Panel.ListItem>
|
||||||
<Panel.ListItem key={index}>
|
<Panel.Field title='Aux timer 1' description='Custom name for aux timer 1' />
|
||||||
<Panel.Field title={`Aux timer ${index + 1}`} description={`Custom name for aux timer ${index + 1}`} />
|
<Input maxLength={auxTimerNameMaxLength} placeholder='Aux 1' {...register('auxTimerNames.0')} />
|
||||||
<Input
|
</Panel.ListItem>
|
||||||
maxLength={auxTimerNameMaxLength}
|
<Panel.ListItem>
|
||||||
placeholder={`Aux ${index + 1}`}
|
<Panel.Field title='Aux timer 2' description='Custom name for aux timer 2' />
|
||||||
{...register(`auxTimerNames.${index}`)}
|
<Input maxLength={auxTimerNameMaxLength} placeholder='Aux 2' {...register('auxTimerNames.1')} />
|
||||||
/>
|
</Panel.ListItem>
|
||||||
</Panel.ListItem>
|
<Panel.ListItem>
|
||||||
))}
|
<Panel.Field title='Aux timer 3' description='Custom name for aux timer 3' />
|
||||||
|
<Input maxLength={auxTimerNameMaxLength} placeholder='Aux 3' {...register('auxTimerNames.2')} />
|
||||||
|
</Panel.ListItem>
|
||||||
</Panel.ListGroup>
|
</Panel.ListGroup>
|
||||||
</Panel.Section>
|
</Panel.Section>
|
||||||
</Panel.Card>
|
</Panel.Card>
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
font-size: $inner-section-text-size;
|
font-size: $inner-section-text-size;
|
||||||
color: $label-gray;
|
color: $label-gray;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function PlaybackControl() {
|
|||||||
timerPhase={data.timerPhase}
|
timerPhase={data.timerPhase}
|
||||||
/>
|
/>
|
||||||
<div className={style.auxHeader}>
|
<div className={style.auxHeader}>
|
||||||
<span>Aux timers</span>
|
<span className={style.label}>Aux timers</span>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
text='Name aux timers'
|
text='Name aux timers'
|
||||||
render={
|
render={
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
customFieldLabelToKey,
|
customFieldLabelToKey,
|
||||||
eventDef as eventModel,
|
eventDef as eventModel,
|
||||||
isKnownTimerType,
|
isKnownTimerType,
|
||||||
normaliseAuxTimerNames,
|
|
||||||
validateEndAction,
|
validateEndAction,
|
||||||
} from 'ontime-utils';
|
} from 'ontime-utils';
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ export function migrateSettings(jsonData: object): (Settings & { serverPort: num
|
|||||||
operatorKey,
|
operatorKey,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
language,
|
language,
|
||||||
auxTimerNames: normaliseAuxTimerNames(),
|
auxTimerNames: ['', '', ''],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DatabaseModel, Settings } from 'ontime-types';
|
import { DatabaseModel, Settings } from 'ontime-types';
|
||||||
import { normaliseAuxTimerNames } from 'ontime-utils';
|
import { sanitiseAuxTimerNames } from 'ontime-utils';
|
||||||
|
|
||||||
import { is } from '../../../utils/is.js';
|
import { is } from '../../../utils/is.js';
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ export function migrateServerPort(jsonData: Partial<DatabaseModel>): {
|
|||||||
const operatorKey = settings?.operatorKey;
|
const operatorKey = settings?.operatorKey;
|
||||||
const timeFormat = settings?.timeFormat;
|
const timeFormat = settings?.timeFormat;
|
||||||
const language = settings?.language;
|
const language = settings?.language;
|
||||||
const auxTimerNames = normaliseAuxTimerNames(settings?.auxTimerNames);
|
const auxTimerNames = sanitiseAuxTimerNames(settings?.auxTimerNames);
|
||||||
const version = '4.5.0';
|
const version = '4.5.0';
|
||||||
db.settings = {
|
db.settings = {
|
||||||
version,
|
version,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ describe('parseSettings()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('carries custom aux timer names through and normalises to a length-3 array', () => {
|
it('carries custom aux timer names through and pads to a length-3 array', () => {
|
||||||
const result = parseSettings({
|
const result = parseSettings({
|
||||||
settings: { version: '1', auxTimerNames: ['Speaker'] } as unknown as Settings,
|
settings: { version: '1', auxTimerNames: ['Speaker'] } as unknown as Settings,
|
||||||
});
|
});
|
||||||
@@ -35,7 +35,6 @@ describe('parseSettings()', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('creates the aux timer names for project files made before the feature existed', () => {
|
it('creates the aux timer names for project files made before the feature existed', () => {
|
||||||
// a settings object as found in a project file which predates aux timer naming
|
|
||||||
const oldSettings = {
|
const oldSettings = {
|
||||||
version: '4.5.0',
|
version: '4.5.0',
|
||||||
editorKey: null,
|
editorKey: null,
|
||||||
@@ -47,7 +46,6 @@ describe('parseSettings()', () => {
|
|||||||
const result = parseSettings({ settings: oldSettings as Settings });
|
const result = parseSettings({ settings: oldSettings as Settings });
|
||||||
|
|
||||||
expect(result.auxTimerNames).toStrictEqual(['', '', '']);
|
expect(result.auxTimerNames).toStrictEqual(['', '', '']);
|
||||||
// the rest of the settings are untouched
|
|
||||||
expect(result).toMatchObject({ timeFormat: '24', language: 'en' });
|
expect(result).toMatchObject({ timeFormat: '24', language: 'en' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DatabaseModel, Settings } from 'ontime-types';
|
import { DatabaseModel, Settings } from 'ontime-types';
|
||||||
import { normaliseAuxTimerNames } from 'ontime-utils';
|
import { sanitiseAuxTimerNames } from 'ontime-utils';
|
||||||
|
|
||||||
import { getPartialProject } from '../../models/dataModel.js';
|
import { getPartialProject } from '../../models/dataModel.js';
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export function parseSettings(data: Partial<DatabaseModel>): Settings {
|
|||||||
operatorKey: data.settings.operatorKey ?? defaultSettings.operatorKey,
|
operatorKey: data.settings.operatorKey ?? defaultSettings.operatorKey,
|
||||||
timeFormat: data.settings.timeFormat ?? defaultSettings.timeFormat,
|
timeFormat: data.settings.timeFormat ?? defaultSettings.timeFormat,
|
||||||
language: data.settings.language ?? defaultSettings.language,
|
language: data.settings.language ?? defaultSettings.language,
|
||||||
// property added in v4.6.0, older project files will not contain it
|
// older project files predate this property
|
||||||
auxTimerNames: normaliseAuxTimerNames(data.settings.auxTimerNames),
|
auxTimerNames: sanitiseAuxTimerNames(data.settings.auxTimerNames),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { normaliseAuxTimerNames } from 'ontime-utils';
|
import { sanitiseAuxTimerNames } from 'ontime-utils';
|
||||||
|
|
||||||
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
|
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
|
||||||
|
|
||||||
@@ -28,11 +28,7 @@ export const validateSettings = [
|
|||||||
pinValidator('operatorKey'),
|
pinValidator('operatorKey'),
|
||||||
body('timeFormat').isString().isIn(['12', '24']).withMessage('Time format can only be "12" or "24"'),
|
body('timeFormat').isString().isIn(['12', '24']).withMessage('Time format can only be "12" or "24"'),
|
||||||
body('language').isString().trim().notEmpty(),
|
body('language').isString().trim().notEmpty(),
|
||||||
body('auxTimerNames')
|
body('auxTimerNames').isArray().withMessage('auxTimerNames must be an array').customSanitizer(sanitiseAuxTimerNames),
|
||||||
.isArray()
|
|
||||||
.withMessage('auxTimerNames must be an array')
|
|
||||||
// normalise to a fixed length array of trimmed, length capped strings
|
|
||||||
.customSanitizer(normaliseAuxTimerNames),
|
|
||||||
|
|
||||||
requestValidationFunction,
|
requestValidationFunction,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import cookieParser from 'cookie-parser';
|
|||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { LogOrigin, SimpleDirection, SimplePlayback, runtimeStorePlaceholder } from 'ontime-types';
|
import { LogOrigin, SimpleDirection, SimplePlayback, runtimeStorePlaceholder } from 'ontime-types';
|
||||||
import { normaliseAuxTimerNames } from 'ontime-utils';
|
import { sanitiseAuxTimerNames } from 'ontime-utils';
|
||||||
import serverTiming from 'server-timing';
|
import serverTiming from 'server-timing';
|
||||||
|
|
||||||
import { oscServer } from './adapters/OscAdapter.js';
|
import { oscServer } from './adapters/OscAdapter.js';
|
||||||
@@ -206,7 +206,7 @@ export const startServer = async (): Promise<{ message: string; serverPort: numb
|
|||||||
* Module initialises the services and provides initial payload for the store
|
* Module initialises the services and provides initial payload for the store
|
||||||
*/
|
*/
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const auxTimerNames = normaliseAuxTimerNames(getDataProvider().getSettings().auxTimerNames);
|
const [auxName1, auxName2, auxName3] = sanitiseAuxTimerNames(getDataProvider().getSettings().auxTimerNames);
|
||||||
eventStore.init({
|
eventStore.init({
|
||||||
clock: state.clock,
|
clock: state.clock,
|
||||||
timer: state.timer,
|
timer: state.timer,
|
||||||
@@ -222,27 +222,27 @@ export const startServer = async (): Promise<{ message: string; serverPort: numb
|
|||||||
current: timerConfig.auxTimerDefault,
|
current: timerConfig.auxTimerDefault,
|
||||||
playback: SimplePlayback.Stop,
|
playback: SimplePlayback.Stop,
|
||||||
direction: SimpleDirection.CountDown,
|
direction: SimpleDirection.CountDown,
|
||||||
name: auxTimerNames[0] ?? '',
|
name: auxName1,
|
||||||
},
|
},
|
||||||
auxtimer2: {
|
auxtimer2: {
|
||||||
duration: timerConfig.auxTimerDefault,
|
duration: timerConfig.auxTimerDefault,
|
||||||
current: timerConfig.auxTimerDefault,
|
current: timerConfig.auxTimerDefault,
|
||||||
playback: SimplePlayback.Stop,
|
playback: SimplePlayback.Stop,
|
||||||
direction: SimpleDirection.CountDown,
|
direction: SimpleDirection.CountDown,
|
||||||
name: auxTimerNames[1] ?? '',
|
name: auxName2,
|
||||||
},
|
},
|
||||||
auxtimer3: {
|
auxtimer3: {
|
||||||
duration: timerConfig.auxTimerDefault,
|
duration: timerConfig.auxTimerDefault,
|
||||||
current: timerConfig.auxTimerDefault,
|
current: timerConfig.auxTimerDefault,
|
||||||
playback: SimplePlayback.Stop,
|
playback: SimplePlayback.Stop,
|
||||||
direction: SimpleDirection.CountDown,
|
direction: SimpleDirection.CountDown,
|
||||||
name: auxTimerNames[2] ?? '',
|
name: auxName3,
|
||||||
},
|
},
|
||||||
ping: 1,
|
ping: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
// seed the aux timer names onto the running service so they persist across commands
|
// AuxTimerService owns its own SimpleTimer instances, so the store above doesn't update them
|
||||||
auxTimerService.loadNames(auxTimerNames);
|
auxTimerService.loadNames(getDataProvider().getSettings().auxTimerNames);
|
||||||
|
|
||||||
// initialise message service
|
// initialise message service
|
||||||
messageService.init(eventStore.set, eventStore.get);
|
messageService.init(eventStore.set, eventStore.get);
|
||||||
|
|||||||
@@ -29,9 +29,6 @@ export class SimpleTimer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the custom name of the timer
|
|
||||||
*/
|
|
||||||
public setName(name: string): SimpleTimerState {
|
public setName(name: string): SimpleTimerState {
|
||||||
this.state.name = name;
|
this.state.name = name;
|
||||||
return this.state;
|
return this.state;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DatabaseModel, Rundown } from 'ontime-types';
|
import { DatabaseModel, Rundown } from 'ontime-types';
|
||||||
import { generateId, normaliseAuxTimerNames } from 'ontime-utils';
|
import { generateId } from 'ontime-utils';
|
||||||
|
|
||||||
import { ONTIME_VERSION } from '../ONTIME_VERSION.js';
|
import { ONTIME_VERSION } from '../ONTIME_VERSION.js';
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ const dbModel: DatabaseModel = {
|
|||||||
operatorKey: null,
|
operatorKey: null,
|
||||||
timeFormat: '24',
|
timeFormat: '24',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
auxTimerNames: normaliseAuxTimerNames(),
|
auxTimerNames: ['', '', ''],
|
||||||
},
|
},
|
||||||
viewSettings: {
|
viewSettings: {
|
||||||
overrideStyles: false,
|
overrideStyles: false,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { DatabaseModel, OntimeView } from 'ontime-types';
|
import { DatabaseModel, OntimeView } from 'ontime-types';
|
||||||
import { normaliseAuxTimerNames } from 'ontime-utils';
|
|
||||||
|
|
||||||
import { backstageRundown, broadcastRundown, stageRundown } from './demoRundowns.js';
|
import { backstageRundown, broadcastRundown, stageRundown } from './demoRundowns.js';
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
operatorKey: null,
|
operatorKey: null,
|
||||||
timeFormat: '24',
|
timeFormat: '24',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
auxTimerNames: normaliseAuxTimerNames(),
|
auxTimerNames: ['', '', ''],
|
||||||
},
|
},
|
||||||
viewSettings: {
|
viewSettings: {
|
||||||
dangerColor: '#ff7300',
|
dangerColor: '#ff7300',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { RuntimeStore, SimpleDirection, SimplePlayback } from 'ontime-types';
|
import { RuntimeStore, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||||
import { normaliseAuxTimerNames } from 'ontime-utils';
|
import { sanitiseAuxTimerNames } from 'ontime-utils';
|
||||||
|
|
||||||
import { SimpleTimer } from '../../classes/simple-timer/SimpleTimer.js';
|
import { SimpleTimer } from '../../classes/simple-timer/SimpleTimer.js';
|
||||||
import { timerConfig } from '../../setup/config.js';
|
import { timerConfig } from '../../setup/config.js';
|
||||||
@@ -26,13 +26,11 @@ export class AuxTimerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies custom names to the aux timers and broadcasts the change.
|
* Called at bootstrap and whenever the loaded project's settings change,
|
||||||
* Names are given in aux timer order (index 0 is aux timer 1).
|
* so the running timers reflect the current project's aux timer names.
|
||||||
* Used to seed the names at bootstrap and to keep them in sync
|
|
||||||
* with the settings of the loaded project.
|
|
||||||
*/
|
*/
|
||||||
loadNames(names?: string[]) {
|
loadNames(names?: string[]) {
|
||||||
const [name1, name2, name3] = normaliseAuxTimerNames(names);
|
const [name1, name2, name3] = sanitiseAuxTimerNames(names);
|
||||||
const patch: AuxTimerStateUpdate = {
|
const patch: AuxTimerStateUpdate = {
|
||||||
auxtimer1: this.aux1.setName(name1),
|
auxtimer1: this.aux1.setName(name1),
|
||||||
auxtimer2: this.aux2.setName(name2),
|
auxtimer2: this.aux2.setName(name2),
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ async function loadProject(projectData: DatabaseModel, fileName: string, rundown
|
|||||||
// stop the runtime service
|
// stop the runtime service
|
||||||
runtimeService.stop();
|
runtimeService.stop();
|
||||||
|
|
||||||
// the aux timer names belong to the project, apply the ones from the newly loaded project
|
// AuxTimerService holds its own state, independent of the loaded project, so it needs to be updated explicitly
|
||||||
auxTimerService.loadNames(projectData.settings.auxTimerNames);
|
auxTimerService.loadNames(projectData.settings.auxTimerNames);
|
||||||
|
|
||||||
// load the rundown given by key otherwise load the first in the project
|
// load the rundown given by key otherwise load the first in the project
|
||||||
@@ -352,7 +352,7 @@ export async function patchCurrentProject(data: Partial<DatabaseModel>) {
|
|||||||
// we can pass some stuff straight to the data provider
|
// we can pass some stuff straight to the data provider
|
||||||
await getDataProvider().mergeIntoData(rest);
|
await getDataProvider().mergeIntoData(rest);
|
||||||
|
|
||||||
// the settings may contain new aux timer names, apply them and notify the clients
|
// AuxTimerService holds its own state, so a settings patch needs to be applied to it explicitly
|
||||||
if (rest.settings) {
|
if (rest.settings) {
|
||||||
auxTimerService.loadNames(getDataProvider().getSettings().auxTimerNames);
|
auxTimerService.loadNames(getDataProvider().getSettings().auxTimerNames);
|
||||||
sendRefetch(RefetchKey.Settings);
|
sendRefetch(RefetchKey.Settings);
|
||||||
|
|||||||
@@ -100,11 +100,7 @@ export {
|
|||||||
export { isPlaybackActive } from './src/playback-utils/playbackstate.js';
|
export { isPlaybackActive } from './src/playback-utils/playbackstate.js';
|
||||||
|
|
||||||
// aux timers
|
// aux timers
|
||||||
export {
|
export { auxTimerNameMaxLength, sanitiseAuxTimerNames } from './src/aux-timer-utils/auxTimerUtils.js';
|
||||||
auxTimerNameMaxLength,
|
|
||||||
normaliseAuxTimerNames,
|
|
||||||
numberOfAuxTimers,
|
|
||||||
} from './src/aux-timer-utils/auxTimerUtils.js';
|
|
||||||
|
|
||||||
//Colour
|
//Colour
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
import { auxTimerNameMaxLength, normaliseAuxTimerNames, numberOfAuxTimers } from './auxTimerUtils.js';
|
import { auxTimerNameMaxLength, sanitiseAuxTimerNames } from './auxTimerUtils.js';
|
||||||
|
|
||||||
describe('normaliseAuxTimerNames()', () => {
|
describe('sanitiseAuxTimerNames()', () => {
|
||||||
it('generates the default value when given nothing', () => {
|
it('generates the default value when given nothing', () => {
|
||||||
expect(normaliseAuxTimerNames()).toStrictEqual(['', '', '']);
|
expect(sanitiseAuxTimerNames()).toStrictEqual(['', '', '']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('always returns an entry per aux timer', () => {
|
it('always returns exactly three entries', () => {
|
||||||
expect(normaliseAuxTimerNames(['Speaker'])).toHaveLength(numberOfAuxTimers);
|
expect(sanitiseAuxTimerNames(['a', 'b', 'c', 'extra'])).toStrictEqual(['a', 'b', 'c']);
|
||||||
expect(normaliseAuxTimerNames(['a', 'b', 'c', 'extra'])).toStrictEqual(['a', 'b', 'c']);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pads missing entries with an empty string', () => {
|
it('pads missing entries with an empty string', () => {
|
||||||
expect(normaliseAuxTimerNames(['Speaker'])).toStrictEqual(['Speaker', '', '']);
|
expect(sanitiseAuxTimerNames(['Speaker'])).toStrictEqual(['Speaker', '', '']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('trims whitespace', () => {
|
it('trims whitespace', () => {
|
||||||
expect(normaliseAuxTimerNames([' Speaker ', '', ''])).toStrictEqual(['Speaker', '', '']);
|
expect(sanitiseAuxTimerNames([' Speaker ', '', ''])).toStrictEqual(['Speaker', '', '']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('caps the name length', () => {
|
it('caps the name length', () => {
|
||||||
const tooLong = 'a'.repeat(auxTimerNameMaxLength + 10);
|
const tooLong = 'a'.repeat(auxTimerNameMaxLength + 10);
|
||||||
expect(normaliseAuxTimerNames([tooLong])[0]).toHaveLength(auxTimerNameMaxLength);
|
expect(sanitiseAuxTimerNames([tooLong])[0]).toHaveLength(auxTimerNameMaxLength);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('falls back to defaults for malformed data', () => {
|
it('falls back to defaults for malformed data', () => {
|
||||||
expect(normaliseAuxTimerNames('not-an-array')).toStrictEqual(['', '', '']);
|
expect(sanitiseAuxTimerNames('not-an-array')).toStrictEqual(['', '', '']);
|
||||||
expect(normaliseAuxTimerNames(null)).toStrictEqual(['', '', '']);
|
expect(sanitiseAuxTimerNames(null)).toStrictEqual(['', '', '']);
|
||||||
expect(normaliseAuxTimerNames([42, {}, undefined])).toStrictEqual(['', '', '']);
|
expect(sanitiseAuxTimerNames([42, {}, undefined])).toStrictEqual(['', '', '']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
/** Number of aux timers available in ontime */
|
|
||||||
export const numberOfAuxTimers = 3;
|
|
||||||
|
|
||||||
/** Maximum length of a user given aux timer name */
|
/** Maximum length of a user given aux timer name */
|
||||||
export const auxTimerNameMaxLength = 30;
|
export const auxTimerNameMaxLength = 30;
|
||||||
|
|
||||||
/**
|
function sanitiseAuxTimerName(value: unknown): string {
|
||||||
* Normalises user or file provided aux timer names into a
|
return typeof value === 'string' ? value.trim().slice(0, auxTimerNameMaxLength) : '';
|
||||||
* fixed length array of trimmed, length capped strings.
|
}
|
||||||
* Missing or malformed entries fallback to an empty string,
|
|
||||||
* which consumers render as the default label.
|
/**
|
||||||
* Used when parsing project files, when validating API payloads
|
* Ontime has three aux timers. Given whatever was found on disk or in a request body,
|
||||||
* and to generate the default value.
|
* returns a name for each of them, so callers never need to deal with a missing
|
||||||
*/
|
* or malformed auxTimerNames (eg. a project file saved before this feature existed).
|
||||||
export function normaliseAuxTimerNames(maybeNames?: unknown): string[] {
|
*/
|
||||||
const source = Array.isArray(maybeNames) ? maybeNames : [];
|
export function sanitiseAuxTimerNames(names?: unknown): [string, string, string] {
|
||||||
|
const source = Array.isArray(names) ? names : [];
|
||||||
return Array.from({ length: numberOfAuxTimers }, (_, index) => {
|
return [sanitiseAuxTimerName(source[0]), sanitiseAuxTimerName(source[1]), sanitiseAuxTimerName(source[2])];
|
||||||
const value = source[index];
|
|
||||||
return typeof value === 'string' ? value.trim().slice(0, auxTimerNameMaxLength) : '';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { DatabaseModel } from 'ontime-types';
|
import type { DatabaseModel } from 'ontime-types';
|
||||||
import { EndAction, OntimeView, SupportedEntry, TimeStrategy, TimerType } from 'ontime-types';
|
import { EndAction, OntimeView, SupportedEntry, TimeStrategy, TimerType } from 'ontime-types';
|
||||||
|
|
||||||
import { normaliseAuxTimerNames } from '../aux-timer-utils/auxTimerUtils.js';
|
|
||||||
|
|
||||||
export const demoDb: DatabaseModel = {
|
export const demoDb: DatabaseModel = {
|
||||||
rundowns: {
|
rundowns: {
|
||||||
default: {
|
default: {
|
||||||
@@ -344,7 +342,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
operatorKey: null,
|
operatorKey: null,
|
||||||
timeFormat: '24',
|
timeFormat: '24',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
auxTimerNames: normaliseAuxTimerNames(),
|
auxTimerNames: ['', '', ''],
|
||||||
},
|
},
|
||||||
viewSettings: {
|
viewSettings: {
|
||||||
dangerColor: '#ff7300',
|
dangerColor: '#ff7300',
|
||||||
|
|||||||
Reference in New Issue
Block a user