mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 06:59:09 +00:00
feat: many timers (#706)
* wip: create simple timer class * refactor: service to manage extra timers * wip:ui for controlling the extra timer --------- Co-authored-by: arc-alex <ac@omnivox.dk>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { RuntimeStore } from 'ontime-types';
|
import { RuntimeStore, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||||
|
|
||||||
import { useRuntimeStore } from '../stores/runtime';
|
import { useRuntimeStore } from '../stores/runtime';
|
||||||
import { socketSendJson } from '../utils/socket';
|
import { socketSendJson } from '../utils/socket';
|
||||||
@@ -92,6 +92,29 @@ export const useInfoPanel = () => {
|
|||||||
return useRuntimeStore(featureSelector);
|
return useRuntimeStore(featureSelector);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useExtraTimerTime = () => {
|
||||||
|
const featureSelector = (state: RuntimeStore) => state.timer1.current;
|
||||||
|
|
||||||
|
return useRuntimeStore(featureSelector);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useExtraTimerControl = () => {
|
||||||
|
const featureSelector = (state: RuntimeStore) => ({
|
||||||
|
playback: state.timer1.playback,
|
||||||
|
direction: state.timer1.direction,
|
||||||
|
});
|
||||||
|
|
||||||
|
return useRuntimeStore(featureSelector);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setExtraTimer = {
|
||||||
|
start: () => socketSendJson('extratimer', SimplePlayback.Start),
|
||||||
|
pause: () => socketSendJson('extratimer', SimplePlayback.Pause),
|
||||||
|
stop: () => socketSendJson('extratimer', SimplePlayback.Stop),
|
||||||
|
setDirection: (direction: SimpleDirection) => socketSendJson('extratimer', { direction }),
|
||||||
|
setTime: (time: number) => socketSendJson('extratimer', { settime: time }),
|
||||||
|
};
|
||||||
|
|
||||||
export const useCuesheet = () => {
|
export const useCuesheet = () => {
|
||||||
const featureSelector = (state: RuntimeStore) => ({
|
const featureSelector = (state: RuntimeStore) => ({
|
||||||
playback: state.timer.playback,
|
playback: state.timer.playback,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { Playback, RuntimeStore } from 'ontime-types';
|
import { Playback, RuntimeStore, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||||
import { createWithEqualityFn, useStoreWithEqualityFn } from 'zustand/traditional';
|
import { createWithEqualityFn, useStoreWithEqualityFn } from 'zustand/traditional';
|
||||||
|
|
||||||
export const runtimeStorePlaceholder: RuntimeStore = {
|
export const runtimeStorePlaceholder: RuntimeStore = {
|
||||||
@@ -44,6 +44,12 @@ export const runtimeStorePlaceholder: RuntimeStore = {
|
|||||||
eventNext: null,
|
eventNext: null,
|
||||||
publicEventNow: null,
|
publicEventNow: null,
|
||||||
publicEventNext: null,
|
publicEventNext: null,
|
||||||
|
timer1: {
|
||||||
|
current: 0,
|
||||||
|
direction: SimpleDirection.CountUp,
|
||||||
|
duration: 0,
|
||||||
|
playback: SimplePlayback.Stop,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const deepCompare = <T>(a: T, b: T) => isEqual(a, b);
|
const deepCompare = <T>(a: T, b: T) => isEqual(a, b);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Playback } from 'ontime-types';
|
|||||||
|
|
||||||
import { usePlaybackControl } from '../../../common/hooks/useSocket';
|
import { usePlaybackControl } from '../../../common/hooks/useSocket';
|
||||||
|
|
||||||
|
import { ExtraTimer } from './extra-timer/ExtraTimer';
|
||||||
import PlaybackButtons from './playback-buttons/PlaybackButtons';
|
import PlaybackButtons from './playback-buttons/PlaybackButtons';
|
||||||
import PlaybackTimer from './playback-timer/PlaybackTimer';
|
import PlaybackTimer from './playback-timer/PlaybackTimer';
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ export default function PlaybackControl() {
|
|||||||
numEvents={data.numEvents}
|
numEvents={data.numEvents}
|
||||||
selectedEventIndex={data.selectedEventIndex}
|
selectedEventIndex={data.selectedEventIndex}
|
||||||
/>
|
/>
|
||||||
|
<ExtraTimer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.extraRow {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown';
|
||||||
|
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||||
|
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
||||||
|
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||||
|
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
||||||
|
import { Playback, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||||
|
|
||||||
|
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
|
||||||
|
import { setExtraTimer, useExtraTimerControl, useExtraTimerTime } from '../../../../common/hooks/useSocket';
|
||||||
|
import { forgivingStringToMillis } from '../../../../common/utils/dateConfig';
|
||||||
|
import TapButton from '../tap-button/TapButton';
|
||||||
|
|
||||||
|
import style from './ExtraTimer.module.scss';
|
||||||
|
|
||||||
|
export function ExtraTimer() {
|
||||||
|
const { playback, direction } = useExtraTimerControl();
|
||||||
|
|
||||||
|
const { start, pause, stop, setDirection } = setExtraTimer;
|
||||||
|
|
||||||
|
const toggleDirection = () => {
|
||||||
|
const newDirection = direction === SimpleDirection.CountDown ? SimpleDirection.CountUp : SimpleDirection.CountDown;
|
||||||
|
setDirection(newDirection);
|
||||||
|
};
|
||||||
|
|
||||||
|
const userCan = {
|
||||||
|
start: playback !== SimplePlayback.Start,
|
||||||
|
pause: playback === SimplePlayback.Start,
|
||||||
|
stop: playback !== SimplePlayback.Stop,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.extraRow}>
|
||||||
|
<ExtraTimeInput />
|
||||||
|
<TapButton onClick={toggleDirection}>
|
||||||
|
{direction === SimpleDirection.CountDown && <IoArrowDown />}
|
||||||
|
{direction === SimpleDirection.CountUp && <IoArrowUp />}
|
||||||
|
</TapButton>
|
||||||
|
|
||||||
|
<TapButton
|
||||||
|
onClick={start}
|
||||||
|
theme={Playback.Play}
|
||||||
|
active={playback === SimplePlayback.Start}
|
||||||
|
disabled={!userCan.start}
|
||||||
|
>
|
||||||
|
<IoPlay />
|
||||||
|
</TapButton>
|
||||||
|
<TapButton
|
||||||
|
onClick={pause}
|
||||||
|
theme={Playback.Pause}
|
||||||
|
active={playback === SimplePlayback.Pause}
|
||||||
|
disabled={!userCan.pause}
|
||||||
|
>
|
||||||
|
<IoPause />
|
||||||
|
</TapButton>
|
||||||
|
<TapButton onClick={stop} theme={Playback.Stop} disabled={!userCan.stop}>
|
||||||
|
<IoStop />
|
||||||
|
</TapButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExtraTimeInput() {
|
||||||
|
const time = useExtraTimerTime();
|
||||||
|
const { setTime } = setExtraTimer;
|
||||||
|
|
||||||
|
const handleTimeUpdate = (_field: string, value: string) => {
|
||||||
|
const newTime = forgivingStringToMillis(value);
|
||||||
|
setTime(newTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TimeInput<'extraTimer'> submitHandler={handleTimeUpdate} name='extraTimer' time={time} placeholder='Timer 1' />
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@ import style from './TapButton.module.scss';
|
|||||||
interface TapButtonProps {
|
interface TapButtonProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
aspect?: 'normal' | 'square' | 'fill';
|
aspect?: 'normal' | 'square' | 'fill';
|
||||||
square?: boolean;
|
|
||||||
free?: boolean;
|
free?: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
theme?: Playback | 'neutral';
|
theme?: Playback | 'neutral';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@use './EditorMixin' as editor;
|
@use './EditorMixin' as editor;
|
||||||
|
|
||||||
$menu-width: 2.75rem;
|
$menu-width: 2.75rem;
|
||||||
$playback-width: 26rem;
|
$playback-width: 30rem;
|
||||||
|
|
||||||
.corner {
|
.corner {
|
||||||
@include editor.corner;
|
@include editor.corner;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { HttpSettings, LogOrigin, OSCSettings, Playback } from 'ontime-types';
|
import { HttpSettings, LogOrigin, OSCSettings, Playback, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||||
|
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
@@ -168,6 +168,12 @@ export const startServer = async () => {
|
|||||||
publicEventNow: state.publicEventNow,
|
publicEventNow: state.publicEventNow,
|
||||||
eventNext: state.eventNext,
|
eventNext: state.eventNext,
|
||||||
publicEventNext: state.publicEventNext,
|
publicEventNext: state.publicEventNext,
|
||||||
|
timer1: {
|
||||||
|
duration: null,
|
||||||
|
current: null,
|
||||||
|
playback: SimplePlayback.Stop,
|
||||||
|
direction: SimpleDirection.CountDown,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// load restore point if it exists
|
// load restore point if it exists
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { SimpleDirection, SimplePlayback, SimpleTimerState } from 'ontime-types';
|
||||||
|
|
||||||
|
export class SimpleTimer {
|
||||||
|
state: SimpleTimerState = {
|
||||||
|
duration: 0,
|
||||||
|
current: 0,
|
||||||
|
playback: SimplePlayback.Stop,
|
||||||
|
direction: SimpleDirection.CountDown,
|
||||||
|
};
|
||||||
|
private startedAt: number | null = null;
|
||||||
|
private pausedAt: number | null = null;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public reset() {
|
||||||
|
this.state = {
|
||||||
|
duration: 0,
|
||||||
|
current: 0,
|
||||||
|
playback: SimplePlayback.Stop,
|
||||||
|
direction: SimpleDirection.CountDown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the duration of the timer
|
||||||
|
* @param time - time in milliseconds
|
||||||
|
*/
|
||||||
|
public setTime(time: number): SimpleTimerState {
|
||||||
|
this.state.duration = time;
|
||||||
|
this.state.current = time;
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDirection(direction: SimpleDirection): SimpleTimerState {
|
||||||
|
this.state.playback = SimplePlayback.Stop;
|
||||||
|
this.state.current = this.state.duration;
|
||||||
|
this.state.direction = direction;
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(timeNow: number): SimpleTimerState {
|
||||||
|
if (this.state.playback === SimplePlayback.Pause) {
|
||||||
|
const elapsedSincePause = this.pausedAt - this.startedAt;
|
||||||
|
this.startedAt = timeNow - elapsedSincePause;
|
||||||
|
} else if (this.state.playback === SimplePlayback.Stop) {
|
||||||
|
this.startedAt = timeNow;
|
||||||
|
}
|
||||||
|
this.state.playback = SimplePlayback.Start;
|
||||||
|
return this.update(timeNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause(timeNow: number): SimpleTimerState {
|
||||||
|
if (this.state.playback !== SimplePlayback.Start) return this.state;
|
||||||
|
this.state.playback = SimplePlayback.Pause;
|
||||||
|
this.pausedAt = timeNow;
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop(): SimpleTimerState {
|
||||||
|
this.state.playback = SimplePlayback.Stop;
|
||||||
|
this.state.current = this.state.duration;
|
||||||
|
this.startedAt = null;
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(timeNow: number): SimpleTimerState {
|
||||||
|
if (this.state.playback === SimplePlayback.Start) {
|
||||||
|
const elapsed = timeNow - this.startedAt;
|
||||||
|
if (this.state.direction === SimpleDirection.CountDown) {
|
||||||
|
this.state.current = this.state.duration - elapsed;
|
||||||
|
} else if (this.state.direction === SimpleDirection.CountUp) {
|
||||||
|
this.state.current = this.state.duration + elapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import { SimpleDirection, SimplePlayback, SimpleTimerState } from 'ontime-types';
|
||||||
|
|
||||||
|
import { SimpleTimer } from '../SimpleTimer.js';
|
||||||
|
|
||||||
|
describe('SimpleTimer count-down', () => {
|
||||||
|
let timer: SimpleTimer;
|
||||||
|
|
||||||
|
describe('normal timer flow', () => {
|
||||||
|
const initialTime = 1000;
|
||||||
|
timer = new SimpleTimer();
|
||||||
|
|
||||||
|
test('setting the timer duration', () => {
|
||||||
|
const newState = timer.setTime(initialTime);
|
||||||
|
const expected: SimpleTimerState = {
|
||||||
|
duration: initialTime,
|
||||||
|
current: initialTime,
|
||||||
|
direction: SimpleDirection.CountDown,
|
||||||
|
playback: SimplePlayback.Stop,
|
||||||
|
};
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setting the timer to play', () => {
|
||||||
|
const newState = timer.start(0);
|
||||||
|
const expected: SimpleTimerState = {
|
||||||
|
duration: initialTime,
|
||||||
|
current: initialTime,
|
||||||
|
direction: SimpleDirection.CountDown,
|
||||||
|
playback: SimplePlayback.Start,
|
||||||
|
};
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updating the timer', () => {
|
||||||
|
let newState = timer.update(100);
|
||||||
|
const expected: SimpleTimerState = {
|
||||||
|
duration: initialTime,
|
||||||
|
current: initialTime - 100,
|
||||||
|
direction: SimpleDirection.CountDown,
|
||||||
|
playback: SimplePlayback.Start,
|
||||||
|
};
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
|
||||||
|
newState = timer.update(500);
|
||||||
|
expected.current = initialTime - 500;
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
|
||||||
|
newState = timer.update(1500);
|
||||||
|
expected.current = initialTime - 1500;
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pausing the time doesnt affect the current', () => {
|
||||||
|
const pausedTime = 200;
|
||||||
|
let newState = timer.pause(1500);
|
||||||
|
const expected: SimpleTimerState = {
|
||||||
|
duration: initialTime,
|
||||||
|
current: initialTime - 1500,
|
||||||
|
direction: SimpleDirection.CountDown,
|
||||||
|
playback: SimplePlayback.Pause,
|
||||||
|
};
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
|
||||||
|
newState = timer.update(1600);
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
|
||||||
|
newState = timer.update(1700);
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
|
||||||
|
newState = timer.start(1700);
|
||||||
|
expected.playback = SimplePlayback.Start;
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
|
||||||
|
newState = timer.update(1800);
|
||||||
|
(expected.current = initialTime - 1800 + pausedTime), expect(newState).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stopping the timer clears the running data', () => {
|
||||||
|
const newState = timer.stop();
|
||||||
|
const expected: SimpleTimerState = {
|
||||||
|
duration: initialTime,
|
||||||
|
current: initialTime,
|
||||||
|
direction: SimpleDirection.CountDown,
|
||||||
|
playback: SimplePlayback.Stop,
|
||||||
|
};
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('count-up mode', () => {
|
||||||
|
const initialState = timer.setDirection(SimpleDirection.CountUp);
|
||||||
|
expect(initialState.current).toBe(initialTime);
|
||||||
|
|
||||||
|
let newState = timer.start(0);
|
||||||
|
const expected: SimpleTimerState = {
|
||||||
|
duration: initialTime,
|
||||||
|
current: initialTime,
|
||||||
|
direction: SimpleDirection.CountUp,
|
||||||
|
playback: SimplePlayback.Start,
|
||||||
|
};
|
||||||
|
|
||||||
|
newState = timer.update(100);
|
||||||
|
expected.current = initialTime + 100;
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
|
||||||
|
newState = timer.update(500);
|
||||||
|
expected.current = initialTime + 500;
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
|
||||||
|
newState = timer.update(1500);
|
||||||
|
expected.current = initialTime + 1500;
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('changing direction stops the timer', () => {
|
||||||
|
const newState = timer.setDirection(SimpleDirection.CountDown);
|
||||||
|
const expected: SimpleTimerState = {
|
||||||
|
duration: initialTime,
|
||||||
|
current: initialTime,
|
||||||
|
direction: SimpleDirection.CountDown,
|
||||||
|
playback: SimplePlayback.Stop,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(newState).toStrictEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DeepPartial, MessageState } from 'ontime-types';
|
import { DeepPartial, MessageState, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||||
|
|
||||||
// skipcq: JS-C1003 - we like the API
|
// skipcq: JS-C1003 - we like the API
|
||||||
import * as assert from '../utils/assert.js';
|
import * as assert from '../utils/assert.js';
|
||||||
@@ -8,6 +8,7 @@ import { messageService } from '../services/message-service/MessageService.js';
|
|||||||
import { runtimeService } from '../services/runtime-service/RuntimeService.js';
|
import { runtimeService } from '../services/runtime-service/RuntimeService.js';
|
||||||
import { eventStore } from '../stores/EventStore.js';
|
import { eventStore } from '../stores/EventStore.js';
|
||||||
import { parse, updateEvent } from './integrationController.config.js';
|
import { parse, updateEvent } from './integrationController.config.js';
|
||||||
|
import { extraTimerService } from '../services/extra-timer-service/ExtraTimerService.js';
|
||||||
import { validateMessage, validateTimerMessage } from '../services/message-service/messageUtils.js';
|
import { validateMessage, validateTimerMessage } from '../services/message-service/messageUtils.js';
|
||||||
|
|
||||||
export type ChangeOptions = {
|
export type ChangeOptions = {
|
||||||
@@ -152,6 +153,40 @@ const actionHandlers: Record<string, ActionHandler> = {
|
|||||||
runtimeService.addTime(time * 1000);
|
runtimeService.addTime(time * 1000);
|
||||||
return { payload: 'success' };
|
return { payload: 'success' };
|
||||||
},
|
},
|
||||||
|
/* Extra timers */
|
||||||
|
extratimer: (payload) => {
|
||||||
|
if (payload && typeof payload === 'string') {
|
||||||
|
if (payload === SimplePlayback.Start) {
|
||||||
|
const reply = extraTimerService.start();
|
||||||
|
return { payload: reply };
|
||||||
|
}
|
||||||
|
if (payload === SimplePlayback.Pause) {
|
||||||
|
const reply = extraTimerService.pause();
|
||||||
|
return { payload: reply };
|
||||||
|
}
|
||||||
|
if (payload === SimplePlayback.Stop) {
|
||||||
|
const reply = extraTimerService.stop();
|
||||||
|
return { payload: reply };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload && typeof payload === 'object') {
|
||||||
|
if ('settime' in payload) {
|
||||||
|
const time = numberOrError(payload.settime);
|
||||||
|
const reply = extraTimerService.setTime(time);
|
||||||
|
return { payload: reply };
|
||||||
|
}
|
||||||
|
if ('direction' in payload) {
|
||||||
|
if (payload.direction === SimpleDirection.CountUp || payload.direction === SimpleDirection.CountDown) {
|
||||||
|
const reply = extraTimerService.setDirection(payload.direction);
|
||||||
|
return { payload: reply };
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid direction payload');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('Invalid extratimer payload');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Returns a value of type number, converting if necessary
|
* Returns a value of type number, converting if necessary
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { SimpleDirection, SimpleTimerState } from 'ontime-types';
|
||||||
|
|
||||||
|
import { SimpleTimer } from '../../classes/simple-timer/SimpleTimer.js';
|
||||||
|
import { eventStore } from '../../stores/EventStore.js';
|
||||||
|
|
||||||
|
export type EmitFn = (state: SimpleTimerState) => void;
|
||||||
|
export type GetTimeFn = () => number;
|
||||||
|
|
||||||
|
export class ExtraTimerService {
|
||||||
|
private timer: SimpleTimer;
|
||||||
|
private interval: NodeJS.Timer;
|
||||||
|
private emit: EmitFn;
|
||||||
|
private getTime: GetTimeFn;
|
||||||
|
|
||||||
|
constructor(emit: EmitFn, getTime: GetTimeFn) {
|
||||||
|
this.timer = new SimpleTimer();
|
||||||
|
this.emit = emit;
|
||||||
|
this.getTime = getTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private startInterval() {
|
||||||
|
this.interval = setInterval(this.update.bind(this), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopInterval() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
@broadcastReturn
|
||||||
|
setDirection(direction: SimpleDirection) {
|
||||||
|
return this.timer.setDirection(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@broadcastReturn
|
||||||
|
start() {
|
||||||
|
this.startInterval();
|
||||||
|
return this.timer.start(this.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@broadcastReturn
|
||||||
|
pause() {
|
||||||
|
return this.timer.pause(this.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@broadcastReturn
|
||||||
|
stop() {
|
||||||
|
this.stopInterval();
|
||||||
|
return this.timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@broadcastReturn
|
||||||
|
setTime(duration: number) {
|
||||||
|
return this.timer.setTime(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@broadcastReturn
|
||||||
|
private update() {
|
||||||
|
return this.timer.update(this.getTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function broadcastReturn(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||||
|
const originalMethod = descriptor.value;
|
||||||
|
|
||||||
|
descriptor.value = function (...args: any[]) {
|
||||||
|
const result = originalMethod.apply(this, args);
|
||||||
|
this.emit(result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = (state: SimpleTimerState) => eventStore.set('timer1', state);
|
||||||
|
const timeNow = () => Date.now();
|
||||||
|
|
||||||
|
export const extraTimerService = new ExtraTimerService(emit, timeNow);
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"types": ["vitest/globals"],
|
"types": ["vitest/globals"],
|
||||||
"outDir": "dist"
|
"outDir": "dist",
|
||||||
|
"experimentalDecorators": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export enum SimplePlayback {
|
||||||
|
Start = 'start',
|
||||||
|
Pause = 'pause',
|
||||||
|
Stop = 'stop',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SimpleDirection {
|
||||||
|
CountUp = 'count-up',
|
||||||
|
CountDown = 'count-down',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SimpleTimerState = {
|
||||||
|
duration: number;
|
||||||
|
current: number;
|
||||||
|
playback: SimplePlayback;
|
||||||
|
direction: SimpleDirection;
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@ import { MessageState } from './MessageControl.type.js';
|
|||||||
import { TimerState } from './TimerState.type.js';
|
import { TimerState } from './TimerState.type.js';
|
||||||
import { Runtime } from './Runtime.type.js';
|
import { Runtime } from './Runtime.type.js';
|
||||||
import { OntimeEvent } from '../core/OntimeEvent.type.js';
|
import { OntimeEvent } from '../core/OntimeEvent.type.js';
|
||||||
|
import { SimpleTimerState } from './ExtraTimer.type.js';
|
||||||
|
|
||||||
export type RuntimeStore = {
|
export type RuntimeStore = {
|
||||||
// timer data
|
// timer data
|
||||||
@@ -18,4 +19,7 @@ export type RuntimeStore = {
|
|||||||
publicEventNow: OntimeEvent | null;
|
publicEventNow: OntimeEvent | null;
|
||||||
eventNext: OntimeEvent | null;
|
eventNext: OntimeEvent | null;
|
||||||
publicEventNext: OntimeEvent | null;
|
publicEventNext: OntimeEvent | null;
|
||||||
|
|
||||||
|
// extra timers
|
||||||
|
timer1: SimpleTimerState;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ export type { Runtime } from './definitions/runtime/Runtime.type.js';
|
|||||||
export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
|
export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
|
||||||
export type { TimerState } from './definitions/runtime/TimerState.type.js';
|
export type { TimerState } from './definitions/runtime/TimerState.type.js';
|
||||||
|
|
||||||
|
// ---> Extra Timer
|
||||||
|
export { type SimpleTimerState, SimplePlayback, SimpleDirection } from './definitions/runtime/ExtraTimer.type.js';
|
||||||
|
|
||||||
// CLIENT
|
// CLIENT
|
||||||
|
|
||||||
// TYPE UTILITIES
|
// TYPE UTILITIES
|
||||||
|
|||||||
Reference in New Issue
Block a user