mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-13 11:23:50 +00:00
V2 ws store (#310)
* Update TimerService.ts * refactor: message service publishes to store * refactor: several type improvements * V2 ws store wss (#309) * refactor: shared logging types * refactor: simplify message service consumption * refactor: create discrete logging system * refactor: move socket.io > websocket
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { useAtomValue } from 'jotai';
|
||||
@@ -15,14 +15,14 @@ import {
|
||||
requestReorderEvent,
|
||||
} from '../api/eventsApi';
|
||||
import { defaultPublicAtom, startTimeIsLastEndAtom } from '../atoms/LocalEventSettings';
|
||||
import { LoggingContext } from '../context/LoggingContext';
|
||||
import { useEmitLog } from '../stores/logger';
|
||||
|
||||
/**
|
||||
* @description Set of utilities for events
|
||||
*/
|
||||
export const useEventAction = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { emitError } = useEmitLog();
|
||||
const defaultPublic = useAtomValue(defaultPublicAtom);
|
||||
const startTimeIsLastEnd = useAtomValue(startTimeIsLastEndAtom);
|
||||
|
||||
@@ -39,23 +39,24 @@ export const useEventAction = () => {
|
||||
networkMode: 'always',
|
||||
});
|
||||
|
||||
type AddOptions = {
|
||||
defaultPublic?: boolean;
|
||||
startTimeIsLastEnd?: boolean;
|
||||
lastEventId?: string;
|
||||
type BaseOptions = {
|
||||
after?: string;
|
||||
};
|
||||
|
||||
type EventOptions = BaseOptions & {
|
||||
defaultPublic?: boolean;
|
||||
lastEventId?: string;
|
||||
startTimeIsLastEnd?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds an event to rundown
|
||||
*/
|
||||
const addEvent = useCallback(
|
||||
async (event: Partial<OntimeRundownEntry>, options?: AddOptions) => {
|
||||
async (event: Partial<OntimeRundownEntry>, options?: EventOptions) => {
|
||||
const newEvent: Partial<OntimeRundownEntry> = { ...event };
|
||||
|
||||
// ************* CHECK OPTIONS
|
||||
// there is an option to pass an index of an array to use as start time
|
||||
// only events have options
|
||||
// ************* CHECK OPTIONS specific to events
|
||||
if (newEvent.type === SupportedEvent.Event) {
|
||||
const applicationOptions = {
|
||||
defaultPublic: options?.defaultPublic ?? defaultPublic,
|
||||
@@ -81,13 +82,15 @@ export const useEventAction = () => {
|
||||
if (applicationOptions.defaultPublic) {
|
||||
newEvent.isPublic = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (applicationOptions?.after) {
|
||||
newEvent.after = applicationOptions.after;
|
||||
}
|
||||
// handle adding options that concern all event type
|
||||
if (options?.after) {
|
||||
newEvent.after = options.after;
|
||||
}
|
||||
|
||||
try {
|
||||
// @ts-expect-error -- we know that the object is well formed now
|
||||
await _addEventMutation.mutateAsync(newEvent);
|
||||
} catch (error) {
|
||||
if (!axios.isAxiosError(error)) {
|
||||
|
||||
@@ -1,144 +1,99 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Playback } from 'ontime-types';
|
||||
import { RuntimeStore } from 'ontime-types';
|
||||
|
||||
import {
|
||||
FEAT_CUESHEET,
|
||||
FEAT_INFO,
|
||||
FEAT_MESSAGECONTROL,
|
||||
FEAT_PLAYBACKCONTROL,
|
||||
FEAT_RUNDOWN,
|
||||
TIMER,
|
||||
} from '../api/apiConstants';
|
||||
import { ontimeQueryClient as queryClient } from '../queryClient';
|
||||
import socket, { subscribeOnce } from '../utils/socket';
|
||||
import { deepCompare, useRuntimeStore } from '../stores/runtime';
|
||||
import { socketSendJson } from '../utils/socket';
|
||||
|
||||
function createSocketHook<T>(key: string, defaultValue: T | null = null) {
|
||||
subscribeOnce<T>(key, (data) => queryClient.setQueryData([key], data));
|
||||
|
||||
// retrieves data from the cache or null if non-existent
|
||||
// we need the null because useQuery can't receive undefined
|
||||
const fetcher = () => (queryClient.getQueryData([key]) ?? defaultValue) as T | null;
|
||||
|
||||
return () => useQuery({ queryKey: [key], queryFn: fetcher, placeholderData: defaultValue });
|
||||
}
|
||||
|
||||
interface IRundown {
|
||||
selectedEventId: string | null;
|
||||
nextEventId: string | null;
|
||||
playback: Playback | null;
|
||||
}
|
||||
|
||||
const emptyRundown: IRundown = {
|
||||
selectedEventId: null,
|
||||
nextEventId: null,
|
||||
playback: null,
|
||||
};
|
||||
|
||||
export const useRundownEditor = createSocketHook(FEAT_RUNDOWN, emptyRundown);
|
||||
|
||||
const emptyMessageControl = {
|
||||
messages: {
|
||||
presenter: {
|
||||
text: '',
|
||||
visible: false,
|
||||
},
|
||||
public: {
|
||||
text: '',
|
||||
visible: false,
|
||||
},
|
||||
lower: {
|
||||
text: '',
|
||||
visible: false,
|
||||
},
|
||||
},
|
||||
onAir: false,
|
||||
};
|
||||
|
||||
export const useMessageControl = createSocketHook(FEAT_MESSAGECONTROL, emptyMessageControl);
|
||||
export const setMessage = {
|
||||
presenterText: (payload: string) => socket.emit('set-timer-message-text', payload),
|
||||
presenterVisible: (payload: boolean) => socket.emit('set-timer-message-visible', payload),
|
||||
publicText: (payload: string) => socket.emit('set-public-message-text', payload),
|
||||
publicVisible: (payload: boolean) => socket.emit('set-public-message-visible', payload),
|
||||
lowerText: (payload: string) => socket.emit('set-lower-message-text', payload),
|
||||
lowerVisible: (payload: boolean) => socket.emit('set-lower-message-visible', payload),
|
||||
onAir: (payload: boolean) => socket.emit('set-onAir', payload),
|
||||
};
|
||||
|
||||
export const emptyPlaybackControl = {
|
||||
playback: 'stop',
|
||||
selectedEventId: null,
|
||||
numEvents: 0,
|
||||
};
|
||||
export const usePlaybackControl = createSocketHook(FEAT_PLAYBACKCONTROL, emptyPlaybackControl);
|
||||
export const resetPlayback = () => {
|
||||
const cacheData = queryClient.getQueryData([FEAT_PLAYBACKCONTROL]) as Record<string, unknown>;
|
||||
queryClient.setQueryData([FEAT_PLAYBACKCONTROL], {
|
||||
...cacheData,
|
||||
playback: 'stop',
|
||||
selectedEventId: null,
|
||||
export const useRundownEditor = () => {
|
||||
const featureSelector = (state: RuntimeStore) => ({
|
||||
playback: state.playback,
|
||||
selectedEventId: state.loaded.selectedEventId,
|
||||
nextEventId: state.loaded.nextEventId,
|
||||
});
|
||||
|
||||
return useRuntimeStore(featureSelector, deepCompare);
|
||||
};
|
||||
|
||||
export const useMessageControl = () => {
|
||||
const featureSelector = (state: RuntimeStore) => ({
|
||||
timerMessage: state.timerMessage,
|
||||
publicMessage: state.publicMessage,
|
||||
lowerMessage: state.lowerMessage,
|
||||
onAir: state.onAir,
|
||||
});
|
||||
|
||||
return useRuntimeStore(featureSelector, deepCompare);
|
||||
};
|
||||
|
||||
export const setMessage = {
|
||||
presenterText: (payload: string) => socketSendJson('set-timer-message-text', payload),
|
||||
presenterVisible: (payload: boolean) => socketSendJson('set-timer-message-visible', payload),
|
||||
publicText: (payload: string) => socketSendJson('set-public-message-text', payload),
|
||||
publicVisible: (payload: boolean) => socketSendJson('set-public-message-visible', payload),
|
||||
lowerText: (payload: string) => socketSendJson('set-lower-message-text', payload),
|
||||
lowerVisible: (payload: boolean) => socketSendJson('set-lower-message-visible', payload),
|
||||
onAir: (payload: boolean) => socketSendJson('set-onAir', payload),
|
||||
};
|
||||
|
||||
export const usePlaybackControl = () => {
|
||||
const featureSelector = (state: RuntimeStore) => ({
|
||||
playback: state.playback,
|
||||
numEvents: state.loaded.numEvents,
|
||||
});
|
||||
|
||||
return useRuntimeStore(featureSelector, deepCompare);
|
||||
};
|
||||
|
||||
export const setPlayback = {
|
||||
start: () => socket.emit('set-start'),
|
||||
pause: () => socket.emit('set-pause'),
|
||||
roll: () => socket.emit('set-roll'),
|
||||
start: () => socketSendJson('start'),
|
||||
pause: () => socketSendJson('pause'),
|
||||
roll: () => socketSendJson('roll'),
|
||||
previous: () => {
|
||||
socket.emit('set-previous');
|
||||
socketSendJson('previous');
|
||||
},
|
||||
next: () => {
|
||||
socket.emit('set-next');
|
||||
socketSendJson('next');
|
||||
},
|
||||
stop: () => {
|
||||
socket.emit('set-stop');
|
||||
socketSendJson('stop');
|
||||
},
|
||||
reload: () => {
|
||||
socket.emit('set-reload');
|
||||
socketSendJson('reload');
|
||||
},
|
||||
delay: (amount: number) => {
|
||||
socket.emit('set-delay', amount);
|
||||
socketSendJson('delay', amount);
|
||||
},
|
||||
};
|
||||
|
||||
export const emptyInfo = {
|
||||
titles: {
|
||||
titleNow: '',
|
||||
subtitleNow: '',
|
||||
presenterNow: '',
|
||||
noteNow: '',
|
||||
titleNext: '',
|
||||
subtitleNext: '',
|
||||
presenterNext: '',
|
||||
noteNext: '',
|
||||
},
|
||||
playback: 'stop',
|
||||
selectedEventId: null,
|
||||
selectedEventIndex: null,
|
||||
numEvents: 0,
|
||||
export const useInfoPanel = () => {
|
||||
const featureSelector = (state: RuntimeStore) => ({
|
||||
titles: state.titles,
|
||||
playback: state.playback,
|
||||
selectedEventIndex: state.loaded.selectedEventIndex,
|
||||
numEvents: state.loaded.numEvents,
|
||||
});
|
||||
|
||||
return useRuntimeStore(featureSelector, deepCompare);
|
||||
};
|
||||
|
||||
export const useInfoPanel = createSocketHook(FEAT_INFO, emptyInfo);
|
||||
export const useCuesheet = () => {
|
||||
const featureSelector = (state: RuntimeStore) => ({
|
||||
selectedEventIndex: state.loaded.selectedEventId,
|
||||
titleNow: state.titles.titleNow,
|
||||
});
|
||||
|
||||
export const emptyCuesheet = {
|
||||
selectedEventId: null,
|
||||
titleNow: '',
|
||||
return useRuntimeStore(featureSelector, deepCompare);
|
||||
};
|
||||
|
||||
export const useCuesheet = createSocketHook(FEAT_CUESHEET, emptyCuesheet);
|
||||
|
||||
export const setEventPlayback = {
|
||||
loadEvent: (eventId: string) => socket.emit('set-loadid', eventId),
|
||||
startEvent: (eventId: string) => socket.emit('set-startid', eventId),
|
||||
pause: () => socket.emit('set-pause'),
|
||||
loadEvent: (eventId: string) => socketSendJson('loadid', eventId),
|
||||
startEvent: (eventId: string) => socketSendJson('startid', eventId),
|
||||
pause: () => socketSendJson('pause'),
|
||||
};
|
||||
|
||||
const emptyTimer = {
|
||||
clock: 0,
|
||||
current: 0,
|
||||
secondaryTimer: null,
|
||||
duration: null,
|
||||
startedAt: null,
|
||||
expectedFinish: null,
|
||||
};
|
||||
export const useTimer = () => {
|
||||
const featureSelector = (state: RuntimeStore) => ({
|
||||
timer: state.timer,
|
||||
});
|
||||
|
||||
export const useTimer = createSocketHook(TIMER, emptyTimer);
|
||||
return useRuntimeStore(featureSelector, deepCompare);
|
||||
};
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import socket from '../utils/socket';
|
||||
|
||||
export default function useSubscription<T>(topic: string, initialState: T, requestString?: string) {
|
||||
const [state, setState] = useState<T>(initialState);
|
||||
|
||||
useEffect(() => {
|
||||
if (requestString) {
|
||||
socket.emit(requestString);
|
||||
} else {
|
||||
socket.emit(`get-${topic}`);
|
||||
}
|
||||
socket.on(topic, setState);
|
||||
|
||||
return () => {
|
||||
socket.off(topic);
|
||||
};
|
||||
}, [requestString, topic]);
|
||||
|
||||
return [state, setState] as const;
|
||||
};
|
||||
Reference in New Issue
Block a user