From 110d3fb7a3ccc5953640d26bda3ec9d2cb6e1bc3 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Mon, 26 Dec 2022 12:58:33 +0100 Subject: [PATCH] v2 event timer (#274) * lint: cleanup unused * refactor: rename component to avoid conflict * style: gap in element row * refactor: rename playstate -> playback * refactor: cleanup usages of timer and prepare integration manager * style: re-arrange button order * refactor: improve event loading * feat(timer-service): hot reload * fix: issue with duration input * chore: cleanup debug * refactor: resolve poll from runtime store * refactor: cleanup merge * refactor: small improvements in timer hot-reload --- .../input/delay-input/DelayInput.tsx | 1 - .../{TimeInput.jsx => TimeInput.tsx} | 55 +-- client/src/common/hooks/useEventAction.ts | 1 - client/src/common/hooks/useSocket.ts | 5 - client/src/common/models/EventTypes.ts | 2 +- client/src/common/models/OntimeTypes.ts | 2 +- .../common/utils/__tests__/styleUtils.test.ts | 1 - client/src/common/utils/timesManager.ts | 14 +- .../control/playback/PlaybackButtons.tsx | 8 +- .../control/playback/PlaybackControl.tsx | 4 +- .../{Playback.tsx => PlaybackDisplay.tsx} | 15 +- .../control/playback/PlaybackTimer.tsx | 42 +-- .../control/playback/TapButton.module.scss | 4 +- .../features/control/playback/TapButton.tsx | 4 +- .../features/control/playback/Transport.tsx | 6 +- .../src/features/event-editor/EventEditor.tsx | 11 +- client/src/features/info/Info.module.scss | 1 + client/src/features/rundown/RundownEntry.tsx | 11 +- .../rundown/event-block/EventBlock.tsx | 8 +- .../EventBlockProgressBar.module.scss | 2 +- .../composite/EventBlockProgressBar.tsx | 4 +- .../composite/EventBlockTimers.jsx | 5 +- .../quick-add-block/QuickAddBlock.module.scss | 3 +- client/src/features/viewers/ViewWrapper.jsx | 6 +- .../features/viewers/countdown/Countdown.jsx | 2 +- .../viewers/countdown/countdown.helpers.js | 2 +- .../viewers/minimal-timer/MinimalTimer.tsx | 2 +- client/yarn.lock | 342 +----------------- server/main.js | 1 - server/src/app.js | 63 ++-- .../src/classes/data-provider/DataProvider.js | 4 +- .../src/classes/event-loader/EventLoader.js | 23 +- server/src/classes/socket/SocketController.js | 135 +++++-- server/src/controllers/ontimeController.js | 3 +- server/src/models/eventsDefinition.js | 2 +- server/src/services/TimerService.js | 272 ++++++++++++++ server/src/services/playbackService.js | 105 +++--- server/src/services/rundownService.js | 48 ++- server/src/stores/EventStore.js | 19 + 39 files changed, 637 insertions(+), 601 deletions(-) rename client/src/common/components/input/time-input/{TimeInput.jsx => TimeInput.tsx} (75%) rename client/src/features/control/playback/{Playback.tsx => PlaybackDisplay.tsx} (76%) create mode 100644 server/src/services/TimerService.js create mode 100644 server/src/stores/EventStore.js diff --git a/client/src/common/components/input/delay-input/DelayInput.tsx b/client/src/common/components/input/delay-input/DelayInput.tsx index 745639191..31828e017 100644 --- a/client/src/common/components/input/delay-input/DelayInput.tsx +++ b/client/src/common/components/input/delay-input/DelayInput.tsx @@ -33,7 +33,6 @@ export default function DelayInput(props: DelayInputProps) { */ const validate = useCallback( (newValue?: string) => { - console.log('debug', newValue, typeof newValue); if (newValue === '') setValue(0); const delayValue = clamp(Number(newValue), -60, 60); diff --git a/client/src/common/components/input/time-input/TimeInput.jsx b/client/src/common/components/input/time-input/TimeInput.tsx similarity index 75% rename from client/src/common/components/input/time-input/TimeInput.jsx rename to client/src/common/components/input/time-input/TimeInput.tsx index eca93dcec..3e813dd98 100644 --- a/client/src/common/components/input/time-input/TimeInput.jsx +++ b/client/src/common/components/input/time-input/TimeInput.tsx @@ -1,20 +1,31 @@ -import { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { KeyboardEvent, useCallback, useContext, useEffect, useRef, useState } from 'react'; import { Button, Input, InputGroup, InputLeftElement, Tooltip } from '@chakra-ui/react'; import { LoggingContext } from 'common/context/LoggingContext'; import { forgivingStringToMillis } from 'common/utils/dateConfig'; import { stringFromMillis } from 'common/utils/time'; -import PropTypes from 'prop-types'; +import { EventEditorSubmitActions } from '../../../../features/event-editor/EventEditor'; import { tooltipDelayFast } from '../../../../ontimeConfig'; +import { TimeEntryField } from '../../../utils/timesManager'; import style from './TimeInput.module.scss'; -export default function TimeInput(props) { +interface TimeInputProps { + name: TimeEntryField; + submitHandler: (field: EventEditorSubmitActions, value: number) => void; + time?: number; + delay?: number; + placeholder: string; + validationHandler: (entry: TimeEntryField, val: number) => boolean; + previousEnd?: number; +} + +export default function TimeInput(props: TimeInputProps) { const { - name, submitHandler, time = 0, delay, placeholder, validationHandler, previousEnd, + name, submitHandler, time = 0, delay = 0, placeholder, validationHandler, previousEnd = 0, } = props; const { emitError } = useContext(LoggingContext); - const inputRef = useRef(null); + const inputRef = useRef(null); const [value, setValue] = useState(''); /** @@ -25,7 +36,7 @@ export default function TimeInput(props) { try { setValue(stringFromMillis(time + delay)); } catch (error) { - emitError(`Unable to parse date: ${error.text}`); + emitError(`Unable to parse date: ${error}`); } }, [delay, emitError, time]); @@ -33,14 +44,14 @@ export default function TimeInput(props) { * @description Selects input text on focus */ const handleFocus = useCallback(() => { - inputRef.current.select(); + inputRef.current?.select(); }, []); /** * @description Submit handler * @param {string} newValue */ - const handleSubmit = useCallback((newValue) => { + const handleSubmit = useCallback((newValue: string) => { // Check if there is anything there if (newValue === '') { return false; @@ -82,7 +93,7 @@ export default function TimeInput(props) { * @description Prepare time fields * @param {string} value string to be parsed */ - const validateAndSubmit = useCallback((newValue) => { + const validateAndSubmit = useCallback((newValue: string) => { const success = handleSubmit(newValue); if (success) { const ms = forgivingStringToMillis(newValue); @@ -96,15 +107,15 @@ export default function TimeInput(props) { * @description Handles common keys for submit and cancel * @param {KeyboardEvent} event */ - const onKeyDownHandler = useCallback((event) => { + const onKeyDownHandler = useCallback((event:KeyboardEvent) => { if (event.key === 'Enter') { - inputRef.current.blur(); - validateAndSubmit(event.target.value); + inputRef.current?.blur(); + validateAndSubmit((event.target as HTMLInputElement).value); } else if (event.key === 'Tab') { - validateAndSubmit(event.target.value); + validateAndSubmit((event.target as HTMLInputElement).value); } if (event.key === 'Escape') { - inputRef.current.blur(); + inputRef.current?.blur(); resetValue(); } }, [resetValue, validateAndSubmit]); @@ -119,13 +130,15 @@ export default function TimeInput(props) { const ButtonInitial = () => { if (name === 'timeStart') return 'S'; if (name === 'timeEnd') return 'E'; - if (name === 'duration') return 'D'; + if (name === 'durationOverride') return 'D'; + return ''; }; const ButtonTooltip = () => { if (name === 'timeStart') return 'Start'; if (name === 'timeEnd') return 'End'; - if (name === 'duration') return 'Duration'; + if (name === 'durationOverride') return 'Duration'; + return ''; }; return ( @@ -162,13 +175,3 @@ export default function TimeInput(props) { ); } - -TimeInput.propTypes = { - name: PropTypes.string, - submitHandler: PropTypes.func, - time: PropTypes.number, - delay: PropTypes.number, - placeholder: PropTypes.string, - validationHandler: PropTypes.func, - previousEnd: PropTypes.number, -}; diff --git a/client/src/common/hooks/useEventAction.ts b/client/src/common/hooks/useEventAction.ts index 4b125bd33..0a1469231 100644 --- a/client/src/common/hooks/useEventAction.ts +++ b/client/src/common/hooks/useEventAction.ts @@ -71,7 +71,6 @@ export const useEventAction = () => { } if (applicationOptions.startTimeIsLastEnd && applicationOptions?.lastEventId) { - console.log('debug got here', applicationOptions.startTimeIsLastEnd, typeof applicationOptions.startTimeIsLastEnd); const rundown = queryClient.getQueryData(RUNDOWN_TABLE) as OntimeRundown; const previousEvent = rundown.find((event) => event.id === applicationOptions.lastEventId); if (typeof previousEvent !== 'undefined' && previousEvent.type === 'event') { diff --git a/client/src/common/hooks/useSocket.ts b/client/src/common/hooks/useSocket.ts index 33f7cced0..73feee62d 100644 --- a/client/src/common/hooks/useSocket.ts +++ b/client/src/common/hooks/useSocket.ts @@ -71,7 +71,6 @@ export const emptyPlaybackControl = { export const usePlaybackControl = createSocketHook(FEAT_PLAYBACKCONTROL, emptyPlaybackControl); export const resetPlayback = () => { const cacheData = queryClient.getQueryData([FEAT_PLAYBACKCONTROL]) as Record; - queryClient.setQueryData([FEAT_PLAYBACKCONTROL], { ...cacheData, playback: 'stop', @@ -84,19 +83,15 @@ export const setPlayback = { roll: () => socket.emit('set-roll'), previous: () => { socket.emit('set-previous'); - resetPlayback(); }, next: () => { socket.emit('set-next'); - resetPlayback(); }, stop: () => { socket.emit('set-stop'); - resetPlayback(); }, reload: () => { socket.emit('set-reload'); - resetPlayback(); }, delay: (amount: number) => { socket.emit('set-delay', amount); diff --git a/client/src/common/models/EventTypes.ts b/client/src/common/models/EventTypes.ts index b03da4f0f..7800279a5 100644 --- a/client/src/common/models/EventTypes.ts +++ b/client/src/common/models/EventTypes.ts @@ -26,9 +26,9 @@ export type OntimeEvent = OntimeBaseEvent & { subtitle: string, presenter: string, note: string, + timeType?: string, timeStart: number, timeEnd: number, - timeType?: string, duration: number, isPublic: boolean, skip: boolean, diff --git a/client/src/common/models/OntimeTypes.ts b/client/src/common/models/OntimeTypes.ts index 5e924b326..a2607c3ae 100644 --- a/client/src/common/models/OntimeTypes.ts +++ b/client/src/common/models/OntimeTypes.ts @@ -1,2 +1,2 @@ -export type Playstate = 'roll' | 'start' | 'pause' | 'stop'; +export type Playback = 'roll' | 'play' | 'pause' | 'stop' | 'armed'; export type TimeFormat = '12' | '24'; diff --git a/client/src/common/utils/__tests__/styleUtils.test.ts b/client/src/common/utils/__tests__/styleUtils.test.ts index a8db697da..ed1f90c5c 100644 --- a/client/src/common/utils/__tests__/styleUtils.test.ts +++ b/client/src/common/utils/__tests__/styleUtils.test.ts @@ -10,7 +10,6 @@ describe('cx()', () => { test('ignores falsy values', () => { const falsyStuff = false; const merged = cx([undefined, false, 0, null, falsyStuff ? style.test : null]); - console.log(merged) expect(merged).toMatchSnapshot(); }); }); diff --git a/client/src/common/utils/timesManager.ts b/client/src/common/utils/timesManager.ts index c95988055..e48fcbc34 100644 --- a/client/src/common/utils/timesManager.ts +++ b/client/src/common/utils/timesManager.ts @@ -35,19 +35,11 @@ export const handleTimeEntry = (field: TimeEntryField, val: number, timeStart: n export const validateEntry = (field: TimeEntryField, value: number, timeStart: number, timeEnd: number): { value: boolean, catch: string } => { const validate = { value: true, catch: '' }; - // 1. if one of times is not entered, anything goes - if (value == null || timeStart == null || timeEnd == null) return validate; - if (timeStart === 0) return validate; + const { start, end } = handleTimeEntry(field, value, timeStart, timeEnd); - // 2. find out what's what - const { start, end, durationOverride } = handleTimeEntry(field, value, timeStart, timeEnd); - if (durationOverride !== null) { - return validate; - } - - // 3. validation rules - if (start > end) { + if (end < start) { validate.catch = 'Start time later than end time'; } + return validate; }; diff --git a/client/src/features/control/playback/PlaybackButtons.tsx b/client/src/features/control/playback/PlaybackButtons.tsx index 5f1caec2b..8902ebd7a 100644 --- a/client/src/features/control/playback/PlaybackButtons.tsx +++ b/client/src/features/control/playback/PlaybackButtons.tsx @@ -1,10 +1,10 @@ -import { Playstate } from '../../../common/models/OntimeTypes'; +import { Playback } from '../../../common/models/OntimeTypes'; -import Playback from './Playback'; +import PlaybackDisplay from './PlaybackDisplay'; import Transport from './Transport'; interface PlaybackButtonsProps { - playback: Playstate; + playback: Playback; selectedId: string | null; noEvents: boolean; } @@ -13,7 +13,7 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) { const { playback, selectedId, noEvents } = props; return ( <> - setPlayback.start()} - disabled={!selectedId || isRolling || noEvents} - theme='start' + disabled={!selectedId || isRolling} + theme='play' active={isPlaying} > @@ -34,7 +35,7 @@ export default function Playback(props: PlaybackProps) { setPlayback.pause()} - disabled={!selectedId || isRolling || noEvents} + disabled={!selectedId || isRolling || isArmed} theme='pause' active={isPaused} > diff --git a/client/src/features/control/playback/PlaybackTimer.tsx b/client/src/features/control/playback/PlaybackTimer.tsx index a1543574d..d63e97471 100644 --- a/client/src/features/control/playback/PlaybackTimer.tsx +++ b/client/src/features/control/playback/PlaybackTimer.tsx @@ -2,7 +2,7 @@ import { Tooltip } from '@chakra-ui/react'; import TimerDisplay from 'common/components/countdown/TimerDisplay'; import { setPlayback, useTimer } from '../../../common/hooks/useSocket'; -import { Playstate } from '../../../common/models/OntimeTypes'; +import { Playback } from '../../../common/models/OntimeTypes'; import { millisToSeconds } from '../../../common/utils/dateConfig'; import { stringFromMillis } from '../../../common/utils/time'; import { tooltipDelayMid } from '../../../ontimeConfig'; @@ -12,7 +12,7 @@ import TapButton from './TapButton'; import style from './PlaybackControl.module.scss'; interface PlaybackTimerProps { - playback: Playstate; + playback: Playback; selectedId: string | null; } @@ -59,24 +59,6 @@ export default function PlaybackTimer(props: PlaybackTimerProps) { )}
- - setPlayback.delay(-5)} - disabled={disableButtons} - square> - -5 - - - - setPlayback.delay(+5)} - disabled={disableButtons} - square> - 5 - - setPlayback.delay(1)} disabled={disableButtons} square> - 1 + +1 + + + + setPlayback.delay(-5)} + disabled={disableButtons} + square> + -5 + + + + setPlayback.delay(+5)} + disabled={disableButtons} + square> + +5
diff --git a/client/src/features/control/playback/TapButton.module.scss b/client/src/features/control/playback/TapButton.module.scss index 838aa362d..6134ae132 100644 --- a/client/src/features/control/playback/TapButton.module.scss +++ b/client/src/features/control/playback/TapButton.module.scss @@ -14,7 +14,7 @@ $button-color-white: $gray-50; transition-duration: $transition-time-feedback; display: grid; place-content: center; - letter-spacing: 0.3px; + letter-spacing: 0.5px; background-color: $button-bg-gray; color: $theme-color; @@ -58,7 +58,7 @@ $button-color-white: $gray-50; } } -.tapButton.start { +.tapButton.play { @include tap-factory($playback-start); } diff --git a/client/src/features/control/playback/TapButton.tsx b/client/src/features/control/playback/TapButton.tsx index 6c0a18e35..fc447a1fd 100644 --- a/client/src/features/control/playback/TapButton.tsx +++ b/client/src/features/control/playback/TapButton.tsx @@ -1,6 +1,6 @@ import { ForwardedRef, forwardRef, PropsWithChildren } from 'react'; -import { Playstate } from '../../../common/models/OntimeTypes'; +import { Playback } from '../../../common/models/OntimeTypes'; import style from './TapButton.module.scss'; @@ -8,7 +8,7 @@ interface TapButtonProps { disabled?: boolean; square?: boolean; onClick: () => void; - theme?: Playstate | 'neutral'; + theme?: Playback | 'neutral'; active?: boolean; } diff --git a/client/src/features/control/playback/Transport.tsx b/client/src/features/control/playback/Transport.tsx index 88e38309a..d20431fa6 100644 --- a/client/src/features/control/playback/Transport.tsx +++ b/client/src/features/control/playback/Transport.tsx @@ -5,7 +5,7 @@ import { IoReload } from '@react-icons/all-files/io5/IoReload'; import { IoStop } from '@react-icons/all-files/io5/IoStop'; import { setPlayback } from '../../../common/hooks/useSocket'; -import { Playstate } from '../../../common/models/OntimeTypes'; +import { Playback } from '../../../common/models/OntimeTypes'; import { tooltipDelayMid } from '../../../ontimeConfig'; import TapButton from './TapButton'; @@ -13,7 +13,7 @@ import TapButton from './TapButton'; import style from './PlaybackControl.module.scss'; interface TransportProps { - playback: Playstate; + playback: Playback; selectedId: string | null; noEvents: boolean; } @@ -43,7 +43,7 @@ export default function Transport(props: TransportProps) { setPlayback.reload()} - disabled={!selectedId || noEvents} + disabled={!selectedId || isRolling} > diff --git a/client/src/features/event-editor/EventEditor.tsx b/client/src/features/event-editor/EventEditor.tsx index 0971f5e06..f7b6c8fc9 100644 --- a/client/src/features/event-editor/EventEditor.tsx +++ b/client/src/features/event-editor/EventEditor.tsx @@ -21,6 +21,7 @@ import style from './EventEditor.module.scss'; export type EventEditorSubmitActions = keyof OntimeEvent | 'durationOverride'; +// Todo: add previous end to TimeInput fields export default function EventEditor() { const [openId] = useAtom(editorEventId); const { data } = useRundown(); @@ -45,7 +46,7 @@ export default function EventEditor() { }, [data, event, openId]); const handleSubmit = useCallback( - (field: EventEditorSubmitActions, value: any) => { + (field: EventEditorSubmitActions, value: string | number) => { if (event === null) { return; } @@ -53,7 +54,8 @@ export default function EventEditor() { switch (field) { case 'durationOverride': { // duration defines timeEnd - newEventData.timeEnd = event.timeStart += value as number; + newEventData.duration = value as number; + newEventData.timeEnd = event.timeStart + (value as number); break; } case 'timeStart': { @@ -149,11 +151,10 @@ export default function EventEditor() { /> @@ -206,8 +207,8 @@ export default function EventEditor() {
diff --git a/client/src/features/rundown/event-block/composite/EventBlockProgressBar.module.scss b/client/src/features/rundown/event-block/composite/EventBlockProgressBar.module.scss index bf3bd48d5..9b803746d 100644 --- a/client/src/features/rundown/event-block/composite/EventBlockProgressBar.module.scss +++ b/client/src/features/rundown/event-block/composite/EventBlockProgressBar.module.scss @@ -10,7 +10,7 @@ transition: 1s linear; transition-property: width; - &.start { + &.play { background-color: $ontime-accent; } diff --git a/client/src/features/rundown/event-block/composite/EventBlockProgressBar.tsx b/client/src/features/rundown/event-block/composite/EventBlockProgressBar.tsx index efba8ef8c..619f17f57 100644 --- a/client/src/features/rundown/event-block/composite/EventBlockProgressBar.tsx +++ b/client/src/features/rundown/event-block/composite/EventBlockProgressBar.tsx @@ -1,11 +1,11 @@ import { useTimer } from '../../../../common/hooks/useSocket'; -import { Playstate } from '../../../../common/models/OntimeTypes'; +import { Playback } from '../../../../common/models/OntimeTypes'; import { clamp } from '../../../../common/utils/math'; import style from './EventBlockProgressBar.module.scss'; interface EventBlockProgressBarProps { - playback?: Playstate; + playback?: Playback; } export default function EventBlockProgressBar(props: EventBlockProgressBarProps) { diff --git a/client/src/features/rundown/event-block/composite/EventBlockTimers.jsx b/client/src/features/rundown/event-block/composite/EventBlockTimers.jsx index bd5bbaca3..51e8b5912 100644 --- a/client/src/features/rundown/event-block/composite/EventBlockTimers.jsx +++ b/client/src/features/rundown/event-block/composite/EventBlockTimers.jsx @@ -24,7 +24,7 @@ export default function EventBlockTimers(props) { const handleValidation = useCallback( (field, value) => { const valid = validateEntry(field, value, timeStart, timeEnd); - if (!valid.value) { + if (valid.catch) { emitWarning(`Time Input Warning: ${valid.catch}`); } return valid.value; @@ -60,11 +60,10 @@ export default function EventBlockTimers(props) { previousEnd={previousEnd} /> diff --git a/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss b/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss index 513a6389e..79593d77b 100644 --- a/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss +++ b/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss @@ -4,8 +4,9 @@ display: grid; grid-template-columns: 1fr auto; align-items: center; - margin: 4px 2px; + margin: 4px 0; font-size: 12px; + padding: 0 10px; } .btnRow { diff --git a/client/src/features/viewers/ViewWrapper.jsx b/client/src/features/viewers/ViewWrapper.jsx index 073e27390..8c72a3b3c 100644 --- a/client/src/features/viewers/ViewWrapper.jsx +++ b/client/src/features/viewers/ViewWrapper.jsx @@ -42,7 +42,7 @@ const withSocket = (Component) => { }); const [selectedId] = useSubscription('selected-id', null); const [nextId] = useSubscription('next-id', null); - const [playback] = useSubscription('playstate', null); + const [playback] = useSubscription('playback', null); // Ask for update on load useEffect(() => { @@ -107,8 +107,8 @@ const withSocket = (Component) => { // get clock string const TimeManagerType = { ...timer, - finished: playback === 'start' && timer.isNegative && timer.startedAt, - playstate: playback, + finished: playback === 'play' && timer.isNegative && timer.startedAt, + playback, }; // prevent render until we get all the data we need diff --git a/client/src/features/viewers/countdown/Countdown.jsx b/client/src/features/viewers/countdown/Countdown.jsx index 8cd1e5ace..c2742553b 100644 --- a/client/src/features/viewers/countdown/Countdown.jsx +++ b/client/src/features/viewers/countdown/Countdown.jsx @@ -77,7 +77,7 @@ export default function Countdown(props) { return null; } - const standby = time.playstate !== 'start' && selectedId === follow?.id; + const standby = time.playback !== 'start' && selectedId === follow?.id; const isRunningFinished = time.finished && runningMessage === timerMessages.running; const isSelected = runningMessage === timerMessages.running; const delayedTimerStyles = delay > 0 ? 'aux-timers__value--delayed' : ''; diff --git a/client/src/features/viewers/countdown/countdown.helpers.js b/client/src/features/viewers/countdown/countdown.helpers.js index b1c193175..14a5022f5 100644 --- a/client/src/features/viewers/countdown/countdown.helpers.js +++ b/client/src/features/viewers/countdown/countdown.helpers.js @@ -32,7 +32,7 @@ export const fetchTimerData = (time, follow, selectedId) => { if (selectedId === follow.id) { // check that is not running - message = time.playstate === 'pause' ? timerMessages.waiting : timerMessages.running; + message = time.playback === 'pause' ? timerMessages.waiting : timerMessages.running; timer = time.running; } else if (time.clock < follow.timeStart) { // if it hasnt started, we count to start diff --git a/client/src/features/viewers/minimal-timer/MinimalTimer.tsx b/client/src/features/viewers/minimal-timer/MinimalTimer.tsx index 7dce25e73..01b4e5aa1 100644 --- a/client/src/features/viewers/minimal-timer/MinimalTimer.tsx +++ b/client/src/features/viewers/minimal-timer/MinimalTimer.tsx @@ -124,7 +124,7 @@ export default function MinimalTimer(props: MinimalTimerProps) { userOptions.hideMessagesOverlay = Boolean(hideMessagesOverlay); const showOverlay = pres.text !== '' && pres.visible; - const isPlaying = time.playstate !== 'pause'; + const isPlaying = time.playback !== 'pause'; const timer = formatDisplay(time.running, true); const clean = timer.replace('/:/g', ''); const showFinished = time.isNegative && !userOptions?.hideOvertime; diff --git a/client/yarn.lock b/client/yarn.lock index 7c5bd88d6..2135e5cf5 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2108,14 +2108,6 @@ abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" @@ -2243,11 +2235,6 @@ aria-query@^5.0.0: resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - array-includes@^3.1.3: version "3.1.4" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" @@ -2348,24 +2335,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2396,11 +2365,6 @@ browserslist@^4.21.3: node-releases "^2.0.6" update-browserslist-db "^1.0.9" -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -2598,18 +2562,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - convert-source-map@^1.5.0, convert-source-map@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" @@ -2617,16 +2569,6 @@ convert-source-map@^1.5.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - cookie@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" @@ -2743,13 +2685,6 @@ data-urls@^3.0.2: whatwg-mimetype "^3.0.0" whatwg-url "^11.0.0" -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -2831,16 +2766,6 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - detect-node-es@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" @@ -2884,11 +2809,6 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - electron-to-chromium@^1.4.251: version "1.4.255" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.255.tgz#dc52d1095b876ed8acf25865db10265b02b1d6e1" @@ -2899,11 +2819,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - engine.io-client@~6.2.3: version "6.2.3" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.2.3.tgz#a8cbdab003162529db85e9de31575097f6d29458" @@ -3138,11 +3053,6 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -3339,11 +3249,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - execall@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45" @@ -3351,43 +3256,6 @@ execall@^2.0.0: dependencies: clone-regexp "^2.1.0" -express@^4.18.2: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.11.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -3440,19 +3308,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - find-root@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" @@ -3508,11 +3363,6 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - framer-motion@^7.5.3: version "7.6.12" resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-7.6.12.tgz#a1f228e00da03ceb78482ae4a7fba63be39d34e4" @@ -3541,11 +3391,6 @@ framesync@6.1.2: dependencies: tslib "2.4.0" -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -3826,17 +3671,6 @@ html-tags@^3.2.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961" integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg== -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" @@ -3854,13 +3688,6 @@ https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: agent-base "6" debug "4" -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -3909,7 +3736,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3935,11 +3762,6 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -4365,11 +4187,6 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - memoize-one@^5.1.1: version "5.2.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" @@ -4393,21 +4210,11 @@ meow@^9.0.0: type-fest "^0.18.0" yargs-parser "^20.2.3" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -4421,18 +4228,13 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -4466,21 +4268,11 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - nanoid@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" @@ -4491,11 +4283,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -4628,13 +4415,6 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -4723,11 +4503,6 @@ parse5@^7.0.0: dependencies: entities "^4.4.0" -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -4748,11 +4523,6 @@ path-parse@^1.0.6, path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -4899,14 +4669,6 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" @@ -4927,13 +4689,6 @@ qr.js@0.0.0: resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f" integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8= -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -4949,21 +4704,6 @@ raf-schd@^4.0.2: resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - react-beautiful-dnd@^13.1.1: version "13.1.1" resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2" @@ -4984,7 +4724,7 @@ react-clientside-effect@^1.2.6: dependencies: "@babel/runtime" "^7.12.13" -react-dom@^18.2.0: +react-dom@^18.1.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -5121,7 +4861,7 @@ react-test-renderer@^18.1.0: react-shallow-renderer "^16.15.0" scheduler "^0.22.0" -react@^18.2.0: +react@^18.1.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -5291,17 +5031,12 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -5360,45 +5095,11 @@ semver@^7.3.5: dependencies: lru-cache "^6.0.0" -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -5518,11 +5219,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -5845,11 +5541,6 @@ toggle-selection@^1.0.6: resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -5956,14 +5647,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - typeface-open-sans@^1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/typeface-open-sans/-/typeface-open-sans-1.1.13.tgz#32a09ebd7df59601e01ad81216f98ce641eeafd1" @@ -5999,11 +5682,6 @@ universalify@^0.1.2: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - unplugin@0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-0.10.1.tgz#e00dc951c1901aef4124121057102a8c290e28b3" @@ -6059,11 +5737,6 @@ util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - v8-compile-cache@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -6077,11 +5750,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - vite-plugin-svgr@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-2.2.2.tgz#c5c9cb573bf455bb079550531847ddc5d2e122af" diff --git a/server/main.js b/server/main.js index 7d85abef1..29cda8828 100644 --- a/server/main.js +++ b/server/main.js @@ -49,7 +49,6 @@ let tray = null; // Start OSC Server await startOSCServer(); } catch (error) { - console.log(error); loaded = error; } })(); diff --git a/server/src/app.js b/server/src/app.js index 183d7c8c0..8fc7324ab 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -1,33 +1,26 @@ -// get environment vars import 'dotenv/config'; - -// import config -import { config } from './config/config.js'; - -// import dependencies -import { dirname, join, resolve } from 'path'; - -// dependencies import express from 'express'; import http from 'http'; import cors from 'cors'; +// import utils +import { config } from './config/config.js'; +import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js'; +import { fileURLToPath } from 'url'; +import { ONTIME_VERSION } from './version.js'; +import { initSentry } from './modules/sentry.js'; +import { dirname, join, resolve } from 'path'; + // Import Routes import { router as rundownRouter } from './routes/rundownRouter.js'; import { router as eventRouter } from './routes/eventRouter.js'; import { router as ontimeRouter } from './routes/ontimeRouter.js'; import { router as playbackRouter } from './routes/playbackRouter.js'; -// Global Objects -import { EventTimer } from './classes/timer/EventTimer.js'; -import { socketProvider } from './classes/socket/SocketController.js'; - -// Start OSC server -import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js'; -import { fileURLToPath } from 'url'; +// Services import { DataProvider } from './classes/data-provider/DataProvider.js'; -import { ONTIME_VERSION } from './version.js'; -import { initSentry } from './modules/sentry.js'; +import { socketProvider } from './classes/socket/SocketController.js'; +import { eventTimer } from './services/TimerService.js'; // get environment const env = process.env.NODE_ENV || 'production'; @@ -65,7 +58,7 @@ app.use('/playback', playbackRouter); // serve static - css app.use('/external', express.static(join(__dirname, 'external'))); -// serve static - react, in test mode we fetch the react app from module +// serve static - react, in test mode we fetch the React app from module const resolvedPath = () => { const sameModule = '../'; const siblingModule = '../../'; @@ -74,6 +67,7 @@ const resolvedPath = () => { } return siblingModule; }; + app.use(express.static(join(__dirname, resolvedPath(), 'client/build'))); app.get('*', (req, res) => { @@ -81,7 +75,7 @@ app.get('*', (req, res) => { }); // Implement catch all -app.use((error, response, _next) => { +app.use((error, response) => { response.status(400).send('Unhandled request'); }); @@ -128,13 +122,10 @@ export const startOSCServer = async (overrideConfig = null) => { const server = http.createServer(app); /** - * @description Starts all necessary services - * @param overrideConfig + * Starts servers * @return {Promise} */ -export const startServer = async (overrideConfig = null) => { - const { http } = DataProvider.getData(); - +export const startServer = async () => { // Start server const returnMessage = `Ontime is listening on port ${serverPort}`; server.listen(serverPort, '0.0.0.0'); @@ -143,18 +134,24 @@ export const startServer = async (overrideConfig = null) => { await socket.initServer(server); socket.info('SERVER', 'Socket initialised'); + socket.info('SERVER', returnMessage); + socket.startListener(); + return returnMessage; +}; + +/** + * starts integrations + * @param overrideConfig + * @return {Promise} + */ +export const startIntegrations = async (overrideConfig = null) => { + const { http } = DataProvider.getData(); + // OSC Config const oscConfig = { ip: oscIP, port: overrideConfig?.port || oscOutPort, }; - - // init timer - global.timer = new EventTimer(socket, config.timer, oscConfig, http); - - socket.info('SERVER', returnMessage); - socket.startListener(); - return returnMessage; }; /** @@ -166,7 +163,7 @@ export const shutdown = async () => { server.close(); shutdownOSCServer(); - global.timer.shutdown(); + eventTimer.shutdown(); socket.shutdown(); }; diff --git a/server/src/classes/data-provider/DataProvider.js b/server/src/classes/data-provider/DataProvider.js index a03d2497a..393c770b1 100644 --- a/server/src/classes/data-provider/DataProvider.js +++ b/server/src/classes/data-provider/DataProvider.js @@ -42,7 +42,7 @@ export class DataProvider { await this.persist(); } - static getRundownLenght() { + static getRundownLength() { return data.rundown.length; } @@ -56,7 +56,6 @@ export class DataProvider { * @param entry * @param index * @return {Promise} - * @private */ static async insertEventAt(entry, index) { // get events @@ -91,7 +90,6 @@ export class DataProvider { * @param entry * @param id * @return {Promise} - * @private */ static async insertEventAfterId(entry, id) { const index = [...data.rundown].findIndex((event) => event.id === id); diff --git a/server/src/classes/event-loader/EventLoader.js b/server/src/classes/event-loader/EventLoader.js index 96566241d..d8552fca0 100644 --- a/server/src/classes/event-loader/EventLoader.js +++ b/server/src/classes/event-loader/EventLoader.js @@ -80,7 +80,7 @@ export class EventLoader { */ loadById(eventId) { const event = EventLoader.getEventWithId(eventId); - return this._loadEvent(event); + return this.loadEvent(event); } /** @@ -90,12 +90,12 @@ export class EventLoader { */ loadByIndex(eventIndex) { const event = EventLoader.getEventAtIndex(eventIndex); - return this._loadEvent(event); + return this.loadEvent(event); } /** - * finds the ID of the previous event - * @returns {{id: string}|null} + * finds the previous event + * @return {object | undefined} */ findPrevious() { const timedEvents = EventLoader.getPlayableEvents(); @@ -105,15 +105,16 @@ export class EventLoader { // if there is no event running, go to first if (this.selectedEventIndex === null) { - return { id: timedEvents[0].id }; + return timedEvents[0]; } + const newIndex = this.selectedEventIndex - 1; - return { id: timedEvents?.[newIndex].id }; + return timedEvents?.[newIndex]; } /** - * finds the ID of the next event - * @returns {{id: string}|null} + * finds the next event + * @return {object | undefined} */ findNext() { const timedEvents = EventLoader.getPlayableEvents(); @@ -127,10 +128,10 @@ export class EventLoader { // if there is no event running, go to first if (this.selectedEventIndex === null) { - return { id: timedEvents[0].id }; + return timedEvents[0]; } const newIndex = this.selectedEventIndex + 1; - return { id: timedEvents?.[newIndex].id }; + return timedEvents?.[newIndex]; } /** @@ -193,7 +194,7 @@ export class EventLoader { * loads an event given its id * @param {object} event */ - _loadEvent(event) { + loadEvent(event) { if (typeof event === 'undefined') { return null; } diff --git a/server/src/classes/socket/SocketController.js b/server/src/classes/socket/SocketController.js index f250e7365..ab3493229 100644 --- a/server/src/classes/socket/SocketController.js +++ b/server/src/classes/socket/SocketController.js @@ -8,6 +8,8 @@ import { messageManager } from '../message-manager/MessageManager.js'; import { PlaybackService } from '../../services/playbackService.js'; import { ADDRESS_MESSAGE_CONTROL } from './socketConfig.js'; +import { eventTimer } from '../../services/TimerService.js'; +import { EventLoader, eventLoader } from '../event-loader/EventLoader.js'; class SocketController { constructor() { @@ -61,12 +63,16 @@ class SocketController { // Todo: review in favour of features // send state - socket.emit('timer', global.timer.getTimeObject()); - socket.emit('playstate', global.timer.state); - socket.emit('selected-id', global.timer.selectedEventId); - socket.emit('next-id', global.timer.nextEventId); - socket.emit('publicselected-id', global.timer.selectedPublicEventId); - socket.emit('publicnext-id', global.timer.nextPublicEventId); + socket.emit('timer', eventTimer.timer); + socket.emit('playback', eventTimer.playback); + socket.emit('selected', { + id: eventLoader.selectedEventId, + index: eventLoader.selectedEventIndex, + total: eventLoader.numEvents, + }); + socket.emit('next-id', eventLoader.nextEventId); + socket.emit('publicselected-id', eventLoader.selectedPublicEventId); + socket.emit('publicnext-id', eventLoader.nextPublicEventId); /** * @description handle disconnecting a user @@ -167,16 +173,15 @@ class SocketController { // general playback state, useful for external sync // Todo: add delayed value (will come from rundownService) socket.on('ontime-poll', () => { - const timerPoll = global.timer.poll(); + const timerPoll = eventTimer.timer; const isDelayed = false; const colour = ''; socket.emit('ontime-poll', { isDelayed, colour, ...timerPoll }); }); /*******************************************/ - // playstate - socket.on('get-playstate', () => { - socket.emit('playstate', global.timer.state); + socket.on('get-playback', () => { + socket.emit('playback', eventTimer.playback); }); socket.on('get-onAir', () => { @@ -184,30 +189,20 @@ class SocketController { }); /*******************************************/ - // selection data socket.on('get-selected', () => { socket.emit('selected', { - id: global.timer.selectedEventId, - index: global.timer.selectedEventIndex, - total: global.timer._eventlist.length, + id: eventLoader.selectedEventId, + index: eventLoader.selectedEventIndex, + total: eventLoader.numEvents, }); }); - socket.on('get-selected-id', () => { - socket.emit('selected-id', global.timer.selectedEventId); - }); - - socket.on('get-next-id', () => { - socket.emit('next-id', global.timer.nextEventId); - }); - - // title data socket.on('get-titles', () => { - socket.emit('titles', global.timer.titles); + socket.emit('titles', eventLoader.titles); }); socket.on('get-publictitles', () => { - socket.emit('publictitles', global.timer.titlesPublic); + socket.emit('publictitles', eventLoader.titlesPublic); }); /***********************************/ @@ -294,33 +289,32 @@ class SocketController { // 1. RUNDOWN socket.on('get-feat-rundown', () => { - global.timer._broadcastFeatureRundown(); + this.broadcastFeatureRundown(); }); // 2. MESSAGE CONTROL socket.on('get-feat-messagecontrol', () => { - const featureData = messageManager.getAll(); - this.socket.emit(ADDRESS_MESSAGE_CONTROL, featureData); + this.broadcastFeatureMessageControl(); }); // 3. PLAYBACK CONTROL socket.on('get-feat-playbackcontrol', () => { - global.timer._broadcastFeaturePlaybackControl(); + this.broadcastFeaturePlaybackControl(); }); // 4. INFO socket.on('get-feat-info', () => { - global.timer._broadcastFeatureInfo(); + this.broadcastFeatureInfo(); }); // 5. CUE SHEET socket.on('get-feat-cuesheet', () => { - global.timer._broadcastFeatureCuesheet(); + this.broadcastFeatureCuesheet(); }); // 6. TIMER socket.on('get-ontime-timer', () => { - global.timer._broadcastFeatureTimer(); + this.broadcastTimer(); }); }); } @@ -363,6 +357,83 @@ class SocketController { } } + /** + * Broadcast data for Event List feature + */ + broadcastFeatureRundown() { + const featureData = { + selectedEventId: eventLoader.selectedEventId, + nextEventId: eventLoader.nextEventId, + playback: eventTimer.playback, + }; + this.send('feat-rundown', featureData); + } + + /** + * Broadcast data for Message Control feature + */ + broadcastFeatureMessageControl() { + const featureData = messageManager.getAll(); + this.send(ADDRESS_MESSAGE_CONTROL, featureData); + } + + /** + * Broadcast data for Playback Control feature + */ + broadcastFeaturePlaybackControl() { + const featureData = { + playback: eventTimer.playback, + selectedEventId: eventLoader.selectedEventId, + numEvents: EventLoader.getNumEvents(), + }; + this.send('feat-playbackcontrol', featureData); + } + + /** + * Broadcast data for Info feature + */ + broadcastFeatureInfo() { + const featureData = { + titles: eventLoader.titles, + playback: eventTimer.playback, + selectedEventId: eventLoader.selectedEventId, + selectedEventIndex: eventLoader.selectedEventIndex, + numEvents: EventLoader.getNumEvents(), + }; + this.send('feat-info', featureData); + } + + /** + * Broadcast data for Cuesheet feature + */ + broadcastFeatureCuesheet() { + const featureData = { + playback: eventTimer.playback, + selectedEventId: eventLoader.selectedEventId, + selectedEventIndex: eventLoader.selectedEventIndex, + numEvents: EventLoader.getNumEvents(), + titleNow: eventLoader.titles.titleNow, + }; + this.send('feat-cuesheet', featureData); + } + + /** + * Broadcast Timer feature + */ + broadcastTimer() { + const featureData = eventTimer.timer; + this.send('ontime-timer', featureData); + } + + broadcastState() { + this.broadcastFeatureRundown(); + this.broadcastFeatureMessageControl(); + this.broadcastFeaturePlaybackControl(); + this.broadcastFeatureInfo(); + this.broadcastFeatureCuesheet(); + this.broadcastTimer(); + } + /** * Sends a message with level LOG * @param {string} origin diff --git a/server/src/controllers/ontimeController.js b/server/src/controllers/ontimeController.js index e37603f04..fe68adf17 100644 --- a/server/src/controllers/ontimeController.js +++ b/server/src/controllers/ontimeController.js @@ -7,12 +7,13 @@ import { DataProvider } from '../classes/data-provider/DataProvider.js'; import { failEmptyObjects, failIsNotArray } from '../utils/routerUtils.js'; import { mergeObject } from '../utils/parserUtils.js'; import { PlaybackService } from '../services/playbackService.js'; +import { runtimeState } from '../stores/EventStore.js'; // Create controller for GET request to '/ontime/poll' // Returns data for current state export const poll = async (req, res) => { try { - const s = global.timer.poll(); + const s = runtimeState.poll(); res.status(200).send(s); } catch (error) { res.status(500).send({ diff --git a/server/src/models/eventsDefinition.js b/server/src/models/eventsDefinition.js index 60908c5a8..1eaec867c 100644 --- a/server/src/models/eventsDefinition.js +++ b/server/src/models/eventsDefinition.js @@ -3,9 +3,9 @@ export const event = { subtitle: '', presenter: '', note: '', + timeType: 'start-end', timeStart: 0, timeEnd: 0, - timeType: 'start-end', duration: 0, isPublic: false, skip: false, diff --git a/server/src/services/TimerService.js b/server/src/services/TimerService.js new file mode 100644 index 000000000..cf84921b1 --- /dev/null +++ b/server/src/services/TimerService.js @@ -0,0 +1,272 @@ +import { runtimeState } from '../stores/EventStore.js'; + +class TimerService { + /** + * @constructor + * @param {object} [timerConfig] + * @param {number} [timerConfig.refresh] + */ + constructor(timerConfig) { + this._clear(); + this._interval = setInterval(() => this.update(), timerConfig?.refresh || 1000); + } + + /** + * Get current time in ms from midnight + * @static + * @return {number} + */ + static getCurrentTime() { + const now = new Date(); + + // extract milliseconds since midnight + let elapsed = now.getHours() * 3600000; + elapsed += now.getMinutes() * 60000; + elapsed += now.getSeconds() * 1000; + elapsed += now.getMilliseconds(); + return elapsed; + } + + /** + * Returns expected time finish + * @private + */ + _getExpectedFinish() { + if (this.timer.startedAt === null) { + return null; + } + + if (this.timer.finishedAt) { + return this.timer.finishedAt; + } + + return Math.max( + this.timer.startedAt + this.timer.duration + this._pausedInterval + this.timer.addedTime, + this.timer.startedAt + ); + } + + /** + * Clears internal state + * @private + */ + _clear() { + this.playback = 'stop'; + this.timer = { + clock: TimerService.getCurrentTime(), + current: null, + elapsed: null, + expectedFinish: null, + addedTime: 0, + startedAt: null, + finishedAt: null, + secondaryTimer: null, + }; + this.loadedTimer = null; + this.loadedTimerId = null; + this._pausedInterval = 0; + this._pausedAt = null; + } + + /** + * Reloads information for currently running timer + * @param timer + */ + hotReload(timer) { + if (timer?.id !== this.loadedTimerId) { + return; + } + if (timer?.skip) { + this.stop(); + } + + // update relevant information and force update + this.loadedTimer = timer; + this.timer.duration = timer.duration; + if (this.timer.startedAt === null) { + this.timer.current = timer.duration; + } + this.update(); + } + + /** + * Loads given timer to object + * @param {object} timer + * @param {number} timer.id + * @param {number} timer.timeStart + * @param {number} timer.timeEnd + * @param {number} timer.duration + * @param {string} timer.timeType + * @param {boolean} timer.skip + */ + load(timer) { + if (timer.skip) { + throw new Error('Refuse load of skipped event'); + } + + this._clear(); + + this.loadedTimer = timer; + this.loadedTimerId = timer.id; + this.timer.duration = timer.duration; + this.timer.current = timer.duration; + this.playback = 'armed'; + this._pausedInterval = 0; + this._pausedAt = 0; + + this._onLoad(); + } + + /** + * Handles side effects related to onLoad event + * @private + */ + _onLoad() { + runtimeState.set('playback', this.playback); + runtimeState.set('ontime-timer', this.timer); + } + + start() { + if (!this.loadedTimerId) { + return; + } + + if (this.playback === 'play') { + return; + } + + this.timer.clock = TimerService.getCurrentTime(); + + // add paused time + if (this._pausedInterval) { + this.timer.addedTime += this._pausedInterval; + this._pausedAt = null; + this._pausedInterval = 0; + } else { + this.timer.startedAt = this.timer.clock; + } + + this.playback = 'play'; + this.timer.expectedFinish = this._getExpectedFinish(); + this._onStart(); + } + + /** + * Handles side effects related to onStart event + * @private + */ + _onStart() { + runtimeState.set('playback', this.playback); + runtimeState.set('ontime-timer', this.timer); + } + + pause() { + if (this.playback !== 'play') { + return; + } + + this.playback = 'pause'; + this.timer.clock = TimerService.getCurrentTime(); + this._pausedAt = this.timer.clock; + this._onPause(); + } + + _onPause() { + runtimeState.set('playback', this.playback); + runtimeState.set('ontime-timer', this.timer); + } + + stop() { + if (this.playback === 'stop') { + return; + } + + this._clear(); + this._onStop(); + } + + _onStop() { + runtimeState.set('playback', this.playback); + runtimeState.set('ontime-timer', this.timer); + } + + /** + * Delays running timer by given amount + * @param {number} amount + */ + delay(amount) { + if (!this.loadedTimerId) { + return; + } + + this.timer.addedTime += amount; + this.timer.current += amount; + this.timer.elapsed += amount; + + // handle edge cases + if (amount < 0 && Math.abs(amount) > this.timer.current) { + if (this.timer.finishedAt === null) { + // if we will make the clock negative + this.timer.finishedAt = TimerService.getCurrentTime(); + } + } else if (this.timer.current < 0 && this.timer.current + amount > 0) { + // clock will go from negative to positive + this.timer.finishedAt = null; + } + + // force an update + this.update(); + } + + update() { + this.timer.clock = TimerService.getCurrentTime(); + + // we only update timer if a timer has been started + if (this.timer.startedAt !== null) { + if (this.playback === 'pause') { + this._pausedInterval = this.timer.clock - this._pausedAt; + } + + this.timer.current = + this.timer.startedAt + + this.timer.duration + + this.timer.addedTime + + this._pausedInterval - + this.timer.clock; + this.timer.elapsed = this.timer.duration - this.timer.current; + + if (this.playback === 'play' && this.timer.current <= 0 && this.timer.finishedAt === null) { + this.timer.finishedAt = this.timer.clock; + this._onFinish(); + } else { + this.timer.finishedAt = null; + } + this.timer.expectedFinish = this._getExpectedFinish(); + } + this._onUpdate(); + } + + _onUpdate() { + runtimeState.set('playback', this.playback); + runtimeState.set('ontime-timer', this.timer); + } + + _onFinish() { + runtimeState.set('playback', this.playback); + runtimeState.set('ontime-timer', this.timer); + } + + roll() { + this._onRoll(); + } + + _onRoll() { + throw new Error('Roll not implemented'); + } + + shutdown() { + clearInterval(this._interval); + } +} + +export const eventTimer = new TimerService(); diff --git a/server/src/services/playbackService.js b/server/src/services/playbackService.js index 5d069b287..6b9907477 100644 --- a/server/src/services/playbackService.js +++ b/server/src/services/playbackService.js @@ -3,6 +3,7 @@ */ import { socketProvider } from '../classes/socket/SocketController.js'; import { eventLoader, EventLoader } from '../classes/event-loader/EventLoader.js'; +import { eventTimer } from './TimerService.js'; /** * Service manages playback status of app @@ -15,18 +16,18 @@ export class PlaybackService { * @return {boolean} success */ static loadEvent(event) { + let success = false; if (!event) { socketProvider.error('PLAYBACK', 'No event found'); - return false; - } - - if (event.skip) { + } else if (event.skip) { socketProvider.warning('PLAYBACK', `Refused playback of skipped event ID ${event.id}`); - return false; + } else { + eventLoader.loadEvent(event); + eventTimer.load(event); + success = true; } - global.timer.pause(); - global.timer.loadEvent(event); - return true; + socketProvider.broadcastState(); + return success; } /** @@ -93,8 +94,10 @@ export class PlaybackService { static loadPrevious() { const previousEvent = eventLoader.findPrevious(); if (previousEvent) { - PlaybackService.loadById(previousEvent.id); - global.timer.previous(); + const success = PlaybackService.loadEvent(previousEvent); + if (success) { + socketProvider.info('PLAYBACK', `Loaded event with ID ${previousEvent.id}`); + } } } @@ -104,8 +107,10 @@ export class PlaybackService { static loadNext() { const nextEvent = eventLoader.findNext(); if (nextEvent) { - PlaybackService.loadById(nextEvent.id); - global.timer.next(); + const success = PlaybackService.loadEvent(nextEvent); + if (success) { + socketProvider.info('PLAYBACK', `Loaded event with ID ${nextEvent.id}`); + } } } @@ -113,86 +118,72 @@ export class PlaybackService { * Starts playback on selected event */ static start() { - if (!eventLoader.selectedEventId) { - return; + if (eventLoader.selectedEventId) { + eventTimer.start(); + const newState = eventTimer.playback; + socketProvider.info('PLAYBACK', `Play Mode ${newState}`); } - const newState = global.timer.start(); - if (newState === 'start') { - socketProvider.info('PLAYBACK', 'Play Mode Start'); - } - socketProvider.send('playstate', newState); + socketProvider.broadcastState(); } /** * Pauses playback on selected event */ static pause() { - if (!eventLoader.selectedEventId) { - return; + if (eventLoader.selectedEventId) { + eventTimer.pause(); + const newState = eventTimer.playback; + socketProvider.info('PLAYBACK', `Play Mode ${newState}`); } - const newState = global.timer.pause(); - if (newState === 'pause') { - socketProvider.info('PLAYBACK', 'Play Mode Paused'); - } - socketProvider.send('playstate', newState); + socketProvider.broadcastState(); } /** * Stops timer and unloads any events */ static stop() { - if (!eventLoader.selectedEventId && global.timer.state !== 'roll') { - return; + if (eventLoader.selectedEventId || eventTimer.playback === 'roll') { + eventLoader.reset(); + eventTimer.stop(); + const newState = eventTimer.playback; + socketProvider.info('PLAYBACK', `Play Mode ${newState}`); } - eventLoader.reset(); - const newState = global.timer.stop(); - if (newState === 'stop') { - socketProvider.info('PLAYBACK', 'Play Mode Stopped'); - } - socketProvider.send('playstate', newState); + socketProvider.broadcastState(); } /** * Reloads current event */ static reload() { - if (!eventLoader.selectedEventId) { - return; + if (eventLoader.selectedEventId) { + this.loadById(eventLoader.selectedEventId); } - const newState = global.timer.reload(); - socketProvider.info('PLAYBACK', 'Reloaded event'); - socketProvider.send('playstate', newState); + socketProvider.broadcastState(); } /** * Sets playback to roll */ static roll() { - if (!EventLoader.getNumEvents()) { - return; + if (EventLoader.getNumEvents() && eventTimer.playback !== 'roll') { + eventTimer.roll(); + const newState = eventTimer.playback; + socketProvider.info('PLAYBACK', `Play Mode ${newState}`); + socketProvider.send('playback', newState); } - - if (global.timer.state === 'roll') { - return; - } - - const newState = global.timer.roll(); - if (newState === 'roll') { - socketProvider.info('PLAYBACK', 'Play Mode Roll'); - } - socketProvider.send('playstate', newState); + socketProvider.broadcastState(); } /** * Adds delay to current event - * @param {number} delayTime time in ms + * @param {number} delayTime time in minutes */ static setDelay(delayTime) { - if (!eventLoader.selectedEventId) { - return; + if (eventLoader.selectedEventId) { + const delayInMs = delayTime * 1000 * 60; + eventTimer.delay(delayInMs); + socketProvider.info('PLAYBACK', `Added ${delayTime} min delay`); } - const delayInMs = delayTime * 1000 * 60; - global.timer.increment(delayInMs); - socketProvider.info('PLAYBACK', `Added ${delayTime} min delay`); + socketProvider.broadcastState(); } } diff --git a/server/src/services/rundownService.js b/server/src/services/rundownService.js index 7691c063e..facc57a51 100644 --- a/server/src/services/rundownService.js +++ b/server/src/services/rundownService.js @@ -7,14 +7,20 @@ import { } from '../models/eventsDefinition.js'; import { MAX_EVENTS } from '../settings.js'; import { EventLoader, eventLoader } from '../classes/event-loader/EventLoader.js'; +import { eventTimer } from './TimerService.js'; +import { socketProvider } from '../classes/socket/SocketController.js'; +/** + * + * @param affectedIds + * @returns boolean + */ const affectedLoaded = (affectedIds) => { const now = eventLoader.selectedEventId; const nowPublic = eventLoader.selectedPublicEventId; const next = eventLoader.nextEventId; const nextPublic = eventLoader.nextPublicEventId; return ( - affectedIds.includes(now) || affectedIds.includes(now) || affectedIds.includes(nowPublic) || affectedIds.includes(next) || @@ -65,24 +71,41 @@ const isNewNext = () => { */ export function updateTimer(affectedIds) { const runningEventId = eventLoader.selectedEventId; + if (runningEventId === null) { return false; } // we need to reload in a few scenarios: // 1. we are not confident that changes do not affect running event - // 2. the edited event is currently being used (now or next) - // 3. the edited event replaces one of the previous (next) - if (typeof affectedIds === 'undefined') { - global.timer.syncLoaded(runningEventId); + const safeOption = typeof affectedIds === 'undefined'; + // 2. the edited event is in memory (now or next) running + const eventInMemory = safeOption ? false : affectedLoaded(affectedIds); + // 3. the edited event replaces next event + const isNext = isNewNext(); + + if (safeOption) { + eventLoader.reset(); + const loadedEvent = eventLoader.loadById(runningEventId); + eventTimer.hotReload(loadedEvent); return true; } - if (affectedLoaded(affectedIds)) { - global.timer.syncLoaded(runningEventId); + + if (eventInMemory) { + const loadedEvent = eventLoader.loadById(runningEventId); + if (!loadedEvent) { + // event was deleted + eventLoader.reset(); + eventTimer.stop(); + } else { + eventTimer.hotReload(loadedEvent); + } return true; } - if (isNewNext()) { - global.timer.syncLoaded(runningEventId); + + if (isNext) { + const loadedEvent = eventLoader.loadById(runningEventId); + eventTimer.hotReload(loadedEvent); return true; } return false; @@ -94,7 +117,7 @@ export function updateTimer(affectedIds) { * @return {unknown[]} */ export async function addEvent(eventData) { - const numEvents = DataProvider.getRundownLenght(); + const numEvents = DataProvider.getRundownLength(); if (numEvents > MAX_EVENTS) { throw new Error(`ERROR: Reached limit number of ${MAX_EVENTS} events`); } @@ -126,6 +149,7 @@ export async function addEvent(eventData) { throw new Error(error); } updateTimer([id]); + socketProvider.broadcastState(); return newEvent; } @@ -137,6 +161,7 @@ export async function editEvent(eventData) { } const newEvent = await DataProvider.updateEventById(eventId, eventData); updateTimer([eventId]); + socketProvider.broadcastState(); return newEvent; } @@ -148,6 +173,7 @@ export async function editEvent(eventData) { export async function deleteEvent(eventId) { await DataProvider.deleteEvent(eventId); updateTimer([eventId]); + socketProvider.broadcastState(); } /** @@ -157,6 +183,7 @@ export async function deleteEvent(eventId) { export async function deleteAllEvents() { await DataProvider.clearRundown(); updateTimer(); + socketProvider.broadcastState(); } /** @@ -237,4 +264,5 @@ export async function applyDelay(eventId) { // update rundown await DataProvider.setRundown(rundown); updateTimer(); + socketProvider.broadcastState(); } diff --git a/server/src/stores/EventStore.js b/server/src/stores/EventStore.js new file mode 100644 index 000000000..09bee4c3f --- /dev/null +++ b/server/src/stores/EventStore.js @@ -0,0 +1,19 @@ +import { socketProvider } from '../classes/socket/SocketController.js'; + +const store = {}; + +/** + * A runtime store that broadcasts its payload + */ +export const runtimeState = { + get(key) { + return store[key]; + }, + set(key, value) { + store[key] = value; + socketProvider.send(key, value); + }, + poll() { + return store; + }, +};