diff --git a/apps/client/src/__mocks__/QueryClient.mock.js b/apps/client/src/__mocks__/QueryClient.mock.js deleted file mode 100644 index 00a9653a2..000000000 --- a/apps/client/src/__mocks__/QueryClient.mock.js +++ /dev/null @@ -1,9 +0,0 @@ -import { QueryClient } from '@tanstack/react-query'; - -export const queryClientMock = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, -}); diff --git a/apps/client/src/appConstants.ts b/apps/client/src/appConstants.ts deleted file mode 100644 index a44531dca..000000000 --- a/apps/client/src/appConstants.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Exported viewer link location -const minimalLocation = 'minimal'; -const speakerLocation = 'speaker'; -const smLocation = 'sm'; -const publicLocation = 'public'; -const studioLocation = 'studio'; -const cuesheetLocation = 'cuesheet'; -const countdownLocation = 'countdown'; -const clockLocation = 'clock'; -const lowerLocation = 'lower'; - -export const viewerLocations = [ - { link: speakerLocation, label: 'Stage timer' }, - { link: clockLocation, label: 'Clock' }, - { link: minimalLocation, label: 'Minimal timer' }, - { link: smLocation, label: 'Backstage screen' }, - { link: publicLocation, label: 'Public screen' }, - { link: lowerLocation, label: 'Lower thirds' }, - { link: studioLocation, label: 'Studio clock' }, - { link: countdownLocation, label: 'Countdown' }, - { link: cuesheetLocation, label: 'Cuesheet' }, -]; diff --git a/apps/client/src/common/components/buttons/EnableBtn.jsx b/apps/client/src/common/components/buttons/EnableBtn.jsx deleted file mode 100644 index c9dace127..000000000 --- a/apps/client/src/common/components/buttons/EnableBtn.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Button } from '@chakra-ui/react'; -import { IoCheckmarkSharp } from '@react-icons/all-files/io5/IoCheckmarkSharp'; -import { IoCloseSharp } from '@react-icons/all-files/io5/IoCloseSharp'; -import PropTypes from 'prop-types'; - -export default function EnableBtn(props) { - const { active, text, actionHandler, size = 'xs' } = props; - return ( - - ); -} - -EnableBtn.propTypes = { - active: PropTypes.bool, - text: PropTypes.string, - actionHandler: PropTypes.func, - size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']), -} diff --git a/apps/client/src/common/components/buttons/PauseIconBtn.tsx b/apps/client/src/common/components/buttons/PauseIconBtn.tsx deleted file mode 100644 index 5bc581a6c..000000000 --- a/apps/client/src/common/components/buttons/PauseIconBtn.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { IconButton, Tooltip } from '@chakra-ui/react'; -import { IoPause } from '@react-icons/all-files/io5/IoPause'; - -import { tooltipDelayMid } from '../../../ontimeConfig'; - -interface PauseIconBtnProps { - clickhandler: (event: React.MouseEvent) => void; - active: boolean; - disabled: boolean; -} - -export default function PauseIconBtn(props: PauseIconBtnProps) { - const { clickhandler, active, disabled, ...rest } = props; - return ( - - } - colorScheme='orange' - variant={active ? 'solid' : 'outline'} - onClick={clickhandler} - width={120} - disabled={disabled} - aria-label='Pause playback' - {...rest} - /> - - ); -} diff --git a/apps/client/src/common/components/buttons/PublicIconBtn.jsx b/apps/client/src/common/components/buttons/PublicIconBtn.jsx deleted file mode 100644 index c9f9c0374..000000000 --- a/apps/client/src/common/components/buttons/PublicIconBtn.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { IconButton, Tooltip } from '@chakra-ui/react'; -import { FiUsers } from '@react-icons/all-files/fi/FiUsers'; -import PropTypes from 'prop-types'; - -export default function PublicIconBtn(props) { - const { actionHandler, active, size = 'xs', ...rest } = props; - return ( - - } - colorScheme='blue' - variant={active ? 'solid' : 'outline'} - onClick={() => actionHandler('update', { field: 'isPublic', value: !active })} - {...rest} - /> - - ); -} - -PublicIconBtn.propTypes = { - actionHandler: PropTypes.func, - active: PropTypes.bool, - size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']), -}; diff --git a/apps/client/src/common/components/buttons/RollIconBtn.jsx b/apps/client/src/common/components/buttons/RollIconBtn.jsx deleted file mode 100644 index aa78ea7ba..000000000 --- a/apps/client/src/common/components/buttons/RollIconBtn.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import { IconButton, Tooltip } from '@chakra-ui/react'; -import { IoTimeOutline } from '@react-icons/all-files/io5/IoTimeOutline'; -import PropTypes from 'prop-types'; - -import { tooltipDelayMid } from '../../../ontimeConfig'; - -export default function RollIconBtn(props) { - const { clickhandler, active, disabled, ...rest } = props; - return ( - - } - colorScheme='blue' - variant={active ? 'solid' : 'outline'} - onClick={clickhandler} - width={120} - disabled={disabled} - {...rest} - /> - - ); -} - -RollIconBtn.propTypes = { - clickhandler: PropTypes.func, - active: PropTypes.bool, - disabled: PropTypes.bool, -}; diff --git a/apps/client/src/common/components/buttons/StartIconBtn.jsx b/apps/client/src/common/components/buttons/StartIconBtn.jsx deleted file mode 100644 index 73479816f..000000000 --- a/apps/client/src/common/components/buttons/StartIconBtn.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import { IconButton, Tooltip } from '@chakra-ui/react'; -import { IoPlay } from '@react-icons/all-files/io5/IoPlay'; -import PropTypes from 'prop-types'; - -import { tooltipDelayMid } from '../../../ontimeConfig'; - -export default function StartIconBtn(props) { - const { clickhandler, active, disabled, ...rest } = props; - return ( - - } - colorScheme='green' - variant={active ? 'solid' : 'outline'} - onClick={clickhandler} - width={120} - disabled={disabled} - {...rest} - /> - - ); -} - -StartIconBtn.propTypes = { - clickhandler: PropTypes.func, - active: PropTypes.bool, - disabled: PropTypes.bool -} diff --git a/apps/client/src/common/components/buttons/TransportIconBtn.jsx b/apps/client/src/common/components/buttons/TransportIconBtn.jsx deleted file mode 100644 index d8b07e731..000000000 --- a/apps/client/src/common/components/buttons/TransportIconBtn.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { IconButton, Tooltip } from '@chakra-ui/react'; -import PropTypes from 'prop-types'; - -import { tooltipDelayMid } from '../../../ontimeConfig'; - -export default function TransportIconBtn(props) { - const { clickHandler, icon, tooltip, disabled, ...rest } = props; - return ( - - - - ); -} - -TransportIconBtn.propTypes = { - clickHandler: PropTypes.func, - icon: PropTypes.element, - tooltip: PropTypes.string, - disabled: PropTypes.bool, -}; diff --git a/apps/client/src/common/components/buttons/UnloadIconBtn.jsx b/apps/client/src/common/components/buttons/UnloadIconBtn.jsx deleted file mode 100644 index a6e1c1f2e..000000000 --- a/apps/client/src/common/components/buttons/UnloadIconBtn.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import { IconButton, Tooltip } from '@chakra-ui/react'; -import { IoStop } from '@react-icons/all-files/io5/IoStop'; -import PropTypes from 'prop-types'; - -import { tooltipDelayMid } from '../../../ontimeConfig'; - -export default function UnloadIconBtn(props) { - const { clickHandler, disabled, ...rest } = props; - return ( - - } - colorScheme='red' - variant='outline' - onClick={clickHandler} - width={90} - disabled={disabled} - {...rest} - /> - - ); -} - -UnloadIconBtn.propTypes = { - clickHandler: PropTypes.func, - disabled: PropTypes.bool, -}; diff --git a/apps/client/src/common/models/Alias.ts b/apps/client/src/common/models/Alias.ts deleted file mode 100644 index a5bd50985..000000000 --- a/apps/client/src/common/models/Alias.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Alias } from 'ontime-types'; - -export const aliasPlaceholder: Alias = { - enabled: false, - alias: '', - pathAndParams: '', -}; diff --git a/apps/client/src/common/models/OntimeVars.ts b/apps/client/src/common/models/OntimeVars.ts deleted file mode 100644 index 9b36bfa0d..000000000 --- a/apps/client/src/common/models/OntimeVars.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const ontimeVars = [ - { - name: '$timer', - description: 'Current running timer', - }, - { - name: '$title', - description: 'Current title', - }, - { - name: '$presenter', - description: 'Current timer', - }, - { - name: '$subtitle', - description: 'Current subtitle', - }, - { - name: '$next-title', - description: 'Next title', - }, - { - name: '$next-presenter', - description: 'Next timer', - }, - { - name: '$next-subtitle', - description: 'Next subtitle', - }, -]; diff --git a/apps/client/src/features/modals/integration-modal/OscIntegration.tsx b/apps/client/src/features/modals/integration-modal/OscIntegration.tsx index 6c2023bc6..2875e9aab 100644 --- a/apps/client/src/features/modals/integration-modal/OscIntegration.tsx +++ b/apps/client/src/features/modals/integration-modal/OscIntegration.tsx @@ -33,7 +33,7 @@ const sectionText: { [key in TimerLifeCycle]: { title: string; subtitle: string }, onUpdate: { title: 'On Every Second', - subtitle: 'Triggers when timers are updated (at least once a second, can be more)', + subtitle: 'Triggers when a running timer is updated (at least once a second, can be more)', }, onFinish: { title: 'On Finish', diff --git a/apps/client/src/features/modals/modalHelper.js b/apps/client/src/features/modals/modalHelper.js index cbb4e70b8..5b47f35de 100644 --- a/apps/client/src/features/modals/modalHelper.js +++ b/apps/client/src/features/modals/modalHelper.js @@ -2,10 +2,3 @@ export const inputProps = { size: 'sm', autoComplete: 'off', }; - -export const portInputProps = { - ...inputProps, - type: 'number', - min: '1024', - max: '65535', -}; diff --git a/apps/server/src/services/TimerService.ts b/apps/server/src/services/TimerService.ts index 6197ff239..96c985283 100644 --- a/apps/server/src/services/TimerService.ts +++ b/apps/server/src/services/TimerService.ts @@ -10,6 +10,8 @@ import { clock } from './Clock.js'; export class TimerService { private readonly _interval: NodeJS.Timer; + private _updateInterval: number; + private _lastUpdate: number | null; playback: Playback; timer: TimerState; @@ -23,10 +25,12 @@ export class TimerService { * @constructor * @param {object} [timerConfig] * @param {number} [timerConfig.refresh] + * @param {number} [timerConfig.updateInterval] */ - constructor(timerConfig?) { + constructor(timerConfig: { refresh?: number; updateInterval?: number } = {}) { this._clear(); - this._interval = setInterval(() => this.update(), timerConfig?.refresh || 1000); + this._interval = setInterval(() => this.update(), timerConfig?.refresh ?? 1000); + this._updateInterval = timerConfig?.updateInterval ?? 1000; } /** @@ -53,6 +57,8 @@ export class TimerService { this.pausedTime = 0; this.pausedAt = null; this.secondaryTarget = null; + + this._lastUpdate = null; } /** @@ -93,7 +99,7 @@ export class TimerService { if (this.timer.startedAt === null) { this.timer.current = timer.duration; } - this.update(); + this.update(true); } /** @@ -240,77 +246,95 @@ export class TimerService { } // force an update - this.update(); + this.update(true); } - update() { + private updateRoll() { + const tempCurrentTimer = { + selectedEventId: this.loadedTimerId, + current: this.timer.current, + // safeguard on midnight rollover + _finishAt: + this.timer.expectedFinish >= this.timer.startedAt + ? this.timer.expectedFinish + : this.timer.expectedFinish + DAY_TO_MS, + + clock: this.timer.clock, + secondaryTimer: this.timer.secondaryTimer, + secondaryTarget: this.secondaryTarget, + }; + const { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished } = updateRoll(tempCurrentTimer); + + this.timer.current = updatedTimer; + this.timer.secondaryTimer = updatedSecondaryTimer; + this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock); + + if (isFinished) { + this.timer.selectedEventId = null; + this.loadedTimerId = null; + this._onFinish(); + } + + // to load the next event we have to escalate to parent service + if (doRollLoad) { + PlaybackService.roll(); + } + } + + private updatePlay() { + if (this.playback === Playback.Pause) { + this.pausedTime = this.timer.clock - this.pausedAt; + } + + if (this.playback === Playback.Play && this.timer.current <= 0 && this.timer.finishedAt === null) { + this.timer.finishedAt = this.timer.clock; + this._onFinish(); + } else { + this.timer.expectedFinish = getExpectedFinish( + this.timer.startedAt, + this.timer.finishedAt, + this.timer.duration, + this.pausedTime, + this.timer.addedTime, + ); + } + this.timer.current = getCurrent( + this.timer.startedAt, + this.timer.duration, + this.timer.addedTime, + this.pausedTime, + this.timer.clock, + ); + this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock); + } + + update(force = false) { this.timer.clock = clock.timeNow(); + // we call integrations if we update timers + let shouldNotify = false; if (this.playback === Playback.Roll) { - const tempCurrentTimer = { - selectedEventId: this.loadedTimerId, - current: this.timer.current, - // safeguard on midnight rollover - _finishAt: - this.timer.expectedFinish >= this.timer.startedAt - ? this.timer.expectedFinish - : this.timer.expectedFinish + DAY_TO_MS, - - clock: this.timer.clock, - secondaryTimer: this.timer.secondaryTimer, - secondaryTarget: this.secondaryTarget, - }; - const { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished } = updateRoll(tempCurrentTimer); - - this.timer.current = updatedTimer; - this.timer.secondaryTimer = updatedSecondaryTimer; - this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock); - - if (isFinished) { - this.timer.selectedEventId = null; - this.loadedTimerId = null; - this._onFinish(); - } - - // to load the next event we have to escalate to parent service - if (doRollLoad) { - PlaybackService.roll(); - } - } else { + shouldNotify = true; + this.updateRoll(); + } else if (this.timer.startedAt !== null) { // we only update timer if a timer has been started - if (this.timer.startedAt !== null) { - if (this.playback === Playback.Pause) { - this.pausedTime = this.timer.clock - this.pausedAt; - } - - if (this.playback === Playback.Play && this.timer.current <= 0 && this.timer.finishedAt === null) { - this.timer.finishedAt = this.timer.clock; - this._onFinish(); - } else { - this.timer.expectedFinish = getExpectedFinish( - this.timer.startedAt, - this.timer.finishedAt, - this.timer.duration, - this.pausedTime, - this.timer.addedTime, - ); - } - this.timer.current = getCurrent( - this.timer.startedAt, - this.timer.duration, - this.timer.addedTime, - this.pausedTime, - this.timer.clock, - ); - this.timer.elapsed = getElapsed(this.timer.startedAt, this.timer.clock); - } + shouldNotify = true; + this.updatePlay(); + } + + // we only update the store at the updateInterval + // side effects such as onFinish will still be triggered in the update functions + if (force || this.timer.clock > this._lastUpdate + this._updateInterval) { + this._lastUpdate = this.timer.clock; + this._onUpdate(shouldNotify); } - this._onUpdate(); } - _onUpdate() { + _onUpdate(shouldNotify: boolean) { eventStore.set('timer', this.timer); - integrationService.dispatch(TimerLifeCycle.onUpdate); + if (shouldNotify) { + integrationService.dispatch(TimerLifeCycle.onUpdate); + } } _onFinish() { @@ -358,7 +382,7 @@ export class TimerService { } this.playback = Playback.Roll; this._onRoll(); - this.update(); + this.update(true); } _onRoll() { @@ -370,4 +394,5 @@ export class TimerService { } } -export const eventTimer = new TimerService(); +// calculate at 30fps, refresh at 1fps +export const eventTimer = new TimerService({ refresh: 32, updateInterval: 1000 });