mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 21:24:11 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f095f0f06 | |||
| 2a3c7f28ff | |||
| 23bfc0787f | |||
| 8169ea724c | |||
| efcd6494be | |||
| f720c56345 | |||
| 52a61777ed | |||
| dfd38c91f6 | |||
| 4cbc09864c | |||
| 11db39ee73 | |||
| edb79d5819 | |||
| 715fa66444 | |||
| 27f6d10677 | |||
| f2504072ce | |||
| 9e285ee8f3 | |||
| 1ccf2108eb | |||
| db56ac8049 | |||
| 0660f014c8 | |||
| 41c26778cb | |||
| e5f6e5afb9 | |||
| 04f7b342c9 | |||
| cdca5eaad3 | |||
| f39c8b46bb | |||
| c30b0cbf0a | |||
| 95bb9b4366 | |||
| 2dd872eb2d | |||
| c0f9521f86 | |||
| 5577fc0505 | |||
| b77d74a37e | |||
| 42c92329a0 | |||
| b9dbda072d | |||
| a670913320 | |||
| de93827e40 | |||
| 2b2c092d99 | |||
| 858aee83ac | |||
| 1e930851ca | |||
| 477fbbe666 | |||
| 4c38207a15 | |||
| 2b87f72acb | |||
| 563ea92a3b | |||
| 7a5bc76a9e | |||
| 89c7f8707b | |||
| 4c039220dc |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-ui",
|
"name": "ontime-ui",
|
||||||
"version": "2.16.2",
|
"version": "2.21.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "^2.7.0",
|
"@chakra-ui/react": "^2.7.0",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const USERFIELDS = ['userFields'];
|
|||||||
export const RUNDOWN = ['rundown'];
|
export const RUNDOWN = ['rundown'];
|
||||||
export const APP_INFO = ['appinfo'];
|
export const APP_INFO = ['appinfo'];
|
||||||
export const OSC_SETTINGS = ['oscSettings'];
|
export const OSC_SETTINGS = ['oscSettings'];
|
||||||
|
export const HTTP_SETTINGS = ['httpSettings'];
|
||||||
export const APP_SETTINGS = ['appSettings'];
|
export const APP_SETTINGS = ['appSettings'];
|
||||||
export const VIEW_SETTINGS = ['viewSettings'];
|
export const VIEW_SETTINGS = ['viewSettings'];
|
||||||
export const RUNTIME = ['runtimeStore'];
|
export const RUNTIME = ['runtimeStore'];
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
Alias,
|
Alias,
|
||||||
DatabaseModel,
|
DatabaseModel,
|
||||||
GetInfo,
|
GetInfo,
|
||||||
|
HttpSettings,
|
||||||
OntimeRundown,
|
OntimeRundown,
|
||||||
OSCSettings,
|
OSCSettings,
|
||||||
OscSubscription,
|
OscSubscription,
|
||||||
@@ -104,6 +105,23 @@ export async function getOSC(): Promise<OSCSettings> {
|
|||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description HTTP request to retrieve http settings
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
export async function getHTTP(): Promise<HttpSettings> {
|
||||||
|
const res = await axios.get(`${ontimeURL}/http`);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description HTTP request to mutate http settings
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
export async function postHTTP(data: HttpSettings) {
|
||||||
|
return axios.post(`${ontimeURL}/http`, data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description HTTP request to mutate osc settings
|
* @description HTTP request to mutate osc settings
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
|
|||||||
@@ -212,6 +212,7 @@ export default function TimeInput(props: TimeInputProps) {
|
|||||||
onKeyDown={onKeyDownHandler}
|
onKeyDown={onKeyDownHandler}
|
||||||
value={value}
|
value={value}
|
||||||
maxLength={8}
|
maxLength={8}
|
||||||
|
autoComplete='off'
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
|
|||||||
import { navigatorConstants } from '../../../viewerConfig';
|
import { navigatorConstants } from '../../../viewerConfig';
|
||||||
import useClickOutside from '../../hooks/useClickOutside';
|
import useClickOutside from '../../hooks/useClickOutside';
|
||||||
import useFullscreen from '../../hooks/useFullscreen';
|
import useFullscreen from '../../hooks/useFullscreen';
|
||||||
import { useKeyDown } from '../../hooks/useKeyDown';
|
|
||||||
import { useViewOptionsStore } from '../../stores/viewOptions';
|
import { useViewOptionsStore } from '../../stores/viewOptions';
|
||||||
|
|
||||||
import RenameClientModal from './rename-client-modal/RenameClientModal';
|
import RenameClientModal from './rename-client-modal/RenameClientModal';
|
||||||
@@ -28,13 +27,14 @@ function NavigationMenu() {
|
|||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
const menuRef = useRef<HTMLDivElement | null>(null);
|
const menuRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
useClickOutside(menuRef, () => setShowMenu(false));
|
useClickOutside(menuRef, () => setShowMenu(false));
|
||||||
|
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
const toggleMenu = () => setShowMenu((prev) => !prev);
|
const toggleMenu = () => setShowMenu((prev) => !prev);
|
||||||
useKeyDown(toggleMenu, ' ', { isDisabled: searchParams.get('edit') === 'true' || isOpen });
|
|
||||||
|
|
||||||
|
// show on mouse move
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let fadeOut: NodeJS.Timeout | null = null;
|
let fadeOut: NodeJS.Timeout | null = null;
|
||||||
const setShowMenuTrue = () => {
|
const setShowMenuTrue = () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { Input, Select, Switch } from '@chakra-ui/react';
|
import { Input, InputGroup, InputLeftElement, Select, Switch } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { isStringBoolean } from '../../utils/viewUtils';
|
import { isStringBoolean } from '../../utils/viewUtils';
|
||||||
|
|
||||||
@@ -9,16 +9,22 @@ interface EditFormInputProps {
|
|||||||
paramField: ParamField;
|
paramField: ParamField;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ParamInput({ paramField }: EditFormInputProps) {
|
export default function ParamInput(props: EditFormInputProps) {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { id, type } = paramField;
|
const { paramField } = props;
|
||||||
|
const { id, type, defaultValue } = paramField;
|
||||||
|
|
||||||
if (type === 'option') {
|
if (type === 'option') {
|
||||||
const optionFromParams = searchParams.get(id);
|
const optionFromParams = searchParams.get(id);
|
||||||
const defaultOptionValue = optionFromParams || undefined;
|
const defaultOptionValue = optionFromParams || defaultValue;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select placeholder='Select an option' variant='ontime' name={id} defaultValue={defaultOptionValue}>
|
<Select
|
||||||
|
placeholder={defaultValue ? undefined : 'Select an option'}
|
||||||
|
variant='ontime'
|
||||||
|
name={id}
|
||||||
|
defaultValue={defaultOptionValue}
|
||||||
|
>
|
||||||
{Object.entries(paramField.values).map(([key, value]) => (
|
{Object.entries(paramField.values).map(([key, value]) => (
|
||||||
<option key={key} value={key}>
|
<option key={key} value={key}>
|
||||||
{value}
|
{value}
|
||||||
@@ -29,19 +35,38 @@ export default function ParamInput({ paramField }: EditFormInputProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'boolean') {
|
if (type === 'boolean') {
|
||||||
const defaultCheckedValue = isStringBoolean(searchParams.get(id)) ?? false;
|
const defaultCheckedValue = isStringBoolean(searchParams.get(id)) || defaultValue;
|
||||||
|
|
||||||
// checked value should be 'true', so it can be captured by the form event
|
// checked value should be 'true', so it can be captured by the form event
|
||||||
return <Switch variant='ontime' name={id} defaultChecked={defaultCheckedValue} value='true' />;
|
return <Switch variant='ontime' name={id} defaultChecked={defaultCheckedValue} value='true' />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'number') {
|
if (type === 'number') {
|
||||||
const defaultNumberValue = searchParams.get(id) ?? '';
|
const { prefix, placeholder } = paramField;
|
||||||
|
const defaultNumberValue = searchParams.get(id) ?? defaultValue;
|
||||||
|
|
||||||
return <Input type='number' step='any' variant='ontime-filled' name={id} defaultValue={defaultNumberValue} />;
|
return (
|
||||||
|
<InputGroup variant='ontime-filled'>
|
||||||
|
{prefix && <InputLeftElement pointerEvents='none'>{prefix}</InputLeftElement>}
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
step='any'
|
||||||
|
variant='ontime-filled'
|
||||||
|
name={id}
|
||||||
|
defaultValue={defaultNumberValue}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultStringValue = searchParams.get(id) ?? '';
|
const defaultStringValue = searchParams.get(id) ?? defaultValue;
|
||||||
|
const { prefix, placeholder } = paramField;
|
||||||
|
|
||||||
return <Input variant='ontime-filled' name={id} defaultValue={defaultStringValue} />;
|
return (
|
||||||
|
<InputGroup variant='ontime-filled'>
|
||||||
|
{prefix && <InputLeftElement pointerEvents='none'>{prefix}</InputLeftElement>}
|
||||||
|
<Input name={id} defaultValue={defaultStringValue} placeholder={placeholder} />
|
||||||
|
</InputGroup>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,15 @@ import style from './ViewParamsEditor.module.scss';
|
|||||||
type ViewParamsObj = { [key: string]: string | FormDataEntryValue };
|
type ViewParamsObj = { [key: string]: string | FormDataEntryValue };
|
||||||
type SavedViewParams = Record<string, ViewParamsObj>;
|
type SavedViewParams = Record<string, ViewParamsObj>;
|
||||||
|
|
||||||
const getURLSearchParamsFromObj = (paramsObj: ViewParamsObj) =>
|
const getURLSearchParamsFromObj = (paramsObj: ViewParamsObj, paramFields: ParamField[]) => {
|
||||||
Object.entries(paramsObj).reduce((newSearchParams, [id, value]) => {
|
const defaultValues = paramFields.map(({ defaultValue }) => String(defaultValue));
|
||||||
|
|
||||||
|
return Object.entries(paramsObj).reduce((newSearchParams, [id, value]) => {
|
||||||
if (typeof value === 'string' && value.length) {
|
if (typeof value === 'string' && value.length) {
|
||||||
|
if (defaultValues.includes(value)) {
|
||||||
|
return newSearchParams;
|
||||||
|
}
|
||||||
|
|
||||||
newSearchParams.set(id, value);
|
newSearchParams.set(id, value);
|
||||||
|
|
||||||
return newSearchParams;
|
return newSearchParams;
|
||||||
@@ -32,6 +38,7 @@ const getURLSearchParamsFromObj = (paramsObj: ViewParamsObj) =>
|
|||||||
|
|
||||||
return newSearchParams;
|
return newSearchParams;
|
||||||
}, new URLSearchParams());
|
}, new URLSearchParams());
|
||||||
|
};
|
||||||
|
|
||||||
interface EditFormDrawerProps {
|
interface EditFormDrawerProps {
|
||||||
paramFields: ParamField[];
|
paramFields: ParamField[];
|
||||||
@@ -72,31 +79,30 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) {
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const onEditDrawerClose = () => {
|
const onCloseWithoutSaving = () => {
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
searchParams.delete('edit');
|
searchParams.delete('edit');
|
||||||
setSearchParams(searchParams);
|
setSearchParams(searchParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearParams = () => {
|
const resetParams = () => {
|
||||||
setStoredViewParams({ ...storedViewParams, [pathname]: {} });
|
setStoredViewParams({ ...storedViewParams, [pathname]: {} });
|
||||||
setSearchParams();
|
setSearchParams();
|
||||||
onClose();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onParamsFormSubmit = (formEvent: FormEvent<HTMLFormElement>) => {
|
const onParamsFormSubmit = (formEvent: FormEvent<HTMLFormElement>) => {
|
||||||
formEvent.preventDefault();
|
formEvent.preventDefault();
|
||||||
|
|
||||||
const newParamsObject = Object.fromEntries(new FormData(formEvent.currentTarget));
|
const newParamsObject = Object.fromEntries(new FormData(formEvent.currentTarget));
|
||||||
const newSearchParams = getURLSearchParamsFromObj(newParamsObject);
|
const newSearchParams = getURLSearchParamsFromObj(newParamsObject, paramFields);
|
||||||
|
|
||||||
setStoredViewParams({ ...storedViewParams, [pathname]: newParamsObject });
|
setStoredViewParams({ ...storedViewParams, [pathname]: newParamsObject });
|
||||||
setSearchParams(newSearchParams);
|
setSearchParams(newSearchParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer isOpen={isOpen} placement='right' onClose={onEditDrawerClose} size='lg'>
|
<Drawer isOpen={isOpen} placement='right' onClose={onCloseWithoutSaving} size='lg'>
|
||||||
<DrawerOverlay />
|
<DrawerOverlay />
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
<DrawerHeader className={style.drawerHeader}>
|
<DrawerHeader className={style.drawerHeader}>
|
||||||
@@ -119,10 +125,10 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) {
|
|||||||
</DrawerBody>
|
</DrawerBody>
|
||||||
|
|
||||||
<DrawerFooter className={style.drawerFooter}>
|
<DrawerFooter className={style.drawerFooter}>
|
||||||
<Button variant='ontime-ghosted' onClick={clearParams} type='reset'>
|
<Button variant='ontime-ghosted' onClick={resetParams} type='reset'>
|
||||||
Clear
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='ontime-subtle' onClick={onEditDrawerClose}>
|
<Button variant='ontime-subtle' onClick={onCloseWithoutSaving}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='ontime-filled' form='edit-params-form' type='submit'>
|
<Button variant='ontime-filled' form='edit-params-form' type='submit'>
|
||||||
|
|||||||
@@ -1,46 +1,56 @@
|
|||||||
import { UserFields } from 'ontime-types';
|
import { UserFields } from 'ontime-types';
|
||||||
|
import { TimeFormat } from 'ontime-types/src/definitions/core/TimeFormat.type';
|
||||||
|
|
||||||
import { ParamField } from './types';
|
import { ParamField } from './types';
|
||||||
|
|
||||||
export const TIME_FORMAT_OPTION: ParamField = {
|
export const getTimeOption = (timeFormat: TimeFormat): ParamField => ({
|
||||||
id: 'format',
|
id: 'format',
|
||||||
title: '12 / 24 hour timer',
|
title: '12 / 24 hour timer',
|
||||||
description: 'Whether to show the time in 12 or 24 hour mode. Overrides the global setting from preferences',
|
description: 'Whether to show the time in 12 or 24 hour mode. Overrides the global setting from preferences',
|
||||||
type: 'option',
|
type: 'option',
|
||||||
values: { '12': '12 hour AM/PM', '24': '24 hour' },
|
values: { '12': '12 hour AM/PM', '24': '24 hour' },
|
||||||
};
|
defaultValue: timeFormat,
|
||||||
|
});
|
||||||
|
|
||||||
export const CLOCK_OPTIONS: ParamField[] = [
|
export const getClockOptions = (timeFormat: TimeFormat): ParamField[] => [
|
||||||
TIME_FORMAT_OPTION,
|
getTimeOption(timeFormat),
|
||||||
{
|
{
|
||||||
id: 'key',
|
id: 'key',
|
||||||
title: 'Key Colour',
|
title: 'Key Colour',
|
||||||
description: 'Background colour in hexadecimal',
|
description: 'Background colour in hexadecimal',
|
||||||
|
prefix: '#',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: '00000000 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'text',
|
id: 'text',
|
||||||
title: 'Text Colour',
|
title: 'Text Colour',
|
||||||
description: 'Text colour in hexadecimal',
|
description: 'Text colour in hexadecimal',
|
||||||
|
prefix: '#',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: 'fffff (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'textbg',
|
id: 'textbg',
|
||||||
title: 'Text Background',
|
title: 'Text Background',
|
||||||
description: 'Colour of text background in hexadecimal',
|
description: 'Colour of text background in hexadecimal',
|
||||||
|
prefix: '#',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: '00000000 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'font',
|
id: 'font',
|
||||||
title: 'Font',
|
title: 'Font',
|
||||||
description: 'Font family, will use the fonts available in the system',
|
description: 'Font family, will use the fonts available in the system',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: 'Arial Black (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'size',
|
id: 'size',
|
||||||
title: 'Text Size',
|
title: 'Text Size',
|
||||||
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
|
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '1 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'alignx',
|
id: 'alignx',
|
||||||
@@ -48,12 +58,14 @@ export const CLOCK_OPTIONS: ParamField[] = [
|
|||||||
description: 'Moves the horizontally in page to start = left | center | end = right',
|
description: 'Moves the horizontally in page to start = left | center | end = right',
|
||||||
type: 'option',
|
type: 'option',
|
||||||
values: { start: 'Start', center: 'Center', end: 'End' },
|
values: { start: 'Start', center: 'Center', end: 'End' },
|
||||||
|
defaultValue: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'offsetx',
|
id: 'offsetx',
|
||||||
title: 'Offset Horizontal',
|
title: 'Offset Horizontal',
|
||||||
description: 'Offsets the timer horizontal position by a given amount in pixels',
|
description: 'Offsets the timer horizontal position by a given amount in pixels',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '0 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'aligny',
|
id: 'aligny',
|
||||||
@@ -61,46 +73,53 @@ export const CLOCK_OPTIONS: ParamField[] = [
|
|||||||
description: 'Moves the vertically in page to start = left | center | end = right',
|
description: 'Moves the vertically in page to start = left | center | end = right',
|
||||||
type: 'option',
|
type: 'option',
|
||||||
values: { start: 'Start', center: 'Center', end: 'End' },
|
values: { start: 'Start', center: 'Center', end: 'End' },
|
||||||
|
defaultValue: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'offsety',
|
id: 'offsety',
|
||||||
title: 'Offset Vertical',
|
title: 'Offset Vertical',
|
||||||
description: 'Offsets the timer vertical position by a given amount in pixels',
|
description: 'Offsets the timer vertical position by a given amount in pixels',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '0 (default)',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const TIMER_OPTIONS: ParamField[] = [
|
export const getTimerOptions = (timeFormat: TimeFormat): ParamField[] => [
|
||||||
TIME_FORMAT_OPTION,
|
getTimeOption(timeFormat),
|
||||||
{
|
{
|
||||||
id: 'hideClock',
|
id: 'hideClock',
|
||||||
title: 'Hide Time Now',
|
title: 'Hide Time Now',
|
||||||
description: 'Hides the Time Now field',
|
description: 'Hides the Time Now field',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hideCards',
|
id: 'hideCards',
|
||||||
title: 'Hide Cards',
|
title: 'Hide Cards',
|
||||||
description: 'Hides the Now and Next cards',
|
description: 'Hides the Now and Next cards',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hideProgress',
|
id: 'hideProgress',
|
||||||
title: 'Hide progress bar',
|
title: 'Hide progress bar',
|
||||||
description: 'Hides the progress bar',
|
description: 'Hides the progress bar',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hideMessage',
|
id: 'hideMessage',
|
||||||
title: 'Hide Presenter Message',
|
title: 'Hide Presenter Message',
|
||||||
description: 'Prevents the screen from displaying messages from the presenter',
|
description: 'Prevents the screen from displaying messages from the presenter',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hideExternal',
|
id: 'hideExternal',
|
||||||
title: 'Hide External',
|
title: 'Hide External',
|
||||||
description: 'Prevents the screen from displaying the external field',
|
description: 'Prevents the screen from displaying the external field',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -109,31 +128,39 @@ export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
|
|||||||
id: 'key',
|
id: 'key',
|
||||||
title: 'Key Colour',
|
title: 'Key Colour',
|
||||||
description: 'Background colour in hexadecimal',
|
description: 'Background colour in hexadecimal',
|
||||||
|
prefix: '#',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: '00000000 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'text',
|
id: 'text',
|
||||||
title: 'Text Colour',
|
title: 'Text Colour',
|
||||||
description: 'Text colour in hexadecimal',
|
description: 'Text colour in hexadecimal',
|
||||||
|
prefix: '#',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: 'fffff (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'textbg',
|
id: 'textbg',
|
||||||
title: 'Text Background',
|
title: 'Text Background',
|
||||||
description: 'Colour of text background in hexadecimal',
|
description: 'Colour of text background in hexadecimal',
|
||||||
|
prefix: '#',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: '00000000 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'font',
|
id: 'font',
|
||||||
title: 'Font',
|
title: 'Font',
|
||||||
description: 'Font family, will use the fonts available in the system',
|
description: 'Font family, will use the fonts available in the system',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: 'Arial Black (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'size',
|
id: 'size',
|
||||||
title: 'Text Size',
|
title: 'Text Size',
|
||||||
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
|
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '1 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'alignx',
|
id: 'alignx',
|
||||||
@@ -141,12 +168,14 @@ export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
|
|||||||
description: 'Moves the horizontally in page to start = left | center | end = right',
|
description: 'Moves the horizontally in page to start = left | center | end = right',
|
||||||
type: 'option',
|
type: 'option',
|
||||||
values: { start: 'Start', center: 'Center', end: 'End' },
|
values: { start: 'Start', center: 'Center', end: 'End' },
|
||||||
|
defaultValue: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'offsetx',
|
id: 'offsetx',
|
||||||
title: 'Offset Horizontal',
|
title: 'Offset Horizontal',
|
||||||
description: 'Offsets the timer horizontal position by a given amount in pixels',
|
description: 'Offsets the timer horizontal position by a given amount in pixels',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '0 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'aligny',
|
id: 'aligny',
|
||||||
@@ -154,145 +183,162 @@ export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
|
|||||||
description: 'Moves the vertically in page to start = left | center | end = right',
|
description: 'Moves the vertically in page to start = left | center | end = right',
|
||||||
type: 'option',
|
type: 'option',
|
||||||
values: { start: 'Start', center: 'Center', end: 'End' },
|
values: { start: 'Start', center: 'Center', end: 'End' },
|
||||||
|
defaultValue: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'offsety',
|
id: 'offsety',
|
||||||
title: 'Offset Vertical',
|
title: 'Offset Vertical',
|
||||||
description: 'Offsets the timer vertical position by a given amount in pixels',
|
description: 'Offsets the timer vertical position by a given amount in pixels',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '0 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hideovertime',
|
id: 'hideovertime',
|
||||||
title: 'Hide Overtime',
|
title: 'Hide Overtime',
|
||||||
description: 'Whether to suppress overtime styles (red borders and red text)',
|
description: 'Whether to suppress overtime styles (red borders and red text)',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hidemessages',
|
id: 'hidemessages',
|
||||||
title: 'Hide Message Overlay',
|
title: 'Hide Message Overlay',
|
||||||
description: 'Whether to hide the overlay from showing timer screen messages',
|
description: 'Whether to hide the overlay from showing timer screen messages',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hideendmessage',
|
id: 'hideendmessage',
|
||||||
title: 'Hide End Message',
|
title: 'Hide End Message',
|
||||||
description: 'Whether to hide end message and continue showing the clock if timer is in overtime',
|
description: 'Whether to hide end message and continue showing the clock if timer is in overtime',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const LOWER_THIRDS_OPTIONS: ParamField[] = [
|
export const LOWER_THIRDS_OPTIONS: ParamField[] = [
|
||||||
{
|
|
||||||
id: 'preset',
|
|
||||||
title: 'Preset',
|
|
||||||
description: 'Selects a style preset (0-1)',
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'size',
|
id: 'size',
|
||||||
title: 'Size',
|
title: 'Size',
|
||||||
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
|
description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '1 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'transition',
|
id: 'transition',
|
||||||
title: 'Transition',
|
title: 'Transition',
|
||||||
description: 'Transition in time in seconds (default 5)',
|
description: 'Transition in time in seconds (default 3)',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '3 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'text',
|
id: 'text',
|
||||||
title: 'Text Colour',
|
title: 'Text Colour',
|
||||||
description: 'Text colour in hexadecimal',
|
description: 'Text colour in hexadecimal',
|
||||||
|
prefix: '#',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: 'fffffa (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bg',
|
id: 'bg',
|
||||||
title: 'Text Background',
|
title: 'Text Background',
|
||||||
description: 'Text background colour in hexadecimal',
|
description: 'Text background colour in hexadecimal',
|
||||||
|
prefix: '#',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: '00000033 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'key',
|
id: 'key',
|
||||||
title: 'Key Colour',
|
title: 'Key Colour',
|
||||||
description: 'Screen background colour in hexadecimal',
|
description: 'Screen background colour in hexadecimal',
|
||||||
|
prefix: '#',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
placeholder: '00000033 (default)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'fadeout',
|
id: 'fadeout',
|
||||||
title: 'Fadeout',
|
title: 'Fadeout',
|
||||||
description: 'Time (in seconds) the lower third displays before fading out',
|
description: 'Time (in seconds) the lower third displays before fading out',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '3 (default)',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const BACKSTAGE_OPTIONS: ParamField[] = [
|
export const getBackstageOptions = (timeFormat: TimeFormat): ParamField[] => [
|
||||||
TIME_FORMAT_OPTION,
|
getTimeOption(timeFormat),
|
||||||
{
|
{
|
||||||
id: 'hidePast',
|
id: 'hidePast',
|
||||||
title: 'Hide past events',
|
title: 'Hide past events',
|
||||||
description: 'Scheduler will only show upcoming events',
|
description: 'Scheduler will only show upcoming events',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'stopCycle',
|
id: 'stopCycle',
|
||||||
title: 'Stop cycling through event pages',
|
title: 'Stop cycling through event pages',
|
||||||
description: 'Schedule will not auto-cycle through events',
|
description: 'Schedule will not auto-cycle through events',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'eventsPerPage',
|
id: 'eventsPerPage',
|
||||||
title: 'Events per page',
|
title: 'Events per page',
|
||||||
description: 'Sets the number of events on the page, can cause overlow',
|
description: 'Sets the number of events on the page, can cause overlow',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '7 (default)',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PUBLIC_OPTIONS: ParamField[] = [
|
export const getPublicOptions = (timeFormat: TimeFormat): ParamField[] => [
|
||||||
TIME_FORMAT_OPTION,
|
getTimeOption(timeFormat),
|
||||||
{
|
{
|
||||||
id: 'hidePast',
|
id: 'hidePast',
|
||||||
title: 'Hide past events',
|
title: 'Hide past events',
|
||||||
description: 'Scheduler will only show upcoming events',
|
description: 'Scheduler will only show upcoming events',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'stopCycle',
|
id: 'stopCycle',
|
||||||
title: 'Stop cycling through event pages',
|
title: 'Stop cycling through event pages',
|
||||||
description: 'Schedule will not auto-cycle through events',
|
description: 'Schedule will not auto-cycle through events',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'eventsPerPage',
|
id: 'eventsPerPage',
|
||||||
title: 'Events per page',
|
title: 'Events per page',
|
||||||
description: 'Sets the number of events on the page, can cause overlow',
|
description: 'Sets the number of events on the page, can cause overlow',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
placeholder: '7 (default)',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
export const STUDIO_CLOCK_OPTIONS: ParamField[] = [
|
export const getStudioClockOptions = (timeFormat: TimeFormat): ParamField[] => [
|
||||||
TIME_FORMAT_OPTION,
|
getTimeOption(timeFormat),
|
||||||
{
|
{
|
||||||
id: 'seconds',
|
id: 'seconds',
|
||||||
title: 'Show Seconds',
|
title: 'Show Seconds',
|
||||||
description: 'Shows seconds in clock',
|
description: 'Shows seconds in clock',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getOperatorOptions = (userFields: UserFields): ParamField[] => {
|
export const getOperatorOptions = (userFields: UserFields, timeFormat: TimeFormat): ParamField[] => {
|
||||||
return [
|
return [
|
||||||
TIME_FORMAT_OPTION,
|
getTimeOption(timeFormat),
|
||||||
{
|
{
|
||||||
id: 'showseconds',
|
id: 'showseconds',
|
||||||
title: 'Show seconds',
|
title: 'Show seconds',
|
||||||
description: 'Schedule shows hh:mm:ss',
|
description: 'Schedule shows hh:mm:ss',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hidepast',
|
id: 'hidepast',
|
||||||
title: 'Hide Past Events',
|
title: 'Hide Past Events',
|
||||||
description: 'Whether to events that have passed',
|
description: 'Whether to events that have passed',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'main',
|
id: 'main',
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ type BaseField = {
|
|||||||
description: string;
|
description: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type OptionsField = { type: 'option'; values: Record<string, string> };
|
type OptionsField = {
|
||||||
type StringField = { type: 'string' };
|
type: 'option';
|
||||||
type BooleanField = { type: 'boolean' };
|
values: Record<string, string>;
|
||||||
type NumberField = { type: 'number' };
|
defaultValue?: string;
|
||||||
|
};
|
||||||
|
type StringField = { type: 'string'; defaultValue?: string; prefix?: string; placeholder?: string };
|
||||||
|
type NumberField = { type: 'number'; defaultValue?: number; prefix?: string; placeholder?: string };
|
||||||
|
type BooleanField = { type: 'boolean'; defaultValue: boolean };
|
||||||
|
|
||||||
export type ParamField = BaseField & (StringField | BooleanField | NumberField | OptionsField);
|
export type ParamField = BaseField & (StringField | BooleanField | NumberField | OptionsField);
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
|
import { HttpSettings } from 'ontime-types';
|
||||||
|
|
||||||
|
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
|
||||||
|
import { HTTP_SETTINGS } from '../api/apiConstants';
|
||||||
|
import { logAxiosError } from '../api/apiUtils';
|
||||||
|
import { getHTTP, postHTTP } from '../api/ontimeApi';
|
||||||
|
import { httpPlaceholder } from '../models/Http';
|
||||||
|
import { ontimeQueryClient } from '../queryClient';
|
||||||
|
|
||||||
|
export function useHttpSettings() {
|
||||||
|
const { data, status, isFetching, isError, refetch } = useQuery({
|
||||||
|
queryKey: HTTP_SETTINGS,
|
||||||
|
queryFn: getHTTP,
|
||||||
|
placeholderData: httpPlaceholder,
|
||||||
|
retry: 5,
|
||||||
|
retryDelay: (attempt: number) => attempt * 2500,
|
||||||
|
refetchInterval: queryRefetchIntervalSlow,
|
||||||
|
networkMode: 'always',
|
||||||
|
});
|
||||||
|
|
||||||
|
// we need to jump through some hoops because of the type op port
|
||||||
|
return { data: data! as unknown as HttpSettings, status, isFetching, isError, refetch };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePostHttpSettings() {
|
||||||
|
const { isPending, mutateAsync } = useMutation({
|
||||||
|
mutationFn: postHTTP,
|
||||||
|
onError: (error) => logAxiosError('Error saving HTTP settings', error),
|
||||||
|
onSettled: () => ontimeQueryClient.invalidateQueries({ queryKey: HTTP_SETTINGS }),
|
||||||
|
});
|
||||||
|
return { isPending, mutateAsync };
|
||||||
|
}
|
||||||
@@ -1,26 +1,13 @@
|
|||||||
export const httpPlaceholder = {
|
import { HttpSettings } from 'ontime-types';
|
||||||
onLoad: {
|
|
||||||
url: '',
|
export const httpPlaceholder: HttpSettings = {
|
||||||
enabled: false,
|
enabledOut: false,
|
||||||
},
|
subscriptions: {
|
||||||
onStart: {
|
onLoad: [],
|
||||||
url: '',
|
onStart: [],
|
||||||
enabled: false,
|
onUpdate: [],
|
||||||
},
|
onPause: [],
|
||||||
onUpdate: {
|
onStop: [],
|
||||||
url: '',
|
onFinish: [],
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
onPause: {
|
|
||||||
url: '',
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
onStop: {
|
|
||||||
url: '',
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
onFinish: {
|
|
||||||
url: '',
|
|
||||||
enabled: false,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isIPAddress, isOnlyNumbers } from '../regex';
|
import { isIPAddress, isOnlyNumbers, startsWithHttp } from '../regex';
|
||||||
|
|
||||||
describe('simple tests for regex', () => {
|
describe('simple tests for regex', () => {
|
||||||
test('isOnlyNumbers', () => {
|
test('isOnlyNumbers', () => {
|
||||||
@@ -24,4 +24,16 @@ describe('simple tests for regex', () => {
|
|||||||
expect(isIPAddress.test(t)).toBe(false);
|
expect(isIPAddress.test(t)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('startsWithHttp', () => {
|
||||||
|
const right = ['http://test'];
|
||||||
|
const wrong = ['https://test', 'testing', '123.0.1'];
|
||||||
|
|
||||||
|
right.forEach((t) => {
|
||||||
|
expect(startsWithHttp.test(t)).toBe(true);
|
||||||
|
});
|
||||||
|
wrong.forEach((t) => {
|
||||||
|
expect(startsWithHttp.test(t)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
export const isOnlyNumbers = /^\d+$/;
|
export const isOnlyNumbers = /^\d+$/;
|
||||||
export const isIPAddress = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
|
export const isIPAddress = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
|
||||||
|
export const startsWithHttp = /^http:\/\//;
|
||||||
|
export const startsWithHttpOrS = /^https?:\/\//;
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ export default function EventEditor() {
|
|||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
<CopyTag label='Event ID'>{event.id}</CopyTag>
|
<CopyTag label='Event ID'>{event.id}</CopyTag>
|
||||||
<CopyTag label='OSC trigger by id'>{`/ontime/gotoid/${event.id}`}</CopyTag>
|
<CopyTag label='OSC trigger by id'>{`/ontime/gotoid "${event.id}"`}</CopyTag>
|
||||||
<CopyTag label='OSC trigger by cue'>{`/ontime/gotocue/${event.cue}`}</CopyTag>
|
<CopyTag label='OSC trigger by cue'>{`/ontime/gotocue "${event.cue}"`}</CopyTag>
|
||||||
</EventEditorDataRight>
|
</EventEditorDataRight>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export default function CountedTextInput(props: CountedTextInputProps) {
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
|
autoComplete='off'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
font-size: $inner-section-text-size;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -14,12 +15,12 @@
|
|||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
min-width: max-content;
|
min-width: max-content;
|
||||||
font-size: $inner-section-text-size;
|
|
||||||
color: $label-gray;
|
color: $label-gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
|
font-size: $inner-section-text-size;
|
||||||
color: $label-gray;
|
color: $label-gray;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { ModalBody, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/r
|
|||||||
|
|
||||||
import ModalWrapper from '../ModalWrapper';
|
import ModalWrapper from '../ModalWrapper';
|
||||||
|
|
||||||
import OscIntegration from './OscIntegration';
|
import HttpIntegration from './http/HttpIntegration';
|
||||||
import OscSettings from './OscSettings';
|
import OscIntegration from './osc/OscIntegration';
|
||||||
|
import OscSettings from './osc/OscSettings';
|
||||||
|
|
||||||
import styles from '../Modal.module.scss';
|
import styles from '../Modal.module.scss';
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ export default function IntegrationModal(props: IntegrationModalProps) {
|
|||||||
<TabList>
|
<TabList>
|
||||||
<Tab>OSC</Tab>
|
<Tab>OSC</Tab>
|
||||||
<Tab>OSC Integration</Tab>
|
<Tab>OSC Integration</Tab>
|
||||||
|
<Tab>HTTP Integration</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
@@ -38,6 +40,9 @@ export default function IntegrationModal(props: IntegrationModalProps) {
|
|||||||
<TabPanel>
|
<TabPanel>
|
||||||
<OscIntegration />
|
<OscIntegration />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<HttpIntegration />
|
||||||
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { Switch } from '@chakra-ui/react';
|
||||||
|
import type { HttpSettings } from 'ontime-types';
|
||||||
|
import { TimerLifeCycle } from 'ontime-types';
|
||||||
|
|
||||||
|
import { useHttpSettings, usePostHttpSettings } from '../../../../common/hooks-query/useHttpSettings';
|
||||||
|
import { useEmitLog } from '../../../../common/stores/logger';
|
||||||
|
import ModalLoader from '../../modal-loader/ModalLoader';
|
||||||
|
import OntimeModalFooter from '../../OntimeModalFooter';
|
||||||
|
import { OntimeCycle, sectionText } from '../integration.utils';
|
||||||
|
|
||||||
|
import HttpSubscriptionRow from './HttpSubscriptionRow';
|
||||||
|
|
||||||
|
import styles from '../../Modal.module.scss';
|
||||||
|
|
||||||
|
export default function HttpIntegration() {
|
||||||
|
const { data, isFetching } = useHttpSettings();
|
||||||
|
const { mutateAsync } = usePostHttpSettings();
|
||||||
|
const { emitError } = useEmitLog();
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
register,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, isDirty, isValid },
|
||||||
|
} = useForm<HttpSettings>({
|
||||||
|
mode: 'onBlur',
|
||||||
|
defaultValues: data,
|
||||||
|
values: data,
|
||||||
|
resetOptions: {
|
||||||
|
keepDirtyValues: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [showSection, setShowSection] = useState<OntimeCycle>(TimerLifeCycle.onLoad);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
reset(data);
|
||||||
|
}
|
||||||
|
}, [data, reset]);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
reset(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (values: HttpSettings) => {
|
||||||
|
try {
|
||||||
|
const newSettings: HttpSettings = {
|
||||||
|
enabledOut: Boolean(values.enabledOut),
|
||||||
|
subscriptions: {
|
||||||
|
onLoad: values.subscriptions.onLoad ?? [],
|
||||||
|
onStart: values.subscriptions.onStart ?? [],
|
||||||
|
onPause: values.subscriptions.onPause ?? [],
|
||||||
|
onStop: values.subscriptions.onStop ?? [],
|
||||||
|
onUpdate: values.subscriptions.onUpdate ?? [],
|
||||||
|
onFinish: values.subscriptions.onFinish ?? [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await mutateAsync(newSettings);
|
||||||
|
} catch (error) {
|
||||||
|
emitError(`Error setting HTML: ${error}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isFetching) {
|
||||||
|
return <ModalLoader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const placeholder = 'http://x.x.x.x:xxxx/api/path';
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='http-subscriptions'>
|
||||||
|
<div className={styles.splitSection}>
|
||||||
|
<div>
|
||||||
|
<span className={`${styles.sectionTitle} ${styles.main}`}>HTTP Output</span>
|
||||||
|
<span className={styles.sectionSubtitle}>Ontime data feedback</span>
|
||||||
|
</div>
|
||||||
|
<Switch {...register('enabledOut')} variant='ontime-on-light' />
|
||||||
|
</div>
|
||||||
|
<HttpSubscriptionRow
|
||||||
|
cycle={TimerLifeCycle.onLoad}
|
||||||
|
title={sectionText.onLoad.title}
|
||||||
|
subtitle={sectionText.onLoad.subtitle}
|
||||||
|
visible={showSection === TimerLifeCycle.onLoad}
|
||||||
|
setShowSection={setShowSection}
|
||||||
|
register={register}
|
||||||
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
<HttpSubscriptionRow
|
||||||
|
cycle={TimerLifeCycle.onStart}
|
||||||
|
title={sectionText.onStart.title}
|
||||||
|
subtitle={sectionText.onStart.subtitle}
|
||||||
|
visible={showSection === TimerLifeCycle.onStart}
|
||||||
|
setShowSection={setShowSection}
|
||||||
|
register={register}
|
||||||
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
<HttpSubscriptionRow
|
||||||
|
cycle={TimerLifeCycle.onPause}
|
||||||
|
title={sectionText.onPause.title}
|
||||||
|
subtitle={sectionText.onPause.subtitle}
|
||||||
|
visible={showSection === TimerLifeCycle.onPause}
|
||||||
|
setShowSection={setShowSection}
|
||||||
|
register={register}
|
||||||
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
<HttpSubscriptionRow
|
||||||
|
cycle={TimerLifeCycle.onStop}
|
||||||
|
title={sectionText.onStop.title}
|
||||||
|
subtitle={sectionText.onStop.subtitle}
|
||||||
|
visible={showSection === TimerLifeCycle.onStop}
|
||||||
|
setShowSection={setShowSection}
|
||||||
|
register={register}
|
||||||
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
<HttpSubscriptionRow
|
||||||
|
cycle={TimerLifeCycle.onUpdate}
|
||||||
|
title={sectionText.onUpdate.title}
|
||||||
|
subtitle={sectionText.onUpdate.subtitle}
|
||||||
|
visible={showSection === TimerLifeCycle.onUpdate}
|
||||||
|
setShowSection={setShowSection}
|
||||||
|
register={register}
|
||||||
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
<HttpSubscriptionRow
|
||||||
|
cycle={TimerLifeCycle.onFinish}
|
||||||
|
title={sectionText.onFinish.title}
|
||||||
|
subtitle={sectionText.onFinish.subtitle}
|
||||||
|
visible={showSection === TimerLifeCycle.onFinish}
|
||||||
|
setShowSection={setShowSection}
|
||||||
|
register={register}
|
||||||
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
<OntimeModalFooter
|
||||||
|
formId='http-subscriptions'
|
||||||
|
handleRevert={resetForm}
|
||||||
|
isDirty={isDirty}
|
||||||
|
isValid={isValid}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { Control, useFieldArray, UseFormRegister } from 'react-hook-form';
|
||||||
|
import { Button, IconButton, Input, Switch } from '@chakra-ui/react';
|
||||||
|
import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp';
|
||||||
|
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
|
||||||
|
import { HttpSettings, TimerLifeCycle } from 'ontime-types';
|
||||||
|
|
||||||
|
import { useEmitLog } from '../../../../common/stores/logger';
|
||||||
|
import { startsWithHttpOrS } from '../../../../common/utils/regex';
|
||||||
|
|
||||||
|
import collapseStyles from '../../../../common/components/collapse-bar/CollapseBar.module.scss';
|
||||||
|
import styles from '../../Modal.module.scss';
|
||||||
|
|
||||||
|
interface SubscriptionRowProps {
|
||||||
|
cycle: TimerLifeCycle;
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
visible: boolean;
|
||||||
|
setShowSection: (cycle: TimerLifeCycle) => void;
|
||||||
|
register: UseFormRegister<HttpSettings>;
|
||||||
|
control: Control<HttpSettings>;
|
||||||
|
placeholder: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SubscriptionRow(props: SubscriptionRowProps) {
|
||||||
|
const { cycle, title, subtitle, visible, setShowSection, register, control, placeholder } = props;
|
||||||
|
const { emitError } = useEmitLog();
|
||||||
|
const { fields, append, remove } = useFieldArray({
|
||||||
|
name: `subscriptions.${cycle}`,
|
||||||
|
control,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasTooManyOptions = fields.length >= 3;
|
||||||
|
const headerStyle = `${styles.splitSection} ${visible ? '' : styles.showPointer}`;
|
||||||
|
|
||||||
|
const sectionTitle = `${title} ${fields.length ? fields.length : '-'} / 3`;
|
||||||
|
|
||||||
|
const handleAddNew = () => {
|
||||||
|
if (hasTooManyOptions) {
|
||||||
|
emitError(`Maximum amount of ${cycle} subscriptions reached (3)`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
append({
|
||||||
|
message: '',
|
||||||
|
enabled: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={headerStyle} onClick={() => setShowSection(cycle)}>
|
||||||
|
<div>
|
||||||
|
<span className={`${styles.sectionTitle} ${styles.main}`}>{sectionTitle}</span>
|
||||||
|
{visible && <span className={styles.sectionSubtitle}>{subtitle}</span>}
|
||||||
|
</div>
|
||||||
|
<FiChevronUp className={visible ? collapseStyles.moreCollapsed : collapseStyles.moreExpanded} />
|
||||||
|
</div>
|
||||||
|
{visible && (
|
||||||
|
<>
|
||||||
|
{fields.map((subscription, index) => (
|
||||||
|
<div key={subscription.id} className={styles.entryRow}>
|
||||||
|
<IconButton
|
||||||
|
icon={<IoRemove />}
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
aria-label='delete'
|
||||||
|
size='xs'
|
||||||
|
colorScheme='red'
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={placeholder}
|
||||||
|
size='xs'
|
||||||
|
variant='ontime-filled-on-light'
|
||||||
|
autoComplete='off'
|
||||||
|
{...register(`subscriptions.${cycle}.${index}.message`, {
|
||||||
|
pattern: { value: startsWithHttpOrS, message: 'Request address must start with http://' },
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Switch variant='ontime-on-light' {...register(`subscriptions.${cycle}.${index}.enabled`)} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
onClick={handleAddNew}
|
||||||
|
className={styles.shiftRight}
|
||||||
|
isDisabled={hasTooManyOptions}
|
||||||
|
size='xs'
|
||||||
|
colorScheme='blue'
|
||||||
|
variant='outline'
|
||||||
|
padding='0 2em'
|
||||||
|
>
|
||||||
|
Add new
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { TimerLifeCycle } from 'ontime-types';
|
||||||
|
|
||||||
|
export type OntimeCycle = keyof typeof TimerLifeCycle;
|
||||||
|
|
||||||
|
export const sectionText: { [key in TimerLifeCycle]: { title: string; subtitle: string } } = {
|
||||||
|
onLoad: {
|
||||||
|
title: 'On Load',
|
||||||
|
subtitle: 'Triggers when a timer is loaded',
|
||||||
|
},
|
||||||
|
onStart: {
|
||||||
|
title: 'On Start',
|
||||||
|
subtitle: 'Triggers when a timer starts',
|
||||||
|
},
|
||||||
|
onPause: {
|
||||||
|
title: 'On Pause',
|
||||||
|
subtitle: 'Triggers when a running timer is paused',
|
||||||
|
},
|
||||||
|
onStop: {
|
||||||
|
title: 'On Stop',
|
||||||
|
subtitle: 'Triggers when a running timer is stopped',
|
||||||
|
},
|
||||||
|
onUpdate: {
|
||||||
|
title: 'On Every Second',
|
||||||
|
subtitle: 'Triggers when a running timer is updated (at least once a second, can be more)',
|
||||||
|
},
|
||||||
|
onFinish: {
|
||||||
|
title: 'On Finish',
|
||||||
|
subtitle: 'Triggers when a running reaches 0',
|
||||||
|
},
|
||||||
|
};
|
||||||
+13
-34
@@ -3,43 +3,15 @@ import { useForm } from 'react-hook-form';
|
|||||||
import type { OscSubscription } from 'ontime-types';
|
import type { OscSubscription } from 'ontime-types';
|
||||||
import { TimerLifeCycle } from 'ontime-types';
|
import { TimerLifeCycle } from 'ontime-types';
|
||||||
|
|
||||||
import useOscSettings, { usePostOscSubscriptions } from '../../../common/hooks-query/useOscSettings';
|
import useOscSettings, { usePostOscSubscriptions } from '../../../../common/hooks-query/useOscSettings';
|
||||||
import { useEmitLog } from '../../../common/stores/logger';
|
import { useEmitLog } from '../../../../common/stores/logger';
|
||||||
import ModalLoader from '../modal-loader/ModalLoader';
|
import ModalLoader from '../../modal-loader/ModalLoader';
|
||||||
import OntimeModalFooter from '../OntimeModalFooter';
|
import OntimeModalFooter from '../../OntimeModalFooter';
|
||||||
|
import { type OntimeCycle, sectionText } from '../integration.utils';
|
||||||
|
|
||||||
import OscSubscriptionRow from './OscSubscriptionRow';
|
import OscSubscriptionRow from './OscSubscriptionRow';
|
||||||
|
|
||||||
import styles from '../Modal.module.scss';
|
import styles from '../../Modal.module.scss';
|
||||||
|
|
||||||
type OntimeCycle = keyof typeof TimerLifeCycle;
|
|
||||||
|
|
||||||
const sectionText: { [key in TimerLifeCycle]: { title: string; subtitle: string } } = {
|
|
||||||
onLoad: {
|
|
||||||
title: 'On Load',
|
|
||||||
subtitle: 'Triggers when a timer is loaded',
|
|
||||||
},
|
|
||||||
onStart: {
|
|
||||||
title: 'On Start',
|
|
||||||
subtitle: 'Triggers when a timer starts',
|
|
||||||
},
|
|
||||||
onPause: {
|
|
||||||
title: 'On Pause',
|
|
||||||
subtitle: 'Triggers when a running timer is paused',
|
|
||||||
},
|
|
||||||
onStop: {
|
|
||||||
title: 'On Stop',
|
|
||||||
subtitle: 'Triggers when a running timer is stopped',
|
|
||||||
},
|
|
||||||
onUpdate: {
|
|
||||||
title: 'On Every Second',
|
|
||||||
subtitle: 'Triggers when a running timer is updated (at least once a second, can be more)',
|
|
||||||
},
|
|
||||||
onFinish: {
|
|
||||||
title: 'On Finish',
|
|
||||||
subtitle: 'Triggers when a running reaches 0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function OscIntegration() {
|
export default function OscIntegration() {
|
||||||
const { data, isFetching } = useOscSettings();
|
const { data, isFetching } = useOscSettings();
|
||||||
@@ -92,6 +64,7 @@ export default function OscIntegration() {
|
|||||||
return <ModalLoader />;
|
return <ModalLoader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const placeholder = 'OSC message';
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='osc-subscriptions'>
|
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='osc-subscriptions'>
|
||||||
<OscSubscriptionRow
|
<OscSubscriptionRow
|
||||||
@@ -102,6 +75,7 @@ export default function OscIntegration() {
|
|||||||
setShowSection={setShowSection}
|
setShowSection={setShowSection}
|
||||||
register={register}
|
register={register}
|
||||||
control={control}
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
<OscSubscriptionRow
|
<OscSubscriptionRow
|
||||||
cycle={TimerLifeCycle.onStart}
|
cycle={TimerLifeCycle.onStart}
|
||||||
@@ -111,6 +85,7 @@ export default function OscIntegration() {
|
|||||||
setShowSection={setShowSection}
|
setShowSection={setShowSection}
|
||||||
register={register}
|
register={register}
|
||||||
control={control}
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
<OscSubscriptionRow
|
<OscSubscriptionRow
|
||||||
cycle={TimerLifeCycle.onPause}
|
cycle={TimerLifeCycle.onPause}
|
||||||
@@ -120,6 +95,7 @@ export default function OscIntegration() {
|
|||||||
setShowSection={setShowSection}
|
setShowSection={setShowSection}
|
||||||
register={register}
|
register={register}
|
||||||
control={control}
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
<OscSubscriptionRow
|
<OscSubscriptionRow
|
||||||
cycle={TimerLifeCycle.onStop}
|
cycle={TimerLifeCycle.onStop}
|
||||||
@@ -129,6 +105,7 @@ export default function OscIntegration() {
|
|||||||
setShowSection={setShowSection}
|
setShowSection={setShowSection}
|
||||||
register={register}
|
register={register}
|
||||||
control={control}
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
<OscSubscriptionRow
|
<OscSubscriptionRow
|
||||||
cycle={TimerLifeCycle.onUpdate}
|
cycle={TimerLifeCycle.onUpdate}
|
||||||
@@ -138,6 +115,7 @@ export default function OscIntegration() {
|
|||||||
setShowSection={setShowSection}
|
setShowSection={setShowSection}
|
||||||
register={register}
|
register={register}
|
||||||
control={control}
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
<OscSubscriptionRow
|
<OscSubscriptionRow
|
||||||
cycle={TimerLifeCycle.onFinish}
|
cycle={TimerLifeCycle.onFinish}
|
||||||
@@ -147,6 +125,7 @@ export default function OscIntegration() {
|
|||||||
setShowSection={setShowSection}
|
setShowSection={setShowSection}
|
||||||
register={register}
|
register={register}
|
||||||
control={control}
|
control={control}
|
||||||
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
<OntimeModalFooter
|
<OntimeModalFooter
|
||||||
formId='osc-subscriptions'
|
formId='osc-subscriptions'
|
||||||
+7
-7
@@ -4,14 +4,14 @@ import { useEffect } from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { FormControl, Input, Switch } from '@chakra-ui/react';
|
import { FormControl, Input, Switch } from '@chakra-ui/react';
|
||||||
|
|
||||||
import useOscSettings, { useOscSettingsMutation } from '../../../common/hooks-query/useOscSettings';
|
import useOscSettings, { useOscSettingsMutation } from '../../../../common/hooks-query/useOscSettings';
|
||||||
import { PlaceholderSettings } from '../../../common/models/OscSettings';
|
import { PlaceholderSettings } from '../../../../common/models/OscSettings';
|
||||||
import { useEmitLog } from '../../../common/stores/logger';
|
import { useEmitLog } from '../../../../common/stores/logger';
|
||||||
import { isIPAddress, isOnlyNumbers } from '../../../common/utils/regex';
|
import { isIPAddress, isOnlyNumbers } from '../../../../common/utils/regex';
|
||||||
import ModalLoader from '../modal-loader/ModalLoader';
|
import ModalLoader from '../../modal-loader/ModalLoader';
|
||||||
import OntimeModalFooter from '../OntimeModalFooter';
|
import OntimeModalFooter from '../../OntimeModalFooter';
|
||||||
|
|
||||||
import styles from '../Modal.module.scss';
|
import styles from '../../Modal.module.scss';
|
||||||
|
|
||||||
export default function OscSettings() {
|
export default function OscSettings() {
|
||||||
const { data, isFetching } = useOscSettings();
|
const { data, isFetching } = useOscSettings();
|
||||||
+7
-6
@@ -2,12 +2,12 @@ import { Control, useFieldArray, UseFormRegister } from 'react-hook-form';
|
|||||||
import { Button, IconButton, Input, Switch } from '@chakra-ui/react';
|
import { Button, IconButton, Input, Switch } from '@chakra-ui/react';
|
||||||
import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp';
|
import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp';
|
||||||
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
|
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
|
||||||
import { OscSubscription, TimerLifeCycle } from 'ontime-types';
|
import type { OscSubscription, TimerLifeCycle } from 'ontime-types';
|
||||||
|
|
||||||
import { useEmitLog } from '../../../common/stores/logger';
|
import { useEmitLog } from '../../../../common/stores/logger';
|
||||||
|
|
||||||
import collapseStyles from '../../../common/components/collapse-bar/CollapseBar.module.scss';
|
import collapseStyles from '../../../../common/components/collapse-bar/CollapseBar.module.scss';
|
||||||
import styles from '../Modal.module.scss';
|
import styles from '../../Modal.module.scss';
|
||||||
|
|
||||||
interface OscSubscriptionRowProps {
|
interface OscSubscriptionRowProps {
|
||||||
cycle: TimerLifeCycle;
|
cycle: TimerLifeCycle;
|
||||||
@@ -17,10 +17,11 @@ interface OscSubscriptionRowProps {
|
|||||||
setShowSection: (cycle: TimerLifeCycle) => void;
|
setShowSection: (cycle: TimerLifeCycle) => void;
|
||||||
register: UseFormRegister<OscSubscription>;
|
register: UseFormRegister<OscSubscription>;
|
||||||
control: Control<OscSubscription>;
|
control: Control<OscSubscription>;
|
||||||
|
placeholder: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function OscSubscriptionRow(props: OscSubscriptionRowProps) {
|
export default function OscSubscriptionRow(props: OscSubscriptionRowProps) {
|
||||||
const { cycle, title, subtitle, visible, setShowSection, register, control } = props;
|
const { cycle, title, subtitle, visible, setShowSection, register, control, placeholder } = props;
|
||||||
const { emitError } = useEmitLog();
|
const { emitError } = useEmitLog();
|
||||||
const { fields, append, remove } = useFieldArray({
|
const { fields, append, remove } = useFieldArray({
|
||||||
name: cycle,
|
name: cycle,
|
||||||
@@ -64,7 +65,7 @@ export default function OscSubscriptionRow(props: OscSubscriptionRowProps) {
|
|||||||
colorScheme='red'
|
colorScheme='red'
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
placeholder='OSC Message'
|
placeholder={placeholder}
|
||||||
size='xs'
|
size='xs'
|
||||||
variant='ontime-filled-on-light'
|
variant='ontime-filled-on-light'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
@@ -11,6 +11,7 @@ import useFollowComponent from '../../common/hooks/useFollowComponent';
|
|||||||
import { useOperator } from '../../common/hooks/useSocket';
|
import { useOperator } from '../../common/hooks/useSocket';
|
||||||
import useProjectData from '../../common/hooks-query/useProjectData';
|
import useProjectData from '../../common/hooks-query/useProjectData';
|
||||||
import useRundown from '../../common/hooks-query/useRundown';
|
import useRundown from '../../common/hooks-query/useRundown';
|
||||||
|
import useSettings from '../../common/hooks-query/useSettings';
|
||||||
import useUserFields from '../../common/hooks-query/useUserFields';
|
import useUserFields from '../../common/hooks-query/useUserFields';
|
||||||
import { debounce } from '../../common/utils/debounce';
|
import { debounce } from '../../common/utils/debounce';
|
||||||
import { isStringBoolean } from '../../common/utils/viewUtils';
|
import { isStringBoolean } from '../../common/utils/viewUtils';
|
||||||
@@ -40,6 +41,7 @@ export default function Operator() {
|
|||||||
|
|
||||||
const featureData = useOperator();
|
const featureData = useOperator();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
const { data: settings } = useSettings();
|
||||||
|
|
||||||
const [showEditPrompt, setShowEditPrompt] = useState(false);
|
const [showEditPrompt, setShowEditPrompt] = useState(false);
|
||||||
const [editEvent, setEditEvent] = useState<PartialEdit | null>(null);
|
const [editEvent, setEditEvent] = useState<PartialEdit | null>(null);
|
||||||
@@ -127,7 +129,7 @@ export default function Operator() {
|
|||||||
const subscribedAlias = subscribe ? userFields[subscribe] : '';
|
const subscribedAlias = subscribe ? userFields[subscribe] : '';
|
||||||
const showSeconds = isStringBoolean(searchParams.get('showseconds'));
|
const showSeconds = isStringBoolean(searchParams.get('showseconds'));
|
||||||
|
|
||||||
const operatorOptions = getOperatorOptions(userFields);
|
const operatorOptions = getOperatorOptions(userFields, settings?.timeFormat ?? '24');
|
||||||
let isPast = Boolean(featureData.selectedEventId);
|
let isPast = Boolean(featureData.selectedEventId);
|
||||||
const hidePast = isStringBoolean(searchParams.get('hidepast'));
|
const hidePast = isStringBoolean(searchParams.get('hidepast'));
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { ComponentType, useMemo } from 'react';
|
import { ComponentType, useMemo } from 'react';
|
||||||
import { TimeManagerType } from 'common/models/TimeManager.type';
|
import { TimeManagerType } from 'common/models/TimeManager.type';
|
||||||
import { Message, OntimeEvent, ProjectData, SupportedEvent, TimerMessage, ViewSettings } from 'ontime-types';
|
import { Message, OntimeEvent, ProjectData, Settings, SupportedEvent, TimerMessage, ViewSettings } from 'ontime-types';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
|
|
||||||
import useProjectData from '../../common/hooks-query/useProjectData';
|
import useProjectData from '../../common/hooks-query/useProjectData';
|
||||||
import useRundown from '../../common/hooks-query/useRundown';
|
import useRundown from '../../common/hooks-query/useRundown';
|
||||||
|
import useSettings from '../../common/hooks-query/useSettings';
|
||||||
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
||||||
import { runtime } from '../../common/stores/runtime';
|
import { runtime } from '../../common/stores/runtime';
|
||||||
import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
||||||
@@ -27,6 +28,7 @@ type WithDataProps = {
|
|||||||
nextId: string | null;
|
nextId: string | null;
|
||||||
general: ProjectData;
|
general: ProjectData;
|
||||||
viewSettings: ViewSettings;
|
viewSettings: ViewSettings;
|
||||||
|
settings: Settings | undefined;
|
||||||
onAir: boolean;
|
onAir: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,6 +45,7 @@ const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
|
|||||||
const { data: rundownData } = useRundown();
|
const { data: rundownData } = useRundown();
|
||||||
const { data: project } = useProjectData();
|
const { data: project } = useProjectData();
|
||||||
const { data: viewSettings } = useViewSettings();
|
const { data: viewSettings } = useViewSettings();
|
||||||
|
const { data: settings } = useSettings();
|
||||||
|
|
||||||
const publicEvents = useMemo(() => {
|
const publicEvents = useMemo(() => {
|
||||||
if (Array.isArray(rundownData)) {
|
if (Array.isArray(rundownData)) {
|
||||||
@@ -104,6 +107,7 @@ const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
|
|||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
publicSelectedId={publicSelectedId}
|
publicSelectedId={publicSelectedId}
|
||||||
viewSettings={viewSettings}
|
viewSettings={viewSettings}
|
||||||
|
settings={settings}
|
||||||
nextId={nextId}
|
nextId={nextId}
|
||||||
general={project}
|
general={project}
|
||||||
onAir={onAir}
|
onAir={onAir}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import QRCode from 'react-qr-code';
|
import QRCode from 'react-qr-code';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { Message, OntimeEvent, ProjectData, SupportedEvent, ViewSettings } from 'ontime-types';
|
import { Message, OntimeEvent, ProjectData, Settings, SupportedEvent, ViewSettings } from 'ontime-types';
|
||||||
import { formatDisplay } from 'ontime-utils';
|
import { formatDisplay } from 'ontime-utils';
|
||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
@@ -11,7 +11,7 @@ import Schedule from '../../../common/components/schedule/Schedule';
|
|||||||
import { ScheduleProvider } from '../../../common/components/schedule/ScheduleContext';
|
import { ScheduleProvider } from '../../../common/components/schedule/ScheduleContext';
|
||||||
import ScheduleNav from '../../../common/components/schedule/ScheduleNav';
|
import ScheduleNav from '../../../common/components/schedule/ScheduleNav';
|
||||||
import TitleCard from '../../../common/components/title-card/TitleCard';
|
import TitleCard from '../../../common/components/title-card/TitleCard';
|
||||||
import { BACKSTAGE_OPTIONS } from '../../../common/components/view-params-editor/constants';
|
import { getBackstageOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||||
@@ -37,10 +37,12 @@ interface BackstageProps {
|
|||||||
selectedId: string | null;
|
selectedId: string | null;
|
||||||
general: ProjectData;
|
general: ProjectData;
|
||||||
viewSettings: ViewSettings;
|
viewSettings: ViewSettings;
|
||||||
|
settings: Settings | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Backstage(props: BackstageProps) {
|
export default function Backstage(props: BackstageProps) {
|
||||||
const { isMirrored, publ, eventNow, eventNext, time, backstageEvents, selectedId, general, viewSettings } = props;
|
const { isMirrored, publ, eventNow, eventNext, time, backstageEvents, selectedId, general, viewSettings, settings } =
|
||||||
|
props;
|
||||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const [blinkClass, setBlinkClass] = useState(false);
|
const [blinkClass, setBlinkClass] = useState(false);
|
||||||
@@ -89,11 +91,12 @@ export default function Backstage(props: BackstageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0);
|
const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0);
|
||||||
|
const backstageOptions = getBackstageOptions(settings?.timeFormat ?? '24');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
|
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
|
||||||
<NavigationMenu />
|
<NavigationMenu />
|
||||||
<ViewParamsEditor paramFields={BACKSTAGE_OPTIONS} />
|
<ViewParamsEditor paramFields={backstageOptions} />
|
||||||
<div className='project-header'>
|
<div className='project-header'>
|
||||||
{general.title}
|
{general.title}
|
||||||
<div className='clock-container'>
|
<div className='clock-container'>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { ViewSettings } from 'ontime-types';
|
import { Settings, ViewSettings } from 'ontime-types';
|
||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import { CLOCK_OPTIONS } from '../../../common/components/view-params-editor/constants';
|
import { getClockOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||||
@@ -18,6 +18,7 @@ interface ClockProps {
|
|||||||
isMirrored: boolean;
|
isMirrored: boolean;
|
||||||
time: TimeManagerType;
|
time: TimeManagerType;
|
||||||
viewSettings: ViewSettings;
|
viewSettings: ViewSettings;
|
||||||
|
settings: Settings | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatOptions = {
|
const formatOptions = {
|
||||||
@@ -26,7 +27,7 @@ const formatOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function Clock(props: ClockProps) {
|
export default function Clock(props: ClockProps) {
|
||||||
const { isMirrored, time, viewSettings } = props;
|
const { isMirrored, time, viewSettings, settings } = props;
|
||||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
@@ -124,6 +125,8 @@ export default function Clock(props: ClockProps) {
|
|||||||
const clock = formatTime(time.clock, formatOptions);
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
const clean = clock.replace('/:/g', '');
|
const clean = clock.replace('/:/g', '');
|
||||||
|
|
||||||
|
const clockOptions = getClockOptions(settings?.timeFormat ?? '24');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`clock-view ${isMirrored ? 'mirror' : ''}`}
|
className={`clock-view ${isMirrored ? 'mirror' : ''}`}
|
||||||
@@ -135,7 +138,7 @@ export default function Clock(props: ClockProps) {
|
|||||||
data-testid='clock-view'
|
data-testid='clock-view'
|
||||||
>
|
>
|
||||||
<NavigationMenu />
|
<NavigationMenu />
|
||||||
<ViewParamsEditor paramFields={CLOCK_OPTIONS} />
|
<ViewParamsEditor paramFields={clockOptions} />
|
||||||
<SuperscriptTime
|
<SuperscriptTime
|
||||||
time={clock}
|
time={clock}
|
||||||
className='clock'
|
className='clock'
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent, ViewSettings } from 'ontime-types';
|
import { OntimeEvent, OntimeRundownEntry, Playback, Settings, SupportedEvent, ViewSettings } from 'ontime-types';
|
||||||
import { formatDisplay } from 'ontime-utils';
|
import { formatDisplay } from 'ontime-utils';
|
||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import { TIME_FORMAT_OPTION } from '../../../common/components/view-params-editor/constants';
|
import { getTimeOption } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||||
@@ -34,10 +34,11 @@ interface CountdownProps {
|
|||||||
time: TimeManagerType;
|
time: TimeManagerType;
|
||||||
selectedId: string | null;
|
selectedId: string | null;
|
||||||
viewSettings: ViewSettings;
|
viewSettings: ViewSettings;
|
||||||
|
settings: Settings | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Countdown(props: CountdownProps) {
|
export default function Countdown(props: CountdownProps) {
|
||||||
const { isMirrored, backstageEvents, time, selectedId, viewSettings } = props;
|
const { isMirrored, backstageEvents, time, selectedId, viewSettings, settings } = props;
|
||||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
@@ -109,10 +110,12 @@ export default function Countdown(props: CountdownProps) {
|
|||||||
isSelected || runningMessage === TimerMessage.waiting,
|
isSelected || runningMessage === TimerMessage.waiting,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const timeOption = getTimeOption(settings?.timeFormat ?? '24');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
|
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
|
||||||
<NavigationMenu />
|
<NavigationMenu />
|
||||||
<ViewParamsEditor paramFields={[TIME_FORMAT_OPTION]} />
|
<ViewParamsEditor paramFields={[timeOption]} />
|
||||||
{follow === null ? (
|
{follow === null ? (
|
||||||
<CountdownSelect events={backstageEvents} />
|
<CountdownSelect events={backstageEvents} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import QRCode from 'react-qr-code';
|
import QRCode from 'react-qr-code';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { Message, OntimeEvent, ProjectData, ViewSettings } from 'ontime-types';
|
import { Message, OntimeEvent, ProjectData, Settings, ViewSettings } from 'ontime-types';
|
||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
@@ -9,7 +9,7 @@ import Schedule from '../../../common/components/schedule/Schedule';
|
|||||||
import { ScheduleProvider } from '../../../common/components/schedule/ScheduleContext';
|
import { ScheduleProvider } from '../../../common/components/schedule/ScheduleContext';
|
||||||
import ScheduleNav from '../../../common/components/schedule/ScheduleNav';
|
import ScheduleNav from '../../../common/components/schedule/ScheduleNav';
|
||||||
import TitleCard from '../../../common/components/title-card/TitleCard';
|
import TitleCard from '../../../common/components/title-card/TitleCard';
|
||||||
import { PUBLIC_OPTIONS } from '../../../common/components/view-params-editor/constants';
|
import { getPublicOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||||
@@ -35,11 +35,22 @@ interface BackstageProps {
|
|||||||
publicSelectedId: string | null;
|
publicSelectedId: string | null;
|
||||||
general: ProjectData;
|
general: ProjectData;
|
||||||
viewSettings: ViewSettings;
|
viewSettings: ViewSettings;
|
||||||
|
settings: Settings | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Public(props: BackstageProps) {
|
export default function Public(props: BackstageProps) {
|
||||||
const { isMirrored, publ, publicEventNow, publicEventNext, time, events, publicSelectedId, general, viewSettings } =
|
const {
|
||||||
props;
|
isMirrored,
|
||||||
|
publ,
|
||||||
|
publicEventNow,
|
||||||
|
publicEventNext,
|
||||||
|
time,
|
||||||
|
events,
|
||||||
|
publicSelectedId,
|
||||||
|
general,
|
||||||
|
viewSettings,
|
||||||
|
settings,
|
||||||
|
} = props;
|
||||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
|
|
||||||
@@ -56,10 +67,12 @@ export default function Public(props: BackstageProps) {
|
|||||||
const clock = formatTime(time.clock, formatOptions);
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
const qrSize = Math.max(window.innerWidth / 15, 128);
|
const qrSize = Math.max(window.innerWidth / 15, 128);
|
||||||
|
|
||||||
|
const publicOptions = getPublicOptions(settings?.timeFormat ?? '24');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`public-screen ${isMirrored ? 'mirror' : ''}`} data-testid='public-view'>
|
<div className={`public-screen ${isMirrored ? 'mirror' : ''}`} data-testid='public-view'>
|
||||||
<NavigationMenu />
|
<NavigationMenu />
|
||||||
<ViewParamsEditor paramFields={PUBLIC_OPTIONS} />
|
<ViewParamsEditor paramFields={publicOptions} />
|
||||||
<div className='project-header'>
|
<div className='project-header'>
|
||||||
{general.title}
|
{general.title}
|
||||||
<div className='clock-container'>
|
<div className='clock-container'>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import type { OntimeEvent, OntimeRundown, ViewSettings } from 'ontime-types';
|
import type { OntimeEvent, OntimeRundown, Settings, ViewSettings } from 'ontime-types';
|
||||||
import { SupportedEvent } from 'ontime-types';
|
import { SupportedEvent } from 'ontime-types';
|
||||||
import { formatDisplay } from 'ontime-utils';
|
import { formatDisplay } from 'ontime-utils';
|
||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import { STUDIO_CLOCK_OPTIONS } from '../../../common/components/view-params-editor/constants';
|
import { getStudioClockOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import useFitText from '../../../common/hooks/useFitText';
|
import useFitText from '../../../common/hooks/useFitText';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
@@ -32,10 +32,11 @@ interface StudioClockProps {
|
|||||||
nextId: string | null;
|
nextId: string | null;
|
||||||
onAir: boolean;
|
onAir: boolean;
|
||||||
viewSettings: ViewSettings;
|
viewSettings: ViewSettings;
|
||||||
|
settings: Settings | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StudioClock(props: StudioClockProps) {
|
export default function StudioClock(props: StudioClockProps) {
|
||||||
const { isMirrored, eventNext, time, backstageEvents, selectedId, nextId, onAir, viewSettings } = props;
|
const { isMirrored, eventNext, time, backstageEvents, selectedId, nextId, onAir, viewSettings, settings } = props;
|
||||||
|
|
||||||
// deferring rendering seems to affect styling (font and useFitText)
|
// deferring rendering seems to affect styling (font and useFitText)
|
||||||
useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
@@ -75,10 +76,12 @@ export default function StudioClock(props: StudioClockProps) {
|
|||||||
const secondsNow = secondsInMillis(time.clock);
|
const secondsNow = secondsInMillis(time.clock);
|
||||||
const isNegative = (time.current ?? 0) < 0;
|
const isNegative = (time.current ?? 0) < 0;
|
||||||
|
|
||||||
|
const studioClockOptions = getStudioClockOptions(settings?.timeFormat ?? '24');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`studio-clock ${isMirrored ? 'mirror' : ''}`} data-testid='studio-view'>
|
<div className={`studio-clock ${isMirrored ? 'mirror' : ''}`} data-testid='studio-view'>
|
||||||
<NavigationMenu />
|
<NavigationMenu />
|
||||||
<ViewParamsEditor paramFields={STUDIO_CLOCK_OPTIONS} />
|
<ViewParamsEditor paramFields={studioClockOptions} />
|
||||||
<div className='clock-container'>
|
<div className='clock-container'>
|
||||||
<div className={`studio-timer ${showSeconds ? 'studio-timer--with-seconds' : ''}`}>{clock}</div>
|
<div className={`studio-timer ${showSeconds ? 'studio-timer--with-seconds' : ''}`}>{clock}</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { Message, OntimeEvent, Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
|
import { Message, OntimeEvent, Playback, Settings, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
|
||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||||
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
|
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
|
||||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||||
import TitleCard from '../../../common/components/title-card/TitleCard';
|
import TitleCard from '../../../common/components/title-card/TitleCard';
|
||||||
import { TIMER_OPTIONS } from '../../../common/components/view-params-editor/constants';
|
import { getTimerOptions } from '../../../common/components/view-params-editor/constants';
|
||||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||||
@@ -48,10 +48,11 @@ interface TimerProps {
|
|||||||
eventNext: OntimeEvent | null;
|
eventNext: OntimeEvent | null;
|
||||||
time: TimeManagerType;
|
time: TimeManagerType;
|
||||||
viewSettings: ViewSettings;
|
viewSettings: ViewSettings;
|
||||||
|
settings: Settings | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Timer(props: TimerProps) {
|
export default function Timer(props: TimerProps) {
|
||||||
const { isMirrored, pres, eventNow, eventNext, time, viewSettings, external } = props;
|
const { isMirrored, pres, eventNow, eventNext, time, viewSettings, external, settings } = props;
|
||||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
@@ -128,10 +129,12 @@ export default function Timer(props: TimerProps) {
|
|||||||
const timerContainerClasses = `timer-container ${showBlinking ? (showOverlay ? '' : 'blink') : ''}`;
|
const timerContainerClasses = `timer-container ${showBlinking ? (showOverlay ? '' : 'blink') : ''}`;
|
||||||
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
|
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
|
||||||
|
|
||||||
|
const timerOptions = getTimerOptions(settings?.timeFormat ?? '24');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={showFinished ? `${baseClasses} stage-timer--finished` : baseClasses} data-testid='timer-view'>
|
<div className={showFinished ? `${baseClasses} stage-timer--finished` : baseClasses} data-testid='timer-view'>
|
||||||
<NavigationMenu />
|
<NavigationMenu />
|
||||||
<ViewParamsEditor paramFields={TIMER_OPTIONS} />
|
<ViewParamsEditor paramFields={timerOptions} />
|
||||||
{!userOptions.hideMessage && (
|
{!userOptions.hideMessage && (
|
||||||
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
||||||
<div className={`message ${showBlinking ? 'blink' : ''}`}>{pres.text}</div>
|
<div className={`message ${showBlinking ? 'blink' : ''}`}>{pres.text}</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "2.16.2",
|
"version": "2.21.3",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "ontime-server",
|
"name": "ontime-server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"version": "2.16.2",
|
"version": "2.21.3",
|
||||||
"exports": "./src/index.js",
|
"exports": "./src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.20.0",
|
"body-parser": "^1.20.0",
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
"express-static-gzip": "^2.1.7",
|
"express-static-gzip": "^2.1.7",
|
||||||
"express-validator": "^6.14.2",
|
"express-validator": "^6.14.2",
|
||||||
|
"got": "^14.0.0",
|
||||||
"lowdb": "^5.0.5",
|
"lowdb": "^5.0.5",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"node-osc": "^9.0.2",
|
"node-osc": "^9.0.2",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { LogOrigin, OSCSettings } from 'ontime-types';
|
|||||||
import { Server } from 'node-osc';
|
import { Server } from 'node-osc';
|
||||||
|
|
||||||
import { IAdapter } from './IAdapter.js';
|
import { IAdapter } from './IAdapter.js';
|
||||||
import { dispatchFromAdapter } from '../controllers/integrationController.js';
|
import { dispatchFromAdapter, type ChangeOptions } from '../controllers/integrationController.js';
|
||||||
import { logger } from '../classes/Logger.js';
|
import { logger } from '../classes/Logger.js';
|
||||||
|
|
||||||
export class OscServer implements IAdapter {
|
export class OscServer implements IAdapter {
|
||||||
@@ -36,12 +36,36 @@ export class OscServer implements IAdapter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let transformedPayload: unknown = args;
|
||||||
|
// we need to transform the params for the change endpoint
|
||||||
|
// OSC: ontime/change/{eventID}/{propertyName} value
|
||||||
|
if (path === 'change') {
|
||||||
|
if (params.length < 2) {
|
||||||
|
logger.error(LogOrigin.Rx, 'OSC IN: No params provided for change');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args === undefined) {
|
||||||
|
logger.error(LogOrigin.Rx, 'OSC IN: No valid payload provided for change');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventId = params[0];
|
||||||
|
const property = params[1];
|
||||||
|
const value: string | number | boolean = args as string | number | boolean;
|
||||||
|
|
||||||
|
transformedPayload = {
|
||||||
|
eventId,
|
||||||
|
property,
|
||||||
|
value,
|
||||||
|
} satisfies ChangeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const reply = dispatchFromAdapter(
|
const reply = dispatchFromAdapter(
|
||||||
path,
|
path,
|
||||||
{
|
{
|
||||||
payload: args,
|
payload: transformedPayload,
|
||||||
params,
|
|
||||||
},
|
},
|
||||||
'osc',
|
'osc',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
import { LogOrigin } from 'ontime-types';
|
import { LogOrigin } from 'ontime-types';
|
||||||
|
|
||||||
import { WebSocket, WebSocketServer } from 'ws';
|
import { WebSocket, WebSocketServer } from 'ws';
|
||||||
|
import type { Server } from 'http';
|
||||||
|
|
||||||
import getRandomName from '../utils/getRandomName.js';
|
import getRandomName from '../utils/getRandomName.js';
|
||||||
import { IAdapter } from './IAdapter.js';
|
import { IAdapter } from './IAdapter.js';
|
||||||
@@ -43,7 +44,7 @@ export class SocketServer implements IAdapter {
|
|||||||
this.wss = null;
|
this.wss = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
init(server) {
|
init(server: Server) {
|
||||||
this.wss = new WebSocketServer({ path: '/ws', server });
|
this.wss = new WebSocketServer({ path: '/ws', server });
|
||||||
|
|
||||||
this.wss.on('connection', (ws) => {
|
this.wss.on('connection', (ws) => {
|
||||||
|
|||||||
+22
-12
@@ -1,9 +1,9 @@
|
|||||||
import { LogOrigin, OSCSettings } from 'ontime-types';
|
import { HttpSettings, LogOrigin, OSCSettings } from 'ontime-types';
|
||||||
|
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import expressStaticGzip from 'express-static-gzip';
|
import expressStaticGzip from 'express-static-gzip';
|
||||||
import http from 'http';
|
import http, { type Server } from 'http';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
|
|
||||||
// import utils
|
// import utils
|
||||||
@@ -29,6 +29,7 @@ import { eventLoader } from './classes/event-loader/EventLoader.js';
|
|||||||
import { integrationService } from './services/integration-service/IntegrationService.js';
|
import { integrationService } from './services/integration-service/IntegrationService.js';
|
||||||
import { logger } from './classes/Logger.js';
|
import { logger } from './classes/Logger.js';
|
||||||
import { oscIntegration } from './services/integration-service/OscIntegration.js';
|
import { oscIntegration } from './services/integration-service/OscIntegration.js';
|
||||||
|
import { httpIntegration } from './services/integration-service/HttpIntegration.js';
|
||||||
import { populateStyles } from './modules/loadStyles.js';
|
import { populateStyles } from './modules/loadStyles.js';
|
||||||
import { eventStore, getInitialPayload } from './stores/EventStore.js';
|
import { eventStore, getInitialPayload } from './stores/EventStore.js';
|
||||||
import { PlaybackService } from './services/PlaybackService.js';
|
import { PlaybackService } from './services/PlaybackService.js';
|
||||||
@@ -107,8 +108,8 @@ enum OntimeStartOrder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let step = OntimeStartOrder.InitAssets;
|
let step = OntimeStartOrder.InitAssets;
|
||||||
let expressServer = null;
|
let expressServer: Server | null = null;
|
||||||
let oscServer = null;
|
let oscServer: OscServer | null = null;
|
||||||
|
|
||||||
const checkStart = (currentState: OntimeStartOrder) => {
|
const checkStart = (currentState: OntimeStartOrder) => {
|
||||||
if (step !== currentState) {
|
if (step !== currentState) {
|
||||||
@@ -165,7 +166,6 @@ export const startServer = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description starts OSC server
|
|
||||||
* @description starts OSC server
|
* @description starts OSC server
|
||||||
* @param overrideConfig
|
* @param overrideConfig
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
@@ -194,20 +194,30 @@ export const startOSCServer = async (overrideConfig = null) => {
|
|||||||
/**
|
/**
|
||||||
* starts integrations
|
* starts integrations
|
||||||
*/
|
*/
|
||||||
export const startIntegrations = async (config?: { osc: OSCSettings }) => {
|
export const startIntegrations = async (config?: { osc: OSCSettings; http: HttpSettings }) => {
|
||||||
checkStart(OntimeStartOrder.InitIO);
|
checkStart(OntimeStartOrder.InitIO);
|
||||||
|
|
||||||
const { osc } = config ?? DataProvider.getData();
|
const { osc, http } = config ?? DataProvider.getData();
|
||||||
|
|
||||||
if (!osc) {
|
if (!osc) {
|
||||||
return 'OSC Invalid configuration';
|
return 'OSC Invalid configuration';
|
||||||
|
} else {
|
||||||
|
const { success, message } = oscIntegration.init(osc);
|
||||||
|
logger.info(LogOrigin.Tx, message);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
integrationService.register(oscIntegration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (!http) {
|
||||||
|
return 'HTTP Invalid configuration';
|
||||||
|
} else {
|
||||||
|
const { success, message } = httpIntegration.init(http);
|
||||||
|
logger.info(LogOrigin.Tx, message);
|
||||||
|
|
||||||
const { success, message } = oscIntegration.init(osc);
|
if (success) {
|
||||||
logger.info(LogOrigin.Tx, message);
|
integrationService.register(httpIntegration);
|
||||||
|
}
|
||||||
if (success) {
|
|
||||||
integrationService.register(oscIntegration);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ export class DataProvider {
|
|||||||
return data.osc;
|
return data.osc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getHttp() {
|
||||||
|
return data.http;
|
||||||
|
}
|
||||||
|
|
||||||
static getAliases() {
|
static getAliases() {
|
||||||
return data.aliases;
|
return data.aliases;
|
||||||
}
|
}
|
||||||
@@ -94,6 +98,11 @@ export class DataProvider {
|
|||||||
await this.persist();
|
await this.persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async setHttp(newData) {
|
||||||
|
data.http = { ...newData };
|
||||||
|
await this.persist();
|
||||||
|
}
|
||||||
|
|
||||||
static getRundown() {
|
static getRundown() {
|
||||||
return [...data.rundown];
|
return [...data.rundown];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { OntimeEvent } from 'ontime-types';
|
|
||||||
|
|
||||||
import { messageService } from '../services/message-service/MessageService.js';
|
import { messageService } from '../services/message-service/MessageService.js';
|
||||||
import { PlaybackService } from '../services/PlaybackService.js';
|
import { PlaybackService } from '../services/PlaybackService.js';
|
||||||
import { eventStore } from '../stores/EventStore.js';
|
import { eventStore } from '../stores/EventStore.js';
|
||||||
@@ -7,19 +5,23 @@ import { parse, updateEvent } from './integrationController.config.js';
|
|||||||
import { isKeyOfType } from 'ontime-types/src/utils/guards.js';
|
import { isKeyOfType } from 'ontime-types/src/utils/guards.js';
|
||||||
import { event } from '../models/eventsDefinition.js';
|
import { event } from '../models/eventsDefinition.js';
|
||||||
|
|
||||||
|
export type ChangeOptions = {
|
||||||
|
eventId: string;
|
||||||
|
property: string;
|
||||||
|
value: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
//TODO: re-throwing the error does not add any extra information or value
|
//TODO: re-throwing the error does not add any extra information or value
|
||||||
export function dispatchFromAdapter(
|
export function dispatchFromAdapter(
|
||||||
type: string,
|
type: string,
|
||||||
args: {
|
args: {
|
||||||
payload: unknown;
|
payload: unknown;
|
||||||
params?: Array<string>;
|
|
||||||
},
|
},
|
||||||
_source?: 'osc' | 'ws',
|
_source?: 'osc' | 'ws',
|
||||||
) {
|
) {
|
||||||
const payload = args.payload;
|
const payload = args.payload;
|
||||||
const typeComponents = type.toLowerCase().split('/');
|
const typeComponents = type.toLowerCase().split('/');
|
||||||
const mainType = typeComponents[0];
|
const mainType = typeComponents[0];
|
||||||
const params = args.params || [];
|
|
||||||
|
|
||||||
switch (mainType) {
|
switch (mainType) {
|
||||||
case 'test-ontime': {
|
case 'test-ontime': {
|
||||||
@@ -267,21 +269,14 @@ export function dispatchFromAdapter(
|
|||||||
return { topic: 'timer', payload: timer };
|
return { topic: 'timer', payload: timer };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ontime/change/{eventID}/{propertyName}
|
// WS: {type: 'change', payload: { eventId, property, value } }
|
||||||
case 'change': {
|
case 'change': {
|
||||||
if (params.length < 2) {
|
const { eventId, property, value } = payload as ChangeOptions;
|
||||||
throw new Error('Too few parameters, 3 expected');
|
if (!isKeyOfType(property, event)) {
|
||||||
|
throw new Error(`Cannot update unknown event property ${property}`);
|
||||||
}
|
}
|
||||||
if (payload === undefined) {
|
const parsedPayload = parse(property, value);
|
||||||
throw new Error('No payload found');
|
return updateEvent(eventId, property, parsedPayload);
|
||||||
}
|
|
||||||
const eventID = params[0];
|
|
||||||
const propertyName = params[1] as keyof OntimeEvent;
|
|
||||||
if (!isKeyOfType(propertyName, event)) {
|
|
||||||
throw new Error(`Cannot update unknown event property ${propertyName}`);
|
|
||||||
}
|
|
||||||
const parsedPayload = parse(propertyName, payload);
|
|
||||||
return updateEvent(eventID, propertyName, parsedPayload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Alias, DatabaseModel, GetInfo, LogOrigin, ProjectData } from 'ontime-types';
|
import { LogOrigin } from 'ontime-types';
|
||||||
|
import type { Alias, DatabaseModel, GetInfo, HttpSettings, ProjectData } from 'ontime-types';
|
||||||
|
|
||||||
import { RequestHandler, Request, Response } from 'express';
|
import { RequestHandler, Request, Response } from 'express';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
@@ -11,11 +12,13 @@ import { PlaybackService } from '../services/PlaybackService.js';
|
|||||||
import { eventStore } from '../stores/EventStore.js';
|
import { eventStore } from '../stores/EventStore.js';
|
||||||
import { isDocker, pathToStartStyles, resolveDbPath } from '../setup.js';
|
import { isDocker, pathToStartStyles, resolveDbPath } from '../setup.js';
|
||||||
import { oscIntegration } from '../services/integration-service/OscIntegration.js';
|
import { oscIntegration } from '../services/integration-service/OscIntegration.js';
|
||||||
|
import { httpIntegration } from '../services/integration-service/HttpIntegration.js';
|
||||||
import { logger } from '../classes/Logger.js';
|
import { logger } from '../classes/Logger.js';
|
||||||
import { deleteAllEvents, notifyChanges } from '../services/rundown-service/RundownService.js';
|
import { deleteAllEvents, notifyChanges } from '../services/rundown-service/RundownService.js';
|
||||||
import { deepmerge } from 'ontime-utils';
|
import { deepmerge } from 'ontime-utils';
|
||||||
import { runtimeCacheStore } from '../stores/cachingStore.js';
|
import { runtimeCacheStore } from '../stores/cachingStore.js';
|
||||||
import { delayedRundownCacheKey } from '../services/rundown-service/delayedRundown.utils.js';
|
import { delayedRundownCacheKey } from '../services/rundown-service/delayedRundown.utils.js';
|
||||||
|
import { integrationService } from '../services/integration-service/IntegrationService.js';
|
||||||
|
|
||||||
// Create controller for GET request to '/ontime/poll'
|
// Create controller for GET request to '/ontime/poll'
|
||||||
// Returns data for current state
|
// Returns data for current state
|
||||||
@@ -283,15 +286,42 @@ export const getOSC = async (req, res) => {
|
|||||||
res.status(200).send(osc);
|
res.status(200).send(osc);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create controller for POST request to '/ontime/osc'
|
||||||
|
// Returns ACK message
|
||||||
|
export const postOSC = async (req, res) => {
|
||||||
|
if (failEmptyObjects(req.body, res)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const oscSettings = req.body;
|
||||||
|
await DataProvider.setOsc(oscSettings);
|
||||||
|
|
||||||
|
integrationService.unregister(oscIntegration);
|
||||||
|
|
||||||
|
// TODO: this update could be more granular, checking that relevant data was changed
|
||||||
|
const { success, message } = oscIntegration.init(oscSettings);
|
||||||
|
logger.info(LogOrigin.Tx, message);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
integrationService.register(oscIntegration);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(oscSettings).status(200);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).send({ message: error.toString() });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const postOscSubscriptions = async (req, res) => {
|
export const postOscSubscriptions = async (req, res) => {
|
||||||
if (failEmptyObjects(req.body, res)) {
|
if (failEmptyObjects(req.body, res)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const oscSubscriptions = req.body;
|
const subscriptions = req.body;
|
||||||
const oscSettings = DataProvider.getOsc();
|
const oscSettings = DataProvider.getOsc();
|
||||||
oscSettings.subscriptions = oscSubscriptions;
|
oscSettings.subscriptions = subscriptions;
|
||||||
await DataProvider.setOsc(oscSettings);
|
await DataProvider.setOsc(oscSettings);
|
||||||
|
|
||||||
// TODO: this update could be more granular, checking that relevant data was changed
|
// TODO: this update could be more granular, checking that relevant data was changed
|
||||||
@@ -304,22 +334,33 @@ export const postOscSubscriptions = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for POST request to '/ontime/osc'
|
// Create controller for GET request to '/ontime/http'
|
||||||
// Returns ACK message
|
export const getHTTP = async (_req, res: Response<HttpSettings>) => {
|
||||||
export const postOSC = async (req, res) => {
|
const http = DataProvider.getHttp();
|
||||||
|
res.status(200).send(http);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create controller for POST request to '/ontime/http'
|
||||||
|
export const postHTTP = async (req, res) => {
|
||||||
if (failEmptyObjects(req.body, res)) {
|
if (failEmptyObjects(req.body, res)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const oscSettings = req.body;
|
const httpSettings = req.body;
|
||||||
await DataProvider.setOsc(oscSettings);
|
await DataProvider.setHttp(httpSettings);
|
||||||
|
|
||||||
|
integrationService.unregister(httpIntegration);
|
||||||
|
|
||||||
// TODO: this update could be more granular, checking that relevant data was changed
|
// TODO: this update could be more granular, checking that relevant data was changed
|
||||||
const { message } = oscIntegration.init(oscSettings);
|
const { success, message } = httpIntegration.init(httpSettings);
|
||||||
logger.info(LogOrigin.Tx, message);
|
logger.info(LogOrigin.Tx, message);
|
||||||
|
|
||||||
res.send(oscSettings).status(200);
|
if (success) {
|
||||||
|
integrationService.register(httpIntegration);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(httpSettings).status(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send({ message: error.toString() });
|
res.status(400).send({ message: error.toString() });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { body, check, validationResult } from 'express-validator';
|
import { body, check, validationResult } from 'express-validator';
|
||||||
import { validateOscObject, validateOscSubscriptionEntry } from '../utils/parserFunctions.js';
|
import {
|
||||||
|
validateHttpSubscriptionObject,
|
||||||
|
validateOscSubscriptionObject,
|
||||||
|
validateOscSubscriptionCycle,
|
||||||
|
} from '../utils/parserFunctions.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Validates object for POST /ontime/views
|
* @description Validates object for POST /ontime/views
|
||||||
@@ -82,7 +86,22 @@ export const validateOSC = [
|
|||||||
body('enabledOut').exists().isBoolean(),
|
body('enabledOut').exists().isBoolean(),
|
||||||
body('subscriptions')
|
body('subscriptions')
|
||||||
.isObject()
|
.isObject()
|
||||||
.custom((value) => validateOscObject(value)),
|
.custom((value) => validateOscSubscriptionObject(value)),
|
||||||
|
(req, res, next) => {
|
||||||
|
const errors = validationResult(req);
|
||||||
|
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Validates object for POST /ontime/http
|
||||||
|
*/
|
||||||
|
export const validateHTTP = [
|
||||||
|
body('enabledOut').exists().isBoolean(),
|
||||||
|
body('subscriptions')
|
||||||
|
.isObject()
|
||||||
|
.custom((value) => validateHttpSubscriptionObject(value)),
|
||||||
(req, res, next) => {
|
(req, res, next) => {
|
||||||
const errors = validationResult(req);
|
const errors = validationResult(req);
|
||||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||||
@@ -96,22 +115,22 @@ export const validateOSC = [
|
|||||||
export const validateOscSubscription = [
|
export const validateOscSubscription = [
|
||||||
body('onLoad')
|
body('onLoad')
|
||||||
.isArray()
|
.isArray()
|
||||||
.custom((value) => validateOscSubscriptionEntry(value)),
|
.custom((value) => validateOscSubscriptionCycle(value)),
|
||||||
body('onStart')
|
body('onStart')
|
||||||
.isArray()
|
.isArray()
|
||||||
.custom((value) => validateOscSubscriptionEntry(value)),
|
.custom((value) => validateOscSubscriptionCycle(value)),
|
||||||
body('onPause')
|
body('onPause')
|
||||||
.isArray()
|
.isArray()
|
||||||
.custom((value) => validateOscSubscriptionEntry(value)),
|
.custom((value) => validateOscSubscriptionCycle(value)),
|
||||||
body('onStop')
|
body('onStop')
|
||||||
.isArray()
|
.isArray()
|
||||||
.custom((value) => validateOscSubscriptionEntry(value)),
|
.custom((value) => validateOscSubscriptionCycle(value)),
|
||||||
body('onUpdate')
|
body('onUpdate')
|
||||||
.isArray()
|
.isArray()
|
||||||
.custom((value) => validateOscSubscriptionEntry(value)),
|
.custom((value) => validateOscSubscriptionCycle(value)),
|
||||||
body('onFinish')
|
body('onFinish')
|
||||||
.isArray()
|
.isArray()
|
||||||
.custom((value) => validateOscSubscriptionEntry(value)),
|
.custom((value) => validateOscSubscriptionCycle(value)),
|
||||||
(req, res, next) => {
|
(req, res, next) => {
|
||||||
const errors = validationResult(req);
|
const errors = validationResult(req);
|
||||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||||
|
|||||||
@@ -57,4 +57,15 @@ export const dbModel: DatabaseModel = {
|
|||||||
onFinish: [],
|
onFinish: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
http: {
|
||||||
|
enabledOut: false,
|
||||||
|
subscriptions: {
|
||||||
|
onLoad: [],
|
||||||
|
onStart: [],
|
||||||
|
onPause: [],
|
||||||
|
onStop: [],
|
||||||
|
onUpdate: [],
|
||||||
|
onFinish: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
getAliases,
|
getAliases,
|
||||||
getInfo,
|
getInfo,
|
||||||
getOSC,
|
getOSC,
|
||||||
|
getHTTP,
|
||||||
getSettings,
|
getSettings,
|
||||||
getUserFields,
|
getUserFields,
|
||||||
getViewSettings,
|
getViewSettings,
|
||||||
@@ -19,16 +20,18 @@ import {
|
|||||||
postUserFields,
|
postUserFields,
|
||||||
postViewSettings,
|
postViewSettings,
|
||||||
previewExcel,
|
previewExcel,
|
||||||
|
postHTTP,
|
||||||
} from '../controllers/ontimeController.js';
|
} from '../controllers/ontimeController.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
validateAliases,
|
validateAliases,
|
||||||
validateOSC,
|
validateOSC,
|
||||||
validateOscSubscription,
|
|
||||||
validatePatchProjectFile,
|
validatePatchProjectFile,
|
||||||
validateSettings,
|
validateSettings,
|
||||||
validateUserFields,
|
validateUserFields,
|
||||||
viewValidator,
|
viewValidator,
|
||||||
|
validateHTTP,
|
||||||
|
validateOscSubscription,
|
||||||
} from '../controllers/ontimeController.validate.js';
|
} from '../controllers/ontimeController.validate.js';
|
||||||
import { projectSanitiser } from '../controllers/projectController.validate.js';
|
import { projectSanitiser } from '../controllers/projectController.validate.js';
|
||||||
|
|
||||||
@@ -85,5 +88,11 @@ router.post('/osc', validateOSC, postOSC);
|
|||||||
// create route between controller and '/ontime/osc-subscriptions' endpoint
|
// create route between controller and '/ontime/osc-subscriptions' endpoint
|
||||||
router.post('/osc-subscriptions', validateOscSubscription, postOscSubscriptions);
|
router.post('/osc-subscriptions', validateOscSubscription, postOscSubscriptions);
|
||||||
|
|
||||||
|
// create route between controller and '/ontime/http' endpoint
|
||||||
|
router.get('/http', getHTTP);
|
||||||
|
|
||||||
|
// create route between controller and '/ontime/http' endpoint
|
||||||
|
router.post('/http', validateHTTP, postHTTP);
|
||||||
|
|
||||||
// create route between controller and '/ontime/new' endpoint
|
// create route between controller and '/ontime/new' endpoint
|
||||||
router.post('/new', projectSanitiser, postNew);
|
router.post('/new', projectSanitiser, postNew);
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import got from 'got';
|
||||||
|
|
||||||
|
import { HttpSettings, HttpSubscription, HttpSubscriptionOptions, LogOrigin } from 'ontime-types';
|
||||||
|
|
||||||
|
import IIntegration, { TimerLifeCycleKey } from './IIntegration.js';
|
||||||
|
import { parseTemplateNested } from './integrationUtils.js';
|
||||||
|
import { dbModel } from '../../models/dataModel.js';
|
||||||
|
import { logger } from '../../classes/Logger.js';
|
||||||
|
import { validateHttpSubscriptionObject } from '../../utils/parserFunctions.js';
|
||||||
|
|
||||||
|
type Action = TimerLifeCycleKey | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Class contains logic towards outgoing HTTP communications
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
export class HttpIntegration implements IIntegration<HttpSubscriptionOptions> {
|
||||||
|
// protected httpAgent: null | http.Agent;
|
||||||
|
subscriptions: HttpSubscription;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// this.httpAgent = null;
|
||||||
|
this.subscriptions = dbModel.http.subscriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes httpClient
|
||||||
|
*/
|
||||||
|
init(config: HttpSettings) {
|
||||||
|
const { subscriptions, enabledOut } = config;
|
||||||
|
|
||||||
|
if (!enabledOut) {
|
||||||
|
// this.httpAgent?.destroy();
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'HTTP output disabled',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initSubscriptions(subscriptions);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// this allows re-calling the init function during runtime
|
||||||
|
// this.httpAgent?.destroy();
|
||||||
|
// this.httpAgent = new http.Agent({ keepAlive: true, timeout: 2000 });
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `HTTP integration client ready`,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `Failed initialising HTTP integration: ${error}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initSubscriptions(subscriptionOptions: HttpSubscription) {
|
||||||
|
if (validateHttpSubscriptionObject(subscriptionOptions)) {
|
||||||
|
this.subscriptions = { ...subscriptionOptions };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(action: Action, state?: object) {
|
||||||
|
// if (!this.httpAgent) {
|
||||||
|
// return {
|
||||||
|
// success: false,
|
||||||
|
// message: 'Client not initialised',
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!action) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'HTTP called with no action',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// check subscriptions for action
|
||||||
|
const eventSubscriptions = this.subscriptions?.[action] || [];
|
||||||
|
|
||||||
|
eventSubscriptions.forEach((sub) => {
|
||||||
|
const { enabled, message } = sub;
|
||||||
|
if (enabled && message) {
|
||||||
|
const parsedMessage = parseTemplateNested(message, state || {});
|
||||||
|
try {
|
||||||
|
const parsedUrl = new globalThis.URL(parsedMessage);
|
||||||
|
// if (parsedUrl.protocol != 'http:') {
|
||||||
|
// logger.error(LogOrigin.Tx, `HTTP Integration: Only HTTP allowed, got ${parsedUrl.protocol}`);
|
||||||
|
// return {
|
||||||
|
// success: false,
|
||||||
|
// message: `Only HTTP allowed, got ${parsedUrl.protocol}`,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
this.emit(parsedUrl);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(LogOrigin.Tx, `HTTP Integration: ${err}`);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `${err}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async emit(path: globalThis.URL) {
|
||||||
|
try {
|
||||||
|
await got.get(path, {
|
||||||
|
retry: { limit: 0 },
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(LogOrigin.Tx, `HTTP integration: ${err}`);
|
||||||
|
}
|
||||||
|
// http
|
||||||
|
// .get(path, { agent: this.httpAgent }, (res) => {
|
||||||
|
// if (res.statusCode !== 200) {
|
||||||
|
// logger.error(LogOrigin.Tx, `HTTP Error: ${res.statusCode}`);
|
||||||
|
// }
|
||||||
|
// res.resume();
|
||||||
|
// })
|
||||||
|
// .on('error', (err) => {
|
||||||
|
// logger.error(LogOrigin.Tx, `HTTP integration: ${err}`);
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdown() {
|
||||||
|
// if (this.httpAgent) {
|
||||||
|
// this.httpAgent?.destroy();
|
||||||
|
// this.httpAgent = null;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const httpIntegration = new HttpIntegration();
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { TimerLifeCycle, OscSubscription } from 'ontime-types';
|
import { TimerLifeCycle, Subscription } from 'ontime-types';
|
||||||
|
|
||||||
export type TimerLifeCycleKey = keyof typeof TimerLifeCycle;
|
export type TimerLifeCycleKey = keyof typeof TimerLifeCycle;
|
||||||
|
|
||||||
export default interface IIntegration {
|
export default interface IIntegration<T> {
|
||||||
subscriptions: OscSubscription;
|
subscriptions: Subscription<T>;
|
||||||
init: (config: unknown) => OperationReturn;
|
init: (config: unknown) => OperationReturn;
|
||||||
dispatch: (action: TimerLifeCycleKey, state?: object) => OperationReturn;
|
dispatch: (action: TimerLifeCycleKey, state?: object) => OperationReturn;
|
||||||
emit: (...args: unknown[]) => unknown;
|
emit: (...args: unknown[]) => unknown;
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import IIntegration, { TimerLifeCycleKey } from './IIntegration.js';
|
|||||||
import { eventStore } from '../../stores/EventStore.js';
|
import { eventStore } from '../../stores/EventStore.js';
|
||||||
|
|
||||||
class IntegrationService {
|
class IntegrationService {
|
||||||
private integrations: IIntegration[];
|
private integrations: IIntegration<unknown>[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.integrations = [];
|
this.integrations = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
register(integrationService: IIntegration) {
|
register(integrationService: IIntegration<unknown>) {
|
||||||
this.integrations.push(integrationService);
|
this.integrations.push(integrationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
unregister(integrationService: IIntegration) {
|
unregister(integrationService: IIntegration<unknown>) {
|
||||||
this.integrations = this.integrations.filter((int) => int !== integrationService);
|
this.integrations = this.integrations.filter((int) => int !== integrationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { ArgumentType, Client, Message } from 'node-osc';
|
import { ArgumentType, Client, Message } from 'node-osc';
|
||||||
import { OSCSettings, OscSubscription } from 'ontime-types';
|
import { OSCSettings, OscSubscription, OscSubscriptionOptions } from 'ontime-types';
|
||||||
|
|
||||||
import IIntegration, { TimerLifeCycleKey } from './IIntegration.js';
|
import IIntegration, { TimerLifeCycleKey } from './IIntegration.js';
|
||||||
import { parseTemplateNested } from './integrationUtils.js';
|
import { parseTemplateNested } from './integrationUtils.js';
|
||||||
import { isObject } from '../../utils/varUtils.js';
|
import { isObject } from '../../utils/varUtils.js';
|
||||||
import { dbModel } from '../../models/dataModel.js';
|
import { dbModel } from '../../models/dataModel.js';
|
||||||
import { validateOscObject } from '../../utils/parserFunctions.js';
|
import { validateOscSubscriptionObject } from '../../utils/parserFunctions.js';
|
||||||
|
|
||||||
type Action = TimerLifeCycleKey | string;
|
type Action = TimerLifeCycleKey | string;
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ type Action = TimerLifeCycleKey | string;
|
|||||||
* @description Class contains logic towards outgoing OSC communications
|
* @description Class contains logic towards outgoing OSC communications
|
||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
export class OscIntegration implements IIntegration {
|
export class OscIntegration implements IIntegration<OscSubscriptionOptions> {
|
||||||
protected oscClient: null | Client;
|
protected oscClient: null | Client;
|
||||||
subscriptions: OscSubscription;
|
subscriptions: OscSubscription;
|
||||||
|
|
||||||
@@ -26,7 +26,15 @@ export class OscIntegration implements IIntegration {
|
|||||||
* Initializes oscClient
|
* Initializes oscClient
|
||||||
*/
|
*/
|
||||||
init(config: OSCSettings) {
|
init(config: OSCSettings) {
|
||||||
const { targetIP, portOut, subscriptions } = config;
|
const { targetIP, portOut, subscriptions, enabledOut } = config;
|
||||||
|
|
||||||
|
if (!enabledOut) {
|
||||||
|
this.oscClient?.close();
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'OSC output disabled',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.initSubscriptions(subscriptions);
|
this.initSubscriptions(subscriptions);
|
||||||
|
|
||||||
@@ -58,7 +66,7 @@ export class OscIntegration implements IIntegration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initSubscriptions(subscriptionOptions: OscSubscription) {
|
initSubscriptions(subscriptionOptions: OscSubscription) {
|
||||||
if (validateOscObject(subscriptionOptions)) {
|
if (validateOscSubscriptionObject(subscriptionOptions)) {
|
||||||
this.subscriptions = { ...subscriptionOptions };
|
this.subscriptions = { ...subscriptionOptions };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
import { validateOscObject } from '../parserFunctions.ts';
|
|
||||||
|
|
||||||
test('validateOscSubscription()', () => {
|
|
||||||
it('should return true when given a valid OscSubscription', () => {
|
|
||||||
const validSubscription = {
|
|
||||||
onLoad: [{ id: '1', message: 'test', enabled: true }],
|
|
||||||
onStart: [{ id: '2', message: 'test', enabled: false }],
|
|
||||||
onPause: [{ id: '3', message: 'test', enabled: true }],
|
|
||||||
onStop: [{ id: '4', message: 'test', enabled: false }],
|
|
||||||
onUpdate: [{ id: '5', message: 'test', enabled: true }],
|
|
||||||
onFinish: [{ id: '6', message: 'test', enabled: false }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = validateOscObject(validSubscription);
|
|
||||||
|
|
||||||
expect(result).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when given undefined', () => {
|
|
||||||
const result = validateOscObject(undefined);
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when given null', () => {
|
|
||||||
const result = validateOscObject(null);
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when given an empty object', () => {
|
|
||||||
const result = validateOscObject({});
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when given an empty array', () => {
|
|
||||||
const result = validateOscObject([]);
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when given an object that is not an OscSubscription', () => {
|
|
||||||
const invalidObject = { foo: 'bar' };
|
|
||||||
|
|
||||||
const result = validateOscObject(invalidObject);
|
|
||||||
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when given an OscSubscription with a missing property', () => {
|
|
||||||
const invalidSubscription = {
|
|
||||||
onLoad: [{ id: '1', message: 'test', enabled: true }],
|
|
||||||
onStart: [{ id: '2', message: 'test', enabled: false }],
|
|
||||||
onPause: [{ id: '3', message: 'test', enabled: true }],
|
|
||||||
// Missing onStop
|
|
||||||
onUpdate: [{ id: '5', message: 'test', enabled: true }],
|
|
||||||
onFinish: [{ id: '6', message: 'test', enabled: false }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = validateOscObject(invalidSubscription);
|
|
||||||
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when given an OscSubscription with an invalid property value', () => {
|
|
||||||
const invalidSubscription = {
|
|
||||||
onLoad: [{ id: '1', message: 'test', enabled: true }],
|
|
||||||
onStart: [{ id: '2', message: 'test', enabled: false }],
|
|
||||||
onPause: [{ id: '3', message: 'test', enabled: true }],
|
|
||||||
onStop: [{ id: '4', message: 'test', enabled: false }],
|
|
||||||
onUpdate: [{ id: '5', message: 'test', enabled: true }],
|
|
||||||
onFinish: [{ id: '6', message: 'test', enabled: 'not a boolean' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = validateOscObject(invalidSubscription);
|
|
||||||
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if the message field is empty', () => {
|
|
||||||
const invalidSubscription = {
|
|
||||||
onLoad: [{ id: '1', message: 'test', enabled: true }],
|
|
||||||
onStart: [{ id: '2', message: '', enabled: false }],
|
|
||||||
onPause: [{ id: '3', message: '', enabled: true }],
|
|
||||||
onStop: [{ id: '4', message: 'test', enabled: false }],
|
|
||||||
onUpdate: [{ id: '5', message: 'test', enabled: true }],
|
|
||||||
onFinish: [{ id: '6', message: 'test', enabled: 'not a boolean' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = validateOscObject(invalidSubscription);
|
|
||||||
|
|
||||||
expect(result).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import { HttpSubscription, OscSubscription } from 'ontime-types';
|
||||||
|
import {
|
||||||
|
validateOscSubscriptionObject,
|
||||||
|
validateOscSubscriptionCycle,
|
||||||
|
validateHttpSubscriptionCycle,
|
||||||
|
validateHttpSubscriptionObject,
|
||||||
|
} from '../parserFunctions.js';
|
||||||
|
|
||||||
|
describe('validateOscSubscriptionCycle()', () => {
|
||||||
|
it('should return false when given an OscSubscription with an invalid property value', () => {
|
||||||
|
const invalidEntry = [{ message: 'test', enabled: 'not a boolean' }];
|
||||||
|
|
||||||
|
// @ts-expect-error -- since this comes from the client, we check things that typescript would have caught
|
||||||
|
const result = validateOscSubscriptionCycle(invalidEntry);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateOscSubscriptionObject()', () => {
|
||||||
|
it('should return true when given a valid OscSubscription', () => {
|
||||||
|
const validSubscription: OscSubscription = {
|
||||||
|
onLoad: [{ message: 'test', enabled: true }],
|
||||||
|
onStart: [{ message: 'test', enabled: false }],
|
||||||
|
onPause: [{ message: 'test', enabled: true }],
|
||||||
|
onStop: [{ message: 'test', enabled: false }],
|
||||||
|
onUpdate: [{ message: 'test', enabled: true }],
|
||||||
|
onFinish: [{ message: 'test', enabled: false }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = validateOscSubscriptionObject(validSubscription);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given undefined', () => {
|
||||||
|
const result = validateOscSubscriptionObject(undefined);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given null', () => {
|
||||||
|
const result = validateOscSubscriptionObject(null);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given an empty object', () => {
|
||||||
|
// @ts-expect-error -- since this comes from the client, we check things that typescript would have caught
|
||||||
|
const result = validateOscSubscriptionObject({});
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given an empty array', () => {
|
||||||
|
// @ts-expect-error -- since this comes from the client, we check things that typescript would have caught
|
||||||
|
const result = validateOscSubscriptionObject([]);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given an object that is not an OscSubscription', () => {
|
||||||
|
const invalidObject = { foo: 'bar' };
|
||||||
|
|
||||||
|
// @ts-expect-error -- since this comes from the client, we check things that typescript would have caught
|
||||||
|
const result = validateOscSubscriptionObject(invalidObject);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given an OscSubscription with a missing property', () => {
|
||||||
|
const invalidSubscription = {
|
||||||
|
onLoad: [{ message: 'test', enabled: true }],
|
||||||
|
onStart: [{ message: 'test', enabled: false }],
|
||||||
|
onPause: [{ message: 'test', enabled: true }],
|
||||||
|
// Missing onStop
|
||||||
|
onUpdate: [{ message: 'test', enabled: true }],
|
||||||
|
onFinish: [{ message: 'test', enabled: false }],
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error -- since this comes from the client, we check things that typescript would have caught
|
||||||
|
const result = validateOscSubscriptionObject(invalidSubscription);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateHttpSubscriptionCycle()', () => {
|
||||||
|
it('should return false when given an HttpSubscription with an invalid property value', () => {
|
||||||
|
const invalidBoolean = [{ message: 'http://', enabled: 'not a boolean' }];
|
||||||
|
const invalidHttp = [{ message: 'test', enabled: true }];
|
||||||
|
const noFtp = [{ message: 'ftp://test', enabled: true }];
|
||||||
|
const noEmpty = [{ message: '', enabled: true }];
|
||||||
|
|
||||||
|
// @ts-expect-error -- since this comes from the client, we check things that typescript would have caught
|
||||||
|
expect(validateHttpSubscriptionCycle(invalidBoolean)).toBe(false);
|
||||||
|
|
||||||
|
expect(validateHttpSubscriptionCycle(invalidHttp)).toBe(false);
|
||||||
|
expect(validateHttpSubscriptionCycle(noFtp)).toBe(false);
|
||||||
|
expect(validateHttpSubscriptionCycle(noEmpty)).toBe(false);
|
||||||
|
});
|
||||||
|
it('should return true when given an HttpSubscription matches definition', () => {
|
||||||
|
const validHttp = [{ message: 'http://', enabled: true }];
|
||||||
|
const validHttps = [{ message: 'https://', enabled: true }];
|
||||||
|
|
||||||
|
expect(validateHttpSubscriptionCycle(validHttp)).toBe(true);
|
||||||
|
expect(validateHttpSubscriptionCycle(validHttps)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validateHttpSubscriptionObject()', () => {
|
||||||
|
it('should return true when given a valid HttpSubscription', () => {
|
||||||
|
const validSubscription: HttpSubscription = {
|
||||||
|
onLoad: [{ message: 'http://', enabled: true }],
|
||||||
|
onStart: [{ message: 'http://', enabled: false }],
|
||||||
|
onPause: [{ message: 'http://', enabled: true }],
|
||||||
|
onStop: [{ message: 'http://', enabled: false }],
|
||||||
|
onUpdate: [{ message: 'http://', enabled: true }],
|
||||||
|
onFinish: [{ message: 'http://', enabled: false }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = validateHttpSubscriptionObject(validSubscription);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given undefined', () => {
|
||||||
|
const result = validateHttpSubscriptionObject(undefined);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given null', () => {
|
||||||
|
const result = validateHttpSubscriptionObject(null);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given an empty object', () => {
|
||||||
|
// @ts-expect-error -- since this comes from the client, we check things that typescript would have caught
|
||||||
|
const result = validateOscSubscriptionObject({});
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given an empty array', () => {
|
||||||
|
// @ts-expect-error -- since this comes from the client, we check things that typescript would have caught
|
||||||
|
const result = validateHttpSubscriptionObject([]);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given an object that is not an HttpSubscription', () => {
|
||||||
|
const invalidObject = { foo: 'bar' };
|
||||||
|
|
||||||
|
// @ts-expect-error -- since this comes from the client, we check things that typescript would have caught
|
||||||
|
const result = validateHttpSubscriptionObject(invalidObject);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when given an HttpSubscription with a missing property', () => {
|
||||||
|
const invalidSubscription = {
|
||||||
|
onLoad: [{ message: 'http://', enabled: true }],
|
||||||
|
onStart: [{ message: 'http://', enabled: false }],
|
||||||
|
onPause: [{ message: 'http://', enabled: true }],
|
||||||
|
// Missing onStop
|
||||||
|
onUpdate: [{ message: 'http://', enabled: true }],
|
||||||
|
onFinish: [{ message: 'http://', enabled: false }],
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-expect-error -- since this comes from the client, we check things that typescript would have caught
|
||||||
|
const result = validateHttpSubscriptionObject(invalidSubscription);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
parseAliases,
|
parseAliases,
|
||||||
parseProject,
|
parseProject,
|
||||||
parseOsc,
|
parseOsc,
|
||||||
|
parseHttp,
|
||||||
parseRundown,
|
parseRundown,
|
||||||
parseSettings,
|
parseSettings,
|
||||||
parseUserFields,
|
parseUserFields,
|
||||||
@@ -275,7 +276,7 @@ export const parseJson = async (jsonData): Promise<DatabaseModel | null> => {
|
|||||||
// Import OSC settings if any
|
// Import OSC settings if any
|
||||||
returnData.osc = parseOsc(jsonData) ?? dbModel.osc;
|
returnData.osc = parseOsc(jsonData) ?? dbModel.osc;
|
||||||
// Import HTTP settings if any
|
// Import HTTP settings if any
|
||||||
// returnData.http = parseHttp(jsonData, enforce);
|
returnData.http = parseHttp(jsonData) ?? dbModel.http;
|
||||||
|
|
||||||
return returnData as DatabaseModel;
|
return returnData as DatabaseModel;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,14 +2,17 @@ import { generateId } from 'ontime-utils';
|
|||||||
import {
|
import {
|
||||||
Alias,
|
Alias,
|
||||||
OntimeRundown,
|
OntimeRundown,
|
||||||
|
HttpSettings,
|
||||||
OSCSettings,
|
OSCSettings,
|
||||||
OscSubscription,
|
|
||||||
OscSubscriptionOptions,
|
|
||||||
ProjectData,
|
ProjectData,
|
||||||
Settings,
|
Settings,
|
||||||
TimerLifeCycle,
|
TimerLifeCycle,
|
||||||
UserFields,
|
UserFields,
|
||||||
ViewSettings,
|
ViewSettings,
|
||||||
|
OscSubscription,
|
||||||
|
HttpSubscription,
|
||||||
|
OscSubscriptionOptions,
|
||||||
|
HttpSubscriptionOptions,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
|
|
||||||
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
||||||
@@ -159,12 +162,12 @@ export const parseViewSettings = (data): ViewSettings => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses and validates subscription entry
|
* Parses and validates OSC subscription cycle options
|
||||||
* @param data
|
* @param data
|
||||||
*/
|
*/
|
||||||
export const validateOscSubscriptionEntry = (data: OscSubscriptionOptions): boolean => {
|
export const validateOscSubscriptionCycle = (data: OscSubscriptionOptions[]): boolean => {
|
||||||
for (const subscription in data) {
|
for (const subscriptionOption of data) {
|
||||||
if (typeof data[subscription].message !== 'string' || typeof data[subscription].enabled !== 'boolean') {
|
if (typeof subscriptionOption.message !== 'string' || typeof subscriptionOption.enabled !== 'boolean') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,22 +175,23 @@ export const validateOscSubscriptionEntry = (data: OscSubscriptionOptions): bool
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses and validates subscription object
|
* Parses and validates OSC subscription object
|
||||||
* @param data
|
* @param data
|
||||||
*/
|
*/
|
||||||
export const validateOscObject = (data: OscSubscription): boolean => {
|
export const validateOscSubscriptionObject = (data: OscSubscription): boolean => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timerKeys = Object.keys(TimerLifeCycle);
|
const timerKeys = Object.keys(TimerLifeCycle);
|
||||||
for (const key of timerKeys) {
|
for (const key of timerKeys) {
|
||||||
|
// must contains all keys and be an array
|
||||||
if (!(key in data) || !Array.isArray(data[key])) {
|
if (!(key in data) || !Array.isArray(data[key])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (const subscription of data[key]) {
|
const isValid = validateOscSubscriptionCycle(data[key]);
|
||||||
if (typeof subscription.message !== 'string' || typeof subscription.enabled !== 'boolean') {
|
if (!isValid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -200,8 +204,9 @@ export const parseOsc = (data: { osc?: Partial<OSCSettings> }): OSCSettings => {
|
|||||||
if ('osc' in data) {
|
if ('osc' in data) {
|
||||||
console.log('Found OSC definition, importing...');
|
console.log('Found OSC definition, importing...');
|
||||||
|
|
||||||
|
// TODO: this can be improved by only merging known keys
|
||||||
const loadedConfig = data.osc || {};
|
const loadedConfig = data.osc || {};
|
||||||
const validatedSubscriptions = validateOscObject(loadedConfig.subscriptions)
|
const validatedSubscriptions = validateOscSubscriptionObject(loadedConfig.subscriptions)
|
||||||
? loadedConfig.subscriptions
|
? loadedConfig.subscriptions
|
||||||
: dbModel.osc.subscriptions;
|
: dbModel.osc.subscriptions;
|
||||||
|
|
||||||
@@ -216,20 +221,64 @@ export const parseOsc = (data: { osc?: Partial<OSCSettings> }): OSCSettings => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and validates HTTP subscription cycle options
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export const validateHttpSubscriptionCycle = (data: HttpSubscriptionOptions[]): boolean => {
|
||||||
|
for (const subscriptionOption of data) {
|
||||||
|
const isHttp =
|
||||||
|
subscriptionOption.message?.startsWith('http://') || subscriptionOption.message?.startsWith('https://');
|
||||||
|
if (typeof subscriptionOption.message !== 'string' || !isHttp || typeof subscriptionOption.enabled !== 'boolean') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and validates HTTP subscription object
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export const validateHttpSubscriptionObject = (data: HttpSubscription): boolean => {
|
||||||
|
if (!data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const timerKeys = Object.keys(TimerLifeCycle);
|
||||||
|
// must contains all keys and be an array
|
||||||
|
for (const key of timerKeys) {
|
||||||
|
if (!(key in data) || !Array.isArray(data[key])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const isValid = validateHttpSubscriptionCycle(data[key]);
|
||||||
|
if (!isValid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse Http portion of an entry
|
* Parse Http portion of an entry
|
||||||
* @param {object} data - data object
|
* @param {object} data - data object
|
||||||
* @param {boolean} enforce - whether to create a definition if one is missing
|
* @param {boolean} enforce - whether to create a definition if one is missing
|
||||||
* @returns {object} - event object data
|
* @returns {object} - event object data
|
||||||
*/
|
*/
|
||||||
export const parseHttp = (data, enforce) => {
|
export const parseHttp = (data: { http?: Partial<HttpSettings> }): HttpSettings => {
|
||||||
const newHttp = {};
|
|
||||||
if ('http' in data) {
|
if ('http' in data) {
|
||||||
console.log('Found HTTP definition, importing...');
|
console.log('Found HTTP definition, importing...');
|
||||||
} else if (enforce) {
|
|
||||||
/* Not yet */
|
// TODO: this can be improved by only merging known keys
|
||||||
|
const loadedConfig = data?.http || {};
|
||||||
|
const validatedSubscriptions = validateHttpSubscriptionObject(loadedConfig.subscriptions)
|
||||||
|
? loadedConfig.subscriptions
|
||||||
|
: dbModel.http.subscriptions;
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabledOut: loadedConfig.enabledOut ?? dbModel.http.enabledOut,
|
||||||
|
subscriptions: validatedSubscriptions,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return newHttp;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -259,6 +259,17 @@
|
|||||||
"targetIP": "127.0.0.1",
|
"targetIP": "127.0.0.1",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
|
"http": {
|
||||||
|
"enabledOut": false,
|
||||||
|
"subscriptions": {
|
||||||
|
"onLoad": [],
|
||||||
|
"onStart": [],
|
||||||
|
"onPause": [],
|
||||||
|
"onStop": [],
|
||||||
|
"onUpdate": [],
|
||||||
|
"onFinish": []
|
||||||
|
}
|
||||||
|
},
|
||||||
"aliases": [
|
"aliases": [
|
||||||
{
|
{
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|||||||
@@ -148,5 +148,16 @@
|
|||||||
"onUpdate": [],
|
"onUpdate": [],
|
||||||
"onFinish": []
|
"onFinish": []
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"enabledOut": true,
|
||||||
|
"subscriptions": {
|
||||||
|
"onLoad": [],
|
||||||
|
"onStart": [],
|
||||||
|
"onPause": [],
|
||||||
|
"onStop": [],
|
||||||
|
"onUpdate": [],
|
||||||
|
"onFinish": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,5 +468,16 @@
|
|||||||
],
|
],
|
||||||
"onFinish": []
|
"onFinish": []
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"enabledOut": true,
|
||||||
|
"subscriptions": {
|
||||||
|
"onLoad": [],
|
||||||
|
"onStart": [],
|
||||||
|
"onPause": [],
|
||||||
|
"onStop": [],
|
||||||
|
"onUpdate": [],
|
||||||
|
"onFinish": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "2.16.2",
|
"version": "2.21.3",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"lighdev",
|
"lighdev",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { OSCSettings } from './core/OscSettings.type.js';
|
|||||||
import { Settings } from './core/Settings.type.js';
|
import { Settings } from './core/Settings.type.js';
|
||||||
import { UserFields } from './core/UserFields.type.js';
|
import { UserFields } from './core/UserFields.type.js';
|
||||||
import { ViewSettings } from './core/Views.type.js';
|
import { ViewSettings } from './core/Views.type.js';
|
||||||
|
import { HttpSettings } from '../index.js';
|
||||||
|
|
||||||
export type DatabaseModel = {
|
export type DatabaseModel = {
|
||||||
rundown: OntimeRundown;
|
rundown: OntimeRundown;
|
||||||
@@ -14,4 +15,5 @@ export type DatabaseModel = {
|
|||||||
aliases: Alias[];
|
aliases: Alias[];
|
||||||
userFields: UserFields;
|
userFields: UserFields;
|
||||||
osc: OSCSettings;
|
osc: OSCSettings;
|
||||||
|
http: HttpSettings;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Subscription } from './Subscription.type.js';
|
||||||
|
|
||||||
|
export type HttpSubscriptionOptions = { message: string; enabled: boolean };
|
||||||
|
export type HttpSubscription = Subscription<HttpSubscriptionOptions>;
|
||||||
|
|
||||||
|
export interface HttpSettings {
|
||||||
|
enabledOut: boolean;
|
||||||
|
subscriptions: HttpSubscription;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { TimerLifeCycleKey } from './TimerLifecycle.type.js';
|
import { Subscription } from './Subscription.type.js';
|
||||||
|
|
||||||
export type OscSubscriptionOptions = { message: string; enabled: boolean };
|
export type OscSubscriptionOptions = { message: string; enabled: boolean };
|
||||||
export type OscSubscription = { [key in TimerLifeCycleKey]: OscSubscriptionOptions[] };
|
export type OscSubscription = Subscription<OscSubscriptionOptions>;
|
||||||
|
|
||||||
export interface OSCSettings {
|
export interface OSCSettings {
|
||||||
portIn: number;
|
portIn: number;
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { TimerLifeCycleKey } from './TimerLifecycle.type.js';
|
||||||
|
|
||||||
|
export type Subscription<T> = { [key in TimerLifeCycleKey]: T[] };
|
||||||
@@ -28,10 +28,14 @@ export type { Alias } from './definitions/core/Alias.type.js';
|
|||||||
// ---> User Fields
|
// ---> User Fields
|
||||||
export type { UserFields } from './definitions/core/UserFields.type.js';
|
export type { UserFields } from './definitions/core/UserFields.type.js';
|
||||||
|
|
||||||
|
// ---> Integration, Subscription
|
||||||
|
export type { Subscription } from './definitions/core/Subscription.type.js';
|
||||||
|
|
||||||
// ---> OSC
|
// ---> OSC
|
||||||
export type { OSCSettings, OscSubscription, OscSubscriptionOptions } from './definitions/core/OscSettings.type.js';
|
export type { OSCSettings, OscSubscription, OscSubscriptionOptions } from './definitions/core/OscSettings.type.js';
|
||||||
|
|
||||||
// ---> HTTP
|
// ---> HTTP
|
||||||
|
export type { HttpSettings, HttpSubscription, HttpSubscriptionOptions } from './definitions/core/HttpSettings.type.js';
|
||||||
|
|
||||||
// SERVER RESPONSES
|
// SERVER RESPONSES
|
||||||
export type { NetworkInterface, GetInfo } from './api/ontime-controller/BackendResponse.type.js';
|
export type { NetworkInterface, GetInfo } from './api/ontime-controller/BackendResponse.type.js';
|
||||||
|
|||||||
Generated
+101
-21
@@ -270,6 +270,9 @@ importers:
|
|||||||
express-validator:
|
express-validator:
|
||||||
specifier: ^6.14.2
|
specifier: ^6.14.2
|
||||||
version: 6.14.2
|
version: 6.14.2
|
||||||
|
got:
|
||||||
|
specifier: ^14.0.0
|
||||||
|
version: 14.0.0
|
||||||
lowdb:
|
lowdb:
|
||||||
specifier: ^5.0.5
|
specifier: ^5.0.5
|
||||||
version: 5.0.5
|
version: 5.0.5
|
||||||
@@ -2572,6 +2575,11 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@sindresorhus/is@6.1.0:
|
||||||
|
resolution: {integrity: sha512-BuvU07zq3tQ/2SIgBsEuxKYDyDjC0n7Zir52bpHy2xnBbW81+po43aLFPLbeV3HRAheFbGud1qgcqSYfhtHMAg==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.20.12):
|
/@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.20.12):
|
||||||
resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==}
|
resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2704,6 +2712,13 @@ packages:
|
|||||||
defer-to-connect: 2.0.1
|
defer-to-connect: 2.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@szmarczak/http-timer@5.0.1:
|
||||||
|
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
dependencies:
|
||||||
|
defer-to-connect: 2.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@tanstack/eslint-plugin-query@5.8.4(eslint@8.53.0)(typescript@5.2.2):
|
/@tanstack/eslint-plugin-query@5.8.4(eslint@8.53.0)(typescript@5.2.2):
|
||||||
resolution: {integrity: sha512-KVgcMc+Bn1qbwkxYVWQoiVSNEIN4IAiLj3cUH/SAHT8m8E59Y97o8ON1syp0Rcw094ItG8pEVZFyQuOaH6PDgQ==}
|
resolution: {integrity: sha512-KVgcMc+Bn1qbwkxYVWQoiVSNEIN4IAiLj3cUH/SAHT8m8E59Y97o8ON1syp0Rcw094ItG8pEVZFyQuOaH6PDgQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2960,6 +2975,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
|
resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/http-cache-semantics@4.0.4:
|
||||||
|
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/istanbul-lib-coverage@2.0.4:
|
/@types/istanbul-lib-coverage@2.0.4:
|
||||||
resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
|
resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -3035,10 +3054,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==}
|
resolution: {integrity: sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/node@18.15.11:
|
|
||||||
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/parse-json@4.0.0:
|
/@types/parse-json@4.0.0:
|
||||||
resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
|
resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
|
||||||
|
|
||||||
@@ -3965,6 +3980,24 @@ packages:
|
|||||||
engines: {node: '>=10.6.0'}
|
engines: {node: '>=10.6.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/cacheable-lookup@7.0.0:
|
||||||
|
resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/cacheable-request@10.2.14:
|
||||||
|
resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
dependencies:
|
||||||
|
'@types/http-cache-semantics': 4.0.4
|
||||||
|
get-stream: 6.0.1
|
||||||
|
http-cache-semantics: 4.1.1
|
||||||
|
keyv: 4.5.4
|
||||||
|
mimic-response: 4.0.0
|
||||||
|
normalize-url: 8.0.0
|
||||||
|
responselike: 3.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cacheable-request@7.0.2:
|
/cacheable-request@7.0.2:
|
||||||
resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==}
|
resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4403,7 +4436,6 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dependencies:
|
dependencies:
|
||||||
mimic-response: 3.1.0
|
mimic-response: 3.1.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/deep-eql@4.1.3:
|
/deep-eql@4.1.3:
|
||||||
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
|
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
|
||||||
@@ -4469,7 +4501,6 @@ packages:
|
|||||||
/defer-to-connect@2.0.1:
|
/defer-to-connect@2.0.1:
|
||||||
resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
|
resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/define-lazy-prop@3.0.0:
|
/define-lazy-prop@3.0.0:
|
||||||
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
||||||
@@ -5412,6 +5443,11 @@ packages:
|
|||||||
is-callable: 1.2.7
|
is-callable: 1.2.7
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/form-data-encoder@4.0.2:
|
||||||
|
resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/form-data@4.0.0:
|
/form-data@4.0.0:
|
||||||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -5570,12 +5606,10 @@ packages:
|
|||||||
/get-stream@6.0.1:
|
/get-stream@6.0.1:
|
||||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/get-stream@8.0.1:
|
/get-stream@8.0.1:
|
||||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/get-symbol-description@1.0.0:
|
/get-symbol-description@1.0.0:
|
||||||
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
|
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
|
||||||
@@ -5682,6 +5716,23 @@ packages:
|
|||||||
responselike: 2.0.1
|
responselike: 2.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/got@14.0.0:
|
||||||
|
resolution: {integrity: sha512-X01vTgaX9SwaMq5DfImvS+3GMQFFs5HtrrlS9CuzUSzkxAf/tWGEyynuI+Qy7BjciMczZGjyVSmawYbP4eYhYA==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
dependencies:
|
||||||
|
'@sindresorhus/is': 6.1.0
|
||||||
|
'@szmarczak/http-timer': 5.0.1
|
||||||
|
cacheable-lookup: 7.0.0
|
||||||
|
cacheable-request: 10.2.14
|
||||||
|
decompress-response: 6.0.0
|
||||||
|
form-data-encoder: 4.0.2
|
||||||
|
get-stream: 8.0.1
|
||||||
|
http2-wrapper: 2.2.1
|
||||||
|
lowercase-keys: 3.0.0
|
||||||
|
p-cancelable: 4.0.1
|
||||||
|
responselike: 3.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/graceful-fs@4.2.11:
|
/graceful-fs@4.2.11:
|
||||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -5757,7 +5808,6 @@ packages:
|
|||||||
|
|
||||||
/http-cache-semantics@4.1.1:
|
/http-cache-semantics@4.1.1:
|
||||||
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
|
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/http-errors@2.0.0:
|
/http-errors@2.0.0:
|
||||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||||
@@ -5789,6 +5839,14 @@ packages:
|
|||||||
resolve-alpn: 1.2.1
|
resolve-alpn: 1.2.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/http2-wrapper@2.2.1:
|
||||||
|
resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==}
|
||||||
|
engines: {node: '>=10.19.0'}
|
||||||
|
dependencies:
|
||||||
|
quick-lru: 5.1.1
|
||||||
|
resolve-alpn: 1.2.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/https-proxy-agent@5.0.1:
|
/https-proxy-agent@5.0.1:
|
||||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -6284,7 +6342,6 @@ packages:
|
|||||||
|
|
||||||
/json-buffer@3.0.1:
|
/json-buffer@3.0.1:
|
||||||
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
|
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/json-parse-even-better-errors@2.3.1:
|
/json-parse-even-better-errors@2.3.1:
|
||||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||||
@@ -6345,7 +6402,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/lazy-val@1.0.5:
|
/lazy-val@1.0.5:
|
||||||
resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==}
|
resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==}
|
||||||
@@ -6464,6 +6520,11 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/lowercase-keys@3.0.0:
|
||||||
|
resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
|
||||||
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lru-cache@5.1.1:
|
/lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6601,7 +6662,11 @@ packages:
|
|||||||
/mimic-response@3.1.0:
|
/mimic-response@3.1.0:
|
||||||
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
|
||||||
|
/mimic-response@4.0.0:
|
||||||
|
resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==}
|
||||||
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/min-indent@1.0.1:
|
/min-indent@1.0.1:
|
||||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||||
@@ -6789,6 +6854,11 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/normalize-url@8.0.0:
|
||||||
|
resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/npm-run-path@4.0.1:
|
/npm-run-path@4.0.1:
|
||||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -6949,6 +7019,11 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/p-cancelable@4.0.1:
|
||||||
|
resolution: {integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/p-limit@3.1.0:
|
/p-limit@3.1.0:
|
||||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -7220,7 +7295,6 @@ packages:
|
|||||||
/quick-lru@5.1.1:
|
/quick-lru@5.1.1:
|
||||||
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
|
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/random-bytes@1.0.0:
|
/random-bytes@1.0.0:
|
||||||
resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==}
|
resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==}
|
||||||
@@ -7489,7 +7563,6 @@ packages:
|
|||||||
|
|
||||||
/resolve-alpn@1.2.1:
|
/resolve-alpn@1.2.1:
|
||||||
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/resolve-from@4.0.0:
|
/resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
@@ -7518,6 +7591,13 @@ packages:
|
|||||||
lowercase-keys: 2.0.0
|
lowercase-keys: 2.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/responselike@3.0.0:
|
||||||
|
resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
dependencies:
|
||||||
|
lowercase-keys: 3.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/restore-cursor@4.0.0:
|
/restore-cursor@4.0.0:
|
||||||
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
|
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
@@ -8525,7 +8605,7 @@ packages:
|
|||||||
- terser
|
- terser
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite-node@0.30.1(@types/node@18.15.11)(sass@1.57.1):
|
/vite-node@0.30.1(@types/node@16.18.23)(sass@1.57.1):
|
||||||
resolution: {integrity: sha512-vTikpU/J7e6LU/8iM3dzBo8ZhEiKZEKRznEMm+mJh95XhWaPrJQraT/QsT2NWmuEf+zgAoMe64PKT7hfZ1Njmg==}
|
resolution: {integrity: sha512-vTikpU/J7e6LU/8iM3dzBo8ZhEiKZEKRznEMm+mJh95XhWaPrJQraT/QsT2NWmuEf+zgAoMe64PKT7hfZ1Njmg==}
|
||||||
engines: {node: '>=v14.18.0'}
|
engines: {node: '>=v14.18.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -8535,7 +8615,7 @@ packages:
|
|||||||
mlly: 1.2.0
|
mlly: 1.2.0
|
||||||
pathe: 1.1.0
|
pathe: 1.1.0
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
vite: 4.3.1(@types/node@18.15.11)(sass@1.57.1)
|
vite: 4.3.1(@types/node@16.18.23)(sass@1.57.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- less
|
- less
|
||||||
@@ -8651,7 +8731,7 @@ packages:
|
|||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite@4.3.1(@types/node@18.15.11)(sass@1.57.1):
|
/vite@4.3.1(@types/node@16.18.23)(sass@1.57.1):
|
||||||
resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==}
|
resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -8676,7 +8756,7 @@ packages:
|
|||||||
terser:
|
terser:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 18.15.11
|
'@types/node': 16.18.23
|
||||||
esbuild: 0.17.5
|
esbuild: 0.17.5
|
||||||
postcss: 8.4.21
|
postcss: 8.4.21
|
||||||
rollup: 3.20.7
|
rollup: 3.20.7
|
||||||
@@ -8784,7 +8864,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 4.3.4
|
'@types/chai': 4.3.4
|
||||||
'@types/chai-subset': 1.3.3
|
'@types/chai-subset': 1.3.3
|
||||||
'@types/node': 18.15.11
|
'@types/node': 16.18.23
|
||||||
'@vitest/expect': 0.30.1
|
'@vitest/expect': 0.30.1
|
||||||
'@vitest/runner': 0.30.1
|
'@vitest/runner': 0.30.1
|
||||||
'@vitest/snapshot': 0.30.1
|
'@vitest/snapshot': 0.30.1
|
||||||
@@ -8806,8 +8886,8 @@ packages:
|
|||||||
strip-literal: 1.0.1
|
strip-literal: 1.0.1
|
||||||
tinybench: 2.4.0
|
tinybench: 2.4.0
|
||||||
tinypool: 0.4.0
|
tinypool: 0.4.0
|
||||||
vite: 4.3.1(@types/node@18.15.11)(sass@1.57.1)
|
vite: 4.3.1(@types/node@16.18.23)(sass@1.57.1)
|
||||||
vite-node: 0.30.1(@types/node@18.15.11)(sass@1.57.1)
|
vite-node: 0.30.1(@types/node@16.18.23)(sass@1.57.1)
|
||||||
why-is-node-running: 2.2.2
|
why-is-node-running: 2.2.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- less
|
- less
|
||||||
|
|||||||
Reference in New Issue
Block a user