From c1f87736ebbe41cbbbfd5e1710eb71e9169d5292 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Sun, 20 Nov 2022 18:08:49 +0100 Subject: [PATCH] V2 socket store (#254) * refactor: create socket utility * refactor: ensure a single subscription to topics * refactor: replace usages of socket object with subscription store handler --- client/src/App.jsx | 35 ++- client/src/AppRouter.jsx | 2 - client/src/common/api/apiConstants.js | 15 +- client/src/common/context/LoggingContext.tsx | 9 +- client/src/common/context/socketContext.tsx | 43 --- client/src/common/hooks/useSocket.ts | 140 ++++++++++ client/src/common/hooks/useSocketProvider.js | 252 ------------------ client/src/common/hooks/useSubscription.tsx | 7 +- client/src/common/utils/socket.ts | 17 ++ .../control/__tests__/MessageControl.test.jsx | 5 +- .../__tests__/PlaybackControl.test.jsx | 7 +- .../control/message/MessageControl.jsx | 4 +- .../features/control/playback/Playback.tsx | 3 +- .../control/playback/PlaybackControl.tsx | 4 +- .../control/playback/PlaybackTimer.tsx | 8 +- .../features/control/playback/Transport.tsx | 3 +- client/src/features/info/Info.jsx | 4 +- .../src/features/info/__tests__/Info.test.jsx | 7 +- client/src/features/rundown/Rundown.jsx | 5 +- .../rundown/event-block/EventBlock.tsx | 7 +- client/src/features/table/TableHeader.jsx | 4 +- client/src/features/table/TableWrapper.jsx | 6 +- client/src/features/viewers/ViewWrapper.jsx | 11 +- server/src/classes/timer/EventTimer.js | 8 +- 24 files changed, 219 insertions(+), 387 deletions(-) delete mode 100644 client/src/common/context/socketContext.tsx create mode 100644 client/src/common/hooks/useSocket.ts delete mode 100644 client/src/common/hooks/useSocketProvider.js create mode 100644 client/src/common/utils/socket.ts diff --git a/client/src/App.jsx b/client/src/App.jsx index 1fa66ff09..0fe24c0c5 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -6,7 +6,6 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary'; import { AppContextProvider } from 'common/context/AppContext'; import { LoggingProvider } from 'common/context/LoggingContext'; -import SocketProvider from 'common/context/socketContext'; import { ontimeQueryClient } from './common/queryClient'; import theme from './theme/theme'; @@ -45,24 +44,22 @@ function App() { return ( - - - - - -
- - - - - - -
-
-
-
-
-
+ + + + +
+ + + + + + +
+
+
+
+
); } diff --git a/client/src/AppRouter.jsx b/client/src/AppRouter.jsx index 921099950..048081897 100644 --- a/client/src/AppRouter.jsx +++ b/client/src/AppRouter.jsx @@ -1,7 +1,6 @@ import { lazy, useEffect } from 'react'; import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'; -import { useSocketProvider } from './common/hooks/useSocketProvider'; import useAliases from './common/hooks-query/useAliases'; import withSocket from './features/viewers/ViewWrapper'; @@ -36,7 +35,6 @@ const MessageControl = lazy(() => import('features/control/message/MessageContro const Info = lazy(() => import('features/info/InfoExport')); export default function AppRouter() { - useSocketProvider(); const { data } = useAliases(); const location = useLocation(); const navigate = useNavigate(); diff --git a/client/src/common/api/apiConstants.js b/client/src/common/api/apiConstants.js index 73cc44953..9005a1b0c 100644 --- a/client/src/common/api/apiConstants.js +++ b/client/src/common/api/apiConstants.js @@ -1,4 +1,6 @@ export const STATIC_PORT = 4001; + +// REST stuff export const EVENT_TABLE = ['event']; export const ALIASES = ['aliases']; export const USERFIELDS = ['userFields']; @@ -9,12 +11,13 @@ export const OSC_SETTINGS = ['oscSettings']; export const APP_SETTINGS = ['appSettings']; export const VIEW_SETTINGS = ['viewSettings']; -export const FEAT_RUNDOWN = ['feat-rundown']; -export const FEAT_MESSAGECONTROL = ['feat-messagecontrol']; -export const FEAT_PLAYBACKCONTROL = ['feat-playbackcontrol']; -export const FEAT_INFO = ['feat-info']; -export const FEAT_CUESHEET = ['feat-cuesheet']; -export const TIMER = ['ontime-timer']; +// websocket stuff +export const FEAT_CUESHEET = 'feat-cuesheet'; +export const FEAT_INFO = 'feat-info'; +export const FEAT_MESSAGECONTROL = 'feat-messagecontrol'; +export const FEAT_PLAYBACKCONTROL = 'feat-playbackcontrol'; +export const FEAT_RUNDOWN = 'feat-rundown'; +export const TIMER = 'ontime-timer'; /** * @description finds server path given the current location, it diff --git a/client/src/common/context/LoggingContext.tsx b/client/src/common/context/LoggingContext.tsx index 025000c34..a0d4460d1 100644 --- a/client/src/common/context/LoggingContext.tsx +++ b/client/src/common/context/LoggingContext.tsx @@ -3,8 +3,7 @@ import { createContext, ReactNode, useCallback, useEffect, useState } from 'reac import { generateId } from '../utils/generate_id'; import { nowInMillis, stringFromMillis } from '../utils/time'; -import { useSocket } from './socketContext'; - +import socket from '../utils/socket'; type LOG_LEVEL = 'INFO' | 'WARN' | 'ERROR'; type Log = { id: string; @@ -40,15 +39,13 @@ export const LoggingContext = createContext({ export const LoggingProvider = ({ children }: LoggingProviderProps) => { const MAX_MESSAGES = 100; - const socket = useSocket(); const [logData, setLogData] = useState([]); const origin = 'USER'; + // todo: use react-query store + // todo: useSubscription or feature // handle incoming messages useEffect(() => { - if (socket == null) return; - - // Ask for log data socket.emit('get-logger'); socket.on('logger', (data: Log) => { diff --git a/client/src/common/context/socketContext.tsx b/client/src/common/context/socketContext.tsx deleted file mode 100644 index 330abd860..000000000 --- a/client/src/common/context/socketContext.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { createContext, ReactNode, useContext, useEffect, useState } from 'react'; -import { serverURL } from 'common/api/apiConstants'; -import io, { Socket } from 'socket.io-client'; - -interface SocketProviderState { - socket: Socket | null; - emit: (topic: string, payload?: T) => void; - on: (topic: string, callback: (data: T) => void) => void; - off: (topic: string) => void; -} - -type SocketProviderProps = { - children: ReactNode; -}; - -const SocketContext = createContext({ - socket: null, - emit: () => {}, - on: () => {}, - off: () => {} -}); - -export const useSocket = () => { - return useContext(SocketContext); -}; - -function SocketProvider({ children }: SocketProviderProps) { - const [socket, setSocket] = useState(null); - - useEffect(() => { - const socketInstance = io(serverURL, { transports: ["websocket"] }); - setSocket(socketInstance); - return () => { - socketInstance.disconnect(); - }; - }, []); - - return ( - {children} - ); -} - -export default SocketProvider; diff --git a/client/src/common/hooks/useSocket.ts b/client/src/common/hooks/useSocket.ts new file mode 100644 index 000000000..363a7e098 --- /dev/null +++ b/client/src/common/hooks/useSocket.ts @@ -0,0 +1,140 @@ +import { useQuery } from '@tanstack/react-query'; +import { ontimeQueryClient as queryClient } from 'common/queryClient'; +import socket, { subscribeOnce } from 'common/utils/socket'; + +import { + FEAT_CUESHEET, + FEAT_INFO, + FEAT_MESSAGECONTROL, + FEAT_PLAYBACKCONTROL, + FEAT_RUNDOWN, + TIMER, +} from '../api/apiConstants'; + +function createSocketHook(key: string, defaultValue: T | null = null) { + subscribeOnce(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([key], fetcher, { placeholderData: defaultValue }); +} + +const emptyRundown = { + selectEventId: null, + nextEventId: null, + playback: null, +}; + +export const useRundownEditor = createSocketHook(FEAT_RUNDOWN, emptyRundown); + +const emptyMessageControl = { + 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; + + queryClient.setQueryData([FEAT_PLAYBACKCONTROL], { + ...cacheData, + playback: 'stop', + selectedEventId: null, + }); +}; +export const setPlayback = { + start: () => socket.emit('set-start'), + pause: () => socket.emit('set-pause'), + 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); + }, +}; + +export const emptyInfo = { + titles: { + titleNow: '', + subtitleNow: '', + presenterNow: '', + noteNow: '', + titleNext: '', + subtitleNext: '', + presenterNext: '', + noteNext: '', + }, + playback: 'stop', + selectedEventId: null, + selectedEventIndex: null, + numEvents: 0, +}; + +export const useInfoPanel = createSocketHook(FEAT_INFO, emptyInfo); + +export const emptyCuesheet = { + selectedEventId: null, + titleNow: '', +}; + +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'), +}; + +const emptyTimer = { + clock: 0, + running: 0, + isNegative: false, + startedAt: null, + expectedFinish: null, +}; + +export const useTimer = createSocketHook(TIMER, emptyTimer); diff --git a/client/src/common/hooks/useSocketProvider.js b/client/src/common/hooks/useSocketProvider.js deleted file mode 100644 index d53617e77..000000000 --- a/client/src/common/hooks/useSocketProvider.js +++ /dev/null @@ -1,252 +0,0 @@ -import { useCallback, useEffect, useMemo } from 'react'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; - -import { - FEAT_CUESHEET, - FEAT_INFO, - FEAT_MESSAGECONTROL, - FEAT_PLAYBACKCONTROL, - FEAT_RUNDOWN, - TIMER, -} from '../api/apiConstants'; -import { useSocket } from '../context/socketContext'; - -export const useRundownProvider = () => { - const { data } = useQuery(FEAT_RUNDOWN, () => undefined, { - cacheTime: Infinity, - staleTime: Infinity, - }); - const placeholder = useMemo( - () => ({ - selectedEventId: null, - nextEventId: null, - playback: null, - }), - [], - ); - return data ?? placeholder; -}; - -export const useMessageControlProvider = () => { - const socket = useSocket(); - const { data } = useQuery(FEAT_MESSAGECONTROL, () => undefined, { - cacheTime: Infinity, - staleTime: Infinity, - }); - const placeholder = useMemo( - () => ({ - presenter: { - text: '', - visible: false, - }, - public: { - text: '', - visible: false, - }, - lower: { - text: '', - visible: false, - }, - onAir: false, - }), - [], - ); - - const returnData = data ?? placeholder; - - const setMessage = useMemo( - () => ({ - presenterText: (payload) => socket.emit('set-timer-message-text', payload), - presenterVisible: (payload) => socket.emit('set-timer-message-visible', payload), - publicText: (payload) => socket.emit('set-public-message-text', payload), - publicVisible: (payload) => socket.emit('set-public-message-visible', payload), - lowerText: (payload) => socket.emit('set-lower-message-text', payload), - lowerVisible: (payload) => socket.emit('set-lower-message-visible', payload), - onAir: (payload) => socket.emit('set-onAir', payload), - }), - [socket], - ); - - return { data: returnData, setMessage }; -}; - -export const usePlaybackControlProvider = () => { - const socket = useSocket(); - const queryClient = useQueryClient(); - const { data } = useQuery(FEAT_PLAYBACKCONTROL, () => undefined, { - cacheTime: Infinity, - staleTime: Infinity, - }); - const placeholder = useMemo( - () => ({ - playback: 'stop', - selectedEventId: null, - numEvents: 0, - }), - [], - ); - - const resetData = useCallback(() => { - queryClient.setQueryData(FEAT_PLAYBACKCONTROL, (data) => ({ - ...data, - playback: 'stop', - selectedEventId: null, - })); - }, [queryClient]); - - const setPlayback = useMemo( - () => ({ - start: () => socket.emit('set-start'), - pause: () => socket.emit('set-pause'), - roll: () => socket.emit('set-roll'), - previous: () => { - socket.emit('set-previous'); - resetData(); - }, - next: () => { - socket.emit('set-next'); - resetData(); - }, - stop: () => { - socket.emit('set-stop'); - resetData(); - }, - reload: () => { - socket.emit('set-reload'); - resetData(); - }, - delay: (amount) => { - socket.emit('set-delay', amount); - }, - }), - [resetData, socket], - ); - - const returnData = data ?? placeholder; - - return { data: returnData, resetData, setPlayback }; -}; - -export const useInfoProvider = () => { - const { data } = useQuery(FEAT_INFO, () => undefined, { - cacheTime: Infinity, - staleTime: Infinity, - }); - const placeholder = useMemo( - () => ({ - titles: { - titleNow: '', - subtitleNow: '', - presenterNow: '', - noteNow: '', - titleNext: '', - subtitleNext: '', - presenterNext: '', - noteNext: '', - }, - playback: 'stop', - selectedEventId: null, - selectedEventIndex: null, - numEvents: 0, - }), - [], - ); - return data ?? placeholder; -}; - -export const useCuesheetProvider = () => { - const { data } = useQuery(FEAT_CUESHEET, () => undefined, { - cacheTime: Infinity, - staleTime: Infinity, - }); - const placeholder = useMemo( - () => ({ - selectedEventId: null, - titleNow: '', - }), - [], - ); - - return data ?? placeholder; -}; - -export const useTimerProvider = () => { - const { data } = useQuery(TIMER, () => undefined, { - cacheTime: Infinity, - staleTime: Infinity, - }); - const placeholder = useMemo( - () => ({ - clock: 0, - current: null, - secondaryTimer: null, - duration: null, - expectedFinish: null, - startedAt: null, - }), - [], - ); - return data ?? placeholder; -}; - -export const useEventProvider = (eventId) => { - const socket = useSocket(); - - const setPlayback = useMemo(() => ({ - loadEvent: () => socket.emit('set-loadid', eventId), - startEvent: () => socket.emit('set-startid', eventId), - pause: () => socket.emit('set-pause'), - }), [socket]); - - return { setPlayback }; -}; - -export const useSocketProvider = () => { - const queryClient = useQueryClient(); - const socket = useSocket(); - - useEffect(() => { - if (!socket) { - return; - } - - socket.emit('get-ontime-feat-eventlist'); - socket.on('ontime-feat-rundown', (featureData) => { - queryClient.setQueryData(FEAT_RUNDOWN, () => featureData); - }); - - socket.emit('get-ontime-feat-messagecontrol'); - socket.on('ontime-feat-messagecontrol', (featureData) => { - queryClient.setQueryData(FEAT_MESSAGECONTROL, () => featureData); - }); - - socket.emit('get-ontime-feat-playbackcontrol'); - socket.on('ontime-feat-playbackcontrol', (featureData) => { - queryClient.setQueryData(FEAT_PLAYBACKCONTROL, () => featureData); - }); - - socket.emit('get-ontime-feat-info'); - socket.on('ontime-feat-info', (featureData) => { - queryClient.setQueryData(FEAT_INFO, () => featureData); - }); - - socket.emit('get-ontime-feat-cuesheet'); - socket.on('ontime-feat-cuesheet', (featureData) => { - queryClient.setQueryData(FEAT_CUESHEET, () => featureData); - }); - - socket.emit('get-ontime-timer'); - socket.on('ontime-timer', (featureData) => { - queryClient.setQueryData(TIMER, () => featureData); - }); - - return () => { - socket.off('ontime-feat-rundown'); - socket.off('ontime-feat-messagecontrol'); - socket.off('ontime-feat-playbackcontrol'); - socket.off('ontime-feat-info'); - socket.off('ontime-feat-cuesheet'); - socket.off('ontime-timer'); - }; - }, [queryClient, socket]); -}; diff --git a/client/src/common/hooks/useSubscription.tsx b/client/src/common/hooks/useSubscription.tsx index 87103f477..634aa38f7 100644 --- a/client/src/common/hooks/useSubscription.tsx +++ b/client/src/common/hooks/useSubscription.tsx @@ -1,16 +1,11 @@ import { useEffect, useState } from 'react'; -import { useSocket } from '../context/socketContext'; +import socket from '../utils/socket'; export default function useSubscription(topic: string, initialState: T, requestString?: string) { - const socket = useSocket(); const [state, setState] = useState(initialState); useEffect(() => { - if (!socket) { - return; - } - if (requestString) { socket.emit(requestString); } else { diff --git a/client/src/common/utils/socket.ts b/client/src/common/utils/socket.ts new file mode 100644 index 000000000..841fc7ed0 --- /dev/null +++ b/client/src/common/utils/socket.ts @@ -0,0 +1,17 @@ +import { serverURL } from 'common/api/apiConstants'; +import { io } from 'socket.io-client'; + +const socket = io(serverURL, { transports: ['websocket'] }); +const subscriptions = new Set(); + +export function subscribeOnce(key: string, callback: (data: T) => void, requestString?: string) { + if (subscriptions.has(key)) { + return; + } + subscriptions.add(key); + + requestString ? socket.emit(requestString) : socket.emit(`get-${key}`); + socket.on(key, callback); +} + +export default socket; diff --git a/client/src/features/control/__tests__/MessageControl.test.jsx b/client/src/features/control/__tests__/MessageControl.test.jsx index 4343b3346..710f843e1 100644 --- a/client/src/features/control/__tests__/MessageControl.test.jsx +++ b/client/src/features/control/__tests__/MessageControl.test.jsx @@ -1,6 +1,5 @@ import { QueryClientProvider } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; -import SocketProvider from 'common/context/socketContext'; import { queryClientMock } from '../../../__mocks__/QueryClient.mock'; import MessageControl from '../message/MessageControl'; @@ -11,9 +10,7 @@ import MessageControl from '../message/MessageControl'; const MockMessageControl = () => { return ( - - - + ); }; diff --git a/client/src/features/control/__tests__/PlaybackControl.test.jsx b/client/src/features/control/__tests__/PlaybackControl.test.jsx index 88c9132da..4cc5b8863 100644 --- a/client/src/features/control/__tests__/PlaybackControl.test.jsx +++ b/client/src/features/control/__tests__/PlaybackControl.test.jsx @@ -1,6 +1,5 @@ import { QueryClientProvider } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; -import SocketProvider from 'common/context/socketContext'; import { queryClientMock } from '../../../__mocks__/QueryClient.mock'; import PlaybackControl from '../playback/PlaybackControl'; @@ -11,10 +10,8 @@ test('check that playback control renders', async () => { // render without failing render( - - - - + + , ); // Text labels for times diff --git a/client/src/features/control/message/MessageControl.jsx b/client/src/features/control/message/MessageControl.jsx index 622878217..ff4db88e1 100644 --- a/client/src/features/control/message/MessageControl.jsx +++ b/client/src/features/control/message/MessageControl.jsx @@ -3,7 +3,7 @@ import { IoMicOffOutline } from '@react-icons/all-files/io5/IoMicOffOutline'; import { IoMicSharp } from '@react-icons/all-files/io5/IoMicSharp'; import CopyTag from '../../../common/components/osc-tag/CopyTag'; -import { useMessageControlProvider } from '../../../common/hooks/useSocketProvider'; +import { setMessage, useMessageControl } from '../../../common/hooks/useSocket'; import { tooltipDelayMid } from '../../../ontimeConfig'; import InputRow from './InputRow'; @@ -11,7 +11,7 @@ import InputRow from './InputRow'; import style from './MessageControl.module.scss'; export default function MessageControl() { - const { data, setMessage } = useMessageControlProvider(); + const { data } = useMessageControl(); return ( <> diff --git a/client/src/features/control/playback/Playback.tsx b/client/src/features/control/playback/Playback.tsx index 3d4f3cad0..f2eec769b 100644 --- a/client/src/features/control/playback/Playback.tsx +++ b/client/src/features/control/playback/Playback.tsx @@ -3,7 +3,7 @@ import { IoPause } from '@react-icons/all-files/io5/IoPause'; import { IoPlay } from '@react-icons/all-files/io5/IoPlay'; import { IoTimeOutline } from '@react-icons/all-files/io5/IoTimeOutline'; -import { usePlaybackControlProvider } from '../../../common/hooks/useSocketProvider'; +import { setPlayback } from '../../../common/hooks/useSocket'; import { Playstate } from '../../../common/models/OntimeTypes'; import { tooltipDelayMid } from '../../../ontimeConfig'; @@ -20,7 +20,6 @@ interface PlaybackProps { export default function Playback(props: PlaybackProps) { const { playback, selectedId, noEvents } = props; const isRolling = playback === 'roll'; - const { setPlayback } = usePlaybackControlProvider(); return (
diff --git a/client/src/features/control/playback/PlaybackControl.tsx b/client/src/features/control/playback/PlaybackControl.tsx index 082975ede..a341b98bb 100644 --- a/client/src/features/control/playback/PlaybackControl.tsx +++ b/client/src/features/control/playback/PlaybackControl.tsx @@ -1,4 +1,4 @@ -import { usePlaybackControlProvider } from '../../../common/hooks/useSocketProvider'; +import { usePlaybackControl } from '../../../common/hooks/useSocket'; import { Playstate } from '../../../common/models/OntimeTypes'; import PlaybackButtons from './PlaybackButtons'; @@ -7,7 +7,7 @@ import PlaybackTimer from './PlaybackTimer'; import style from './PlaybackControl.module.scss'; export default function PlaybackControl() { - const { data } = usePlaybackControlProvider(); + const { data } = usePlaybackControl(); return (
diff --git a/client/src/features/control/playback/PlaybackTimer.tsx b/client/src/features/control/playback/PlaybackTimer.tsx index 776b6c70f..738afed5a 100644 --- a/client/src/features/control/playback/PlaybackTimer.tsx +++ b/client/src/features/control/playback/PlaybackTimer.tsx @@ -1,10 +1,7 @@ import { Tooltip } from '@chakra-ui/react'; import TimerDisplay from 'common/components/countdown/TimerDisplay'; -import { - usePlaybackControlProvider, - useTimerProvider, -} from '../../../common/hooks/useSocketProvider'; +import { setPlayback, useTimer } from '../../../common/hooks/useSocket'; import { Playstate } from '../../../common/models/OntimeTypes'; import { millisToSeconds } from '../../../common/utils/dateConfig'; import { stringFromMillis } from '../../../common/utils/time'; @@ -21,8 +18,7 @@ interface PlaybackTimerProps { export default function PlaybackTimer(props: PlaybackTimerProps) { const { playback, selectedId } = props; - const { setPlayback } = usePlaybackControlProvider(); - const timerData = useTimerProvider(); + const { data: timerData } = useTimer(); const started = stringFromMillis(timerData.startedAt, true); const finish = stringFromMillis(timerData.expectedFinish, true); const isRolling = playback === 'roll'; diff --git a/client/src/features/control/playback/Transport.tsx b/client/src/features/control/playback/Transport.tsx index c4944b174..7fb19a5e8 100644 --- a/client/src/features/control/playback/Transport.tsx +++ b/client/src/features/control/playback/Transport.tsx @@ -4,7 +4,7 @@ import { IoPlaySkipBack } from '@react-icons/all-files/io5/IoPlaySkipBack'; import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward'; import { IoStop } from '@react-icons/all-files/io5/IoStop'; -import { usePlaybackControlProvider } from '../../../common/hooks/useSocketProvider'; +import { setPlayback } from '../../../common/hooks/useSocket'; import { Playstate } from '../../../common/models/OntimeTypes'; import { tooltipDelayMid } from '../../../ontimeConfig'; @@ -20,7 +20,6 @@ interface TransportProps { export default function Transport(props: TransportProps) { const { playback, selectedId, noEvents } = props; - const { setPlayback } = usePlaybackControlProvider(); const isRolling = playback === 'roll'; return ( diff --git a/client/src/features/info/Info.jsx b/client/src/features/info/Info.jsx index 8adced29f..6f442baa2 100644 --- a/client/src/features/info/Info.jsx +++ b/client/src/features/info/Info.jsx @@ -1,4 +1,4 @@ -import { useInfoProvider } from '../../common/hooks/useSocketProvider'; +import { useInfoPanel } from '../../common/hooks/useSocket'; import InfoLogger from './InfoLogger'; import InfoNif from './InfoNif'; @@ -7,7 +7,7 @@ import InfoTitle from './InfoTitle'; import style from './Info.module.scss'; export default function Info() { - const data = useInfoProvider(); + const { data } = useInfoPanel(); const titlesNow = { title: data.titles.titleNow, diff --git a/client/src/features/info/__tests__/Info.test.jsx b/client/src/features/info/__tests__/Info.test.jsx index 8b3430bbe..72aa4dd4f 100644 --- a/client/src/features/info/__tests__/Info.test.jsx +++ b/client/src/features/info/__tests__/Info.test.jsx @@ -1,6 +1,5 @@ import { QueryClientProvider } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; -import SocketProvider from 'common/context/socketContext'; import { queryClientMock } from '../../../__mocks__/QueryClient.mock'; import Info from '../Info'; @@ -10,10 +9,8 @@ test('check static info render', async () => { // render without failing render( - - - - + + , ); // Info titles diff --git a/client/src/features/rundown/Rundown.jsx b/client/src/features/rundown/Rundown.jsx index b54349f80..59dbb10ce 100644 --- a/client/src/features/rundown/Rundown.jsx +++ b/client/src/features/rundown/Rundown.jsx @@ -9,7 +9,7 @@ import { import Empty from 'common/components/state/Empty'; import { CursorContext } from 'common/context/CursorContext'; import { useEventAction } from 'common/hooks/useEventAction'; -import { useRundownProvider } from 'common/hooks/useSocketProvider'; +import { useRundownEditor } from 'common/hooks/useSocket'; import { duplicateEvent } from 'common/utils/eventsManager'; import { useAtomValue } from 'jotai'; import PropTypes from 'prop-types'; @@ -23,6 +23,8 @@ import style from './Rundown.module.scss'; export default function Rundown(props) { const { entries } = props; + // Todo: add selectedId and nextId to rundown editor hook + const { data } = useRundownEditor(); const { cursor, moveCursorUp, moveCursorDown, moveCursorTo, isCursorLocked } = useContext(CursorContext); const startTimeIsLastEnd = useAtomValue(startTimeIsLastEndAtom); @@ -30,7 +32,6 @@ export default function Rundown(props) { const { addEvent, reorderEvent } = useEventAction(); const cursorRef = createRef(); const showQuickEntry = useAtomValue(showQuickEntryAtom); - const data = useRundownProvider(); const [selectedId] = useSubscription('selected-id', null); const [nextId] = useSubscription('next-id', null); diff --git a/client/src/features/rundown/event-block/EventBlock.tsx b/client/src/features/rundown/event-block/EventBlock.tsx index 6aecb8a67..3d1ae52d7 100644 --- a/client/src/features/rundown/event-block/EventBlock.tsx +++ b/client/src/features/rundown/event-block/EventBlock.tsx @@ -17,7 +17,7 @@ import TooltipActionBtn from 'common/components/buttons/TooltipActionBtn'; import { getAccessibleColour } from 'common/utils/styleUtils'; import { useAtom } from 'jotai'; -import { useEventProvider } from '../../../common/hooks/useSocketProvider'; +import { setEventPlayback } from '../../../common/hooks/useSocket'; import { Playstate } from '../../../common/models/OntimeTypes'; import { tooltipDelayMid } from '../../../ontimeConfig'; import { EventItemActions } from '../RundownEntry'; @@ -80,7 +80,6 @@ export default function EventBlock(props: EventBlockProps) { } = props; const [openId, setOpenId] = useAtom(editorEventId); - const { setPlayback } = useEventProvider(eventId); const [blockTitle, setBlockTitle] = useState(title || ''); const binderColours = colour && getAccessibleColour(colour); @@ -157,7 +156,7 @@ export default function EventBlock(props: EventBlockProps) { icon={selected ? : } disabled={skip} {...blockBtnStyle} - clickHandler={() => setPlayback.loadEvent()} + clickHandler={() => setEventPlayback.loadEvent(eventId)} tabIndex={-1} /> setPlayback.startEvent()} + clickHandler={() => setEventPlayback.startEvent(eventId)} backgroundColor={eventIsPlaying ? '#58A151' : undefined} tabIndex={-1} /> diff --git a/client/src/features/table/TableHeader.jsx b/client/src/features/table/TableHeader.jsx index c98421681..771f6a5f2 100644 --- a/client/src/features/table/TableHeader.jsx +++ b/client/src/features/table/TableHeader.jsx @@ -9,7 +9,7 @@ import PropTypes from 'prop-types'; import { TableSettingsContext } from '../../common/context/TableSettingsContext'; import useFullscreen from '../../common/hooks/useFullscreen'; -import { useTimerProvider } from '../../common/hooks/useSocketProvider'; +import { useTimer } from '../../common/hooks/useSocket'; import useEvent from '../../common/hooks-query/useEvent'; import { formatDisplay, millisToSeconds } from '../../common/utils/dateConfig'; import { formatTime } from '../../common/utils/time'; @@ -22,7 +22,7 @@ import style from './Table.module.scss'; export default function TableHeader({ handleCSVExport, featureData }) { const { followSelected, showSettings, toggleTheme, toggleSettings, toggleFollow } = useContext(TableSettingsContext); - const timer = useTimerProvider(); + const { data: timer } = useTimer(); const { isFullScreen, toggleFullScreen } = useFullscreen(); const { data: event } = useEvent(); diff --git a/client/src/features/table/TableWrapper.jsx b/client/src/features/table/TableWrapper.jsx index 6e16abef2..cd9f83c77 100644 --- a/client/src/features/table/TableWrapper.jsx +++ b/client/src/features/table/TableWrapper.jsx @@ -3,7 +3,7 @@ import { useCallback, useContext, useEffect } from 'react'; import { requestPatchEvent } from '../../common/api/eventsApi'; import { TableSettingsContext } from '../../common/context/TableSettingsContext'; import useMutateEvents from '../../common/hooks/useMutateEvents'; -import { useCuesheetProvider } from '../../common/hooks/useSocketProvider'; +import { useCuesheet } from '../../common/hooks/useSocket'; import useRundown from '../../common/hooks-query/useRundown'; import useUserFields from '../../common/hooks-query/useUserFields'; @@ -16,10 +16,10 @@ import style from './Table.module.scss'; export default function TableWrapper() { const { data: events } = useRundown(); const { data: userFields } = useUserFields(); + const { data: featureData } = useCuesheet(); + const mutation = useMutateEvents(requestPatchEvent); const { theme } = useContext(TableSettingsContext); - const featureData = useCuesheetProvider(); - // Set window title useEffect(() => { document.title = 'ontime - Cuesheet'; diff --git a/client/src/features/viewers/ViewWrapper.jsx b/client/src/features/viewers/ViewWrapper.jsx index ac9208f59..ddbcc68bf 100644 --- a/client/src/features/viewers/ViewWrapper.jsx +++ b/client/src/features/viewers/ViewWrapper.jsx @@ -1,21 +1,20 @@ /* eslint-disable react/display-name */ import { useEffect, useMemo, useState } from 'react'; -import { useSocket } from '../../common/context/socketContext'; -import { useMessageControlProvider } from '../../common/hooks/useSocketProvider'; +import { useMessageControl } from '../../common/hooks/useSocket'; import useSubscription from '../../common/hooks/useSubscription'; import useEvent from '../../common/hooks-query/useEvent'; import useRundown from '../../common/hooks-query/useRundown'; import useViewSettings from '../../common/hooks-query/useViewSettings'; +import socket from '../../common/utils/socket'; const withSocket = (Component) => { return (props) => { const { data: eventsData } = useRundown(); const { data: genData } = useEvent(); const { data: viewSettings } = useViewSettings(); - const { data: messages } = useMessageControlProvider(); + const { data: messages } = useMessageControl(); - const socket = useSocket(); const [publicSelectedId, setPublicSelectedId] = useState(null); const [timer] = useSubscription('timer', { @@ -44,13 +43,9 @@ const withSocket = (Component) => { const [selectedId] = useSubscription('selected-id', null); const [nextId] = useSubscription('next-id', null); const [playback] = useSubscription('playstate', null); - const [onAir] = useSubscription('onAir', false); // Ask for update on load useEffect(() => { - if (!socket) { - return; - } // todo: remove socket.on('publicselected-id', (data) => { setPublicSelectedId(data); diff --git a/server/src/classes/timer/EventTimer.js b/server/src/classes/timer/EventTimer.js index 32fe44182..cffe3eb55 100644 --- a/server/src/classes/timer/EventTimer.js +++ b/server/src/classes/timer/EventTimer.js @@ -136,7 +136,7 @@ export class EventTimer extends Timer { nextEventId: this.nextEventId, playback: this.state, }; - this.socket.send('ontime-feat-rundown', featureData); + this.socket.send('feat-rundown', featureData); } /** @@ -149,7 +149,7 @@ export class EventTimer extends Timer { selectedEventId: this.selectedEventId, numEvents: this.rundown.length, }; - this.socket.send('ontime-feat-playbackcontrol', featureData); + this.socket.send('feat-playbackcontrol', featureData); } /** @@ -164,7 +164,7 @@ export class EventTimer extends Timer { selectedEventIndex: this.selectedEventIndex, numEvents: this.rundown.length, }; - this.socket.send('ontime-feat-info', featureData); + this.socket.send('feat-info', featureData); } _broadcastFeatureCuesheet() { @@ -175,7 +175,7 @@ export class EventTimer extends Timer { numEvents: this.rundown.length, titleNow: this.titles.titleNow, }; - this.socket.send('ontime-feat-cuesheet', featureData); + this.socket.send('feat-cuesheet', featureData); } /**