Refact/feature sockets (#193)

* chore: migrate and upgrade state dependencies
* refactor(eventlist): get data from store
* refactor(messagecontrol): get data from store
* refactor(playbackcontrol): get data from store
* refactor(info): get data from store
* refactor(messagecontrol): handle mutations in hook
* feat(playbackcontrol): get data from store
* refactor(playbackcontrol): handle mutations in hook
* refactor(playbackcontrol): simplify object
* refactor: simplify interfaces
* refactor(InputRow): mixed control + styles
* refactor(socket): cleanup unused endpoints
* refactor(table): prepare table socket endpoint
* refactor(timer): extract timer to own endpoint
* refactor(table): get data from store
* chore: update tests
* refactor(socket): update sockets on cycle
* refactor: remove unnecessary safeguards
This commit is contained in:
Carlos Valente
2022-09-06 21:11:24 +02:00
committed by GitHub
parent 0e450cb6cb
commit b384284175
35 changed files with 667 additions and 630 deletions
-1
View File
@@ -7,7 +7,6 @@
"plugins": [ "plugins": [
"react", "react",
"testing-library", "testing-library",
"jest",
"simple-import-sort" "simple-import-sort"
], ],
"rules": { "rules": {
+5 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "ontime-ui", "name": "ontime-ui",
"version": "1.6.0", "version": "1.7.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@chakra-ui/react": "^2.0.0-next.3", "@chakra-ui/react": "^2.0.0-next.3",
@@ -10,8 +10,10 @@
"@emotion/react": "^11.7.1", "@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0", "@emotion/styled": "^11.6.0",
"@react-icons/all-files": "^4.1.0", "@react-icons/all-files": "^4.1.0",
"@tanstack/react-query": "^4.1.3",
"@tanstack/react-query-devtools": "^4.0.10",
"autosize": "^5.0.1", "autosize": "^5.0.1",
"axios": "^0.25.0", "axios": "^0.27.2",
"color": "^4.2.3", "color": "^4.2.3",
"framer-motion": "^6.3.3", "framer-motion": "^6.3.3",
"jotai": "^1.7.8", "jotai": "^1.7.8",
@@ -21,10 +23,9 @@
"react-dom": "^18.1.0", "react-dom": "^18.1.0",
"react-fast-compare": "^3.2.0", "react-fast-compare": "^3.2.0",
"react-qr-code": "^2.0.5", "react-qr-code": "^2.0.5",
"react-query": "^3.38.0",
"react-router-dom": "^6.3.0", "react-router-dom": "^6.3.0",
"react-table": "^7.7.0", "react-table": "^7.7.0",
"socket.io-client": "^4.5.0", "socket.io-client": "^4.5.1",
"typeface-open-sans": "^1.1.13", "typeface-open-sans": "^1.1.13",
"use-fit-text": "^2.4.0", "use-fit-text": "^2.4.0",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
+3 -1
View File
@@ -1,7 +1,8 @@
import React, { Suspense, useCallback, useEffect } from 'react'; import React, { Suspense, useCallback, useEffect } from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import { ChakraProvider } from '@chakra-ui/react'; import { ChakraProvider } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary'; import ErrorBoundary from 'common/components/errorBoundary/ErrorBoundary';
import { AppContextProvider } from './common/context/AppContext'; import { AppContextProvider } from './common/context/AppContext';
@@ -55,6 +56,7 @@ function App() {
<AppRouter /> <AppRouter />
</Suspense> </Suspense>
</ErrorBoundary> </ErrorBoundary>
<ReactQueryDevtools initialIsOpen={false} />
</div> </div>
</BrowserRouter> </BrowserRouter>
</AppContextProvider> </AppContextProvider>
+2
View File
@@ -4,6 +4,7 @@ import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import { ALIASES } from './common/api/apiConstants'; import { ALIASES } from './common/api/apiConstants';
import { getAliases } from './common/api/ontimeApi'; import { getAliases } from './common/api/ontimeApi';
import { useFetch } from './common/hooks/useFetch'; import { useFetch } from './common/hooks/useFetch';
import { useSocketProvider } from './common/hooks/useSocketProvider';
import withSocket from './features/viewers/ViewWrapper'; import withSocket from './features/viewers/ViewWrapper';
const Editor = lazy(() => import('features/editors/ProtectedEditor')); const Editor = lazy(() => import('features/editors/ProtectedEditor'));
@@ -35,6 +36,7 @@ const MessageControl = lazy(() => import('features/control/message/MessageContro
const Info = lazy(() => import('features/info/InfoExport')); const Info = lazy(() => import('features/info/InfoExport'));
export default function AppRouter() { export default function AppRouter() {
useSocketProvider();
const { data } = useFetch(ALIASES, getAliases); const { data } = useFetch(ALIASES, getAliases);
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
+1 -1
View File
@@ -1,4 +1,4 @@
import {QueryClient} from "react-query"; import { QueryClient } from '@tanstack/react-query';
export const queryClientMock = new QueryClient({ export const queryClientMock = new QueryClient({
defaultOptions: { defaultOptions: {
+14 -7
View File
@@ -1,11 +1,18 @@
export const STATIC_PORT = 4001; export const STATIC_PORT = 4001;
export const EVENT_TABLE = 'event'; export const EVENT_TABLE = ['event'];
export const ALIASES = 'aliases'; export const ALIASES = ['aliases'];
export const USERFIELDS = 'userFields'; export const USERFIELDS = ['userFields'];
export const EVENTS_TABLE = 'events'; export const EVENTS_TABLE = ['events'];
export const APP_TABLE = 'appinfo'; export const APP_TABLE = ['appinfo'];
export const OSC_SETTINGS = 'oscSettings'; export const OSC_SETTINGS = ['oscSettings'];
export const APP_SETTINGS = 'appSettings'; export const APP_SETTINGS = ['appSettings'];
export const FEAT_EVENTLIST = ['feat-eventList'];
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'];
/** /**
* @description finds server path given the current location, it * @description finds server path given the current location, it
@@ -5,9 +5,11 @@ import PropTypes from 'prop-types';
import styles from './TimerDisplay.module.scss'; import styles from './TimerDisplay.module.scss';
const TimerDisplay = ({ time, small, isNegative, hideZeroHours }) => { const TimerDisplay = ({ time, small, isNegative, hideZeroHours }) => {
// prepare display string
const display = const display =
time != null && !isNaN(time) ? formatDisplay(time, hideZeroHours) : '-- : -- : --'; (time === null || typeof time === 'undefined' || isNaN(time))
? '-- : -- : --'
: formatDisplay(time, hideZeroHours);
const classes = `${small ? styles.countdownClockSmall : styles.countdownClock} const classes = `${small ? styles.countdownClockSmall : styles.countdownClock}
${isNegative ? styles.negative : ''}`; ${isNegative ? styles.negative : ''}`;
+2 -2
View File
@@ -1,4 +1,5 @@
import { useQuery } from 'react-query'; import { useQuery } from '@tanstack/react-query';
const refetchIntervalMs = 10000; const refetchIntervalMs = 10000;
/** /**
@@ -10,7 +11,6 @@ export const useFetch = (namespace, fn) => {
const { data, status, isError, refetch } = useQuery(namespace, fn, { const { data, status, isError, refetch } = useQuery(namespace, fn, {
refetchInterval: refetchIntervalMs, refetchInterval: refetchIntervalMs,
cacheTime: Infinity, cacheTime: Infinity,
notifyOnChangeProps: 'tracked',
}); });
return { data, status, isError, refetch }; return { data, status, isError, refetch };
+1 -1
View File
@@ -1,4 +1,4 @@
import { useMutation, useQueryClient } from 'react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { EVENTS_TABLE } from '../api/apiConstants'; import { EVENTS_TABLE } from '../api/apiConstants';
@@ -0,0 +1,239 @@
import { useCallback, useEffect, useMemo } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import {
FEAT_CUESHEET,
FEAT_EVENTLIST,
FEAT_INFO,
FEAT_MESSAGECONTROL,
FEAT_PLAYBACKCONTROL,
TIMER,
} from '../api/apiConstants';
import { useSocket } from '../context/socketContext';
export const useEventListProvider = () => {
const { data } = useQuery(FEAT_EVENTLIST, () => undefined, {
cacheTime: Infinity,
staleTime: Infinity,
});
const placeholder = useMemo(
() => ({
selectedEventId: null,
nextEventId: 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 useSocketProvider = () => {
const queryClient = useQueryClient();
const socket = useSocket();
useEffect(() => {
if (!socket) {
return;
}
socket.emit('get-ontime-feat-eventlist');
socket.on('ontime-feat-eventlist', (featureData) => {
queryClient.setQueryData(FEAT_EVENTLIST, () => 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-eventlist');
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]);
};
@@ -1,16 +1,21 @@
import React from 'react'; import React from 'react';
import { QueryClientProvider } from '@tanstack/react-query';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import SocketProvider from 'common/context/socketContext'; import SocketProvider from 'common/context/socketContext';
import { queryClientMock } from '../../../__mocks__/QueryClient.mock';
import MessageControl from '../message/MessageControl'; import MessageControl from '../message/MessageControl';
// need to inject the socket provider to make component // need to inject the socket provider to make component
// render without failing // render without failing
const MockMessageControl = () => { const MockMessageControl = () => {
return ( return (
<SocketProvider> <QueryClientProvider client={queryClientMock}>
<MessageControl /> <SocketProvider>
</SocketProvider> <MessageControl />
</SocketProvider>
</QueryClientProvider>
); );
}; };
@@ -1,16 +1,21 @@
import React from 'react'; import React from 'react';
import { QueryClientProvider } from '@tanstack/react-query';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import SocketProvider from 'common/context/socketContext'; import SocketProvider from 'common/context/socketContext';
import { queryClientMock } from '../../../__mocks__/QueryClient.mock';
import PlaybackControl from '../playback/PlaybackControl'; import PlaybackControl from '../playback/PlaybackControl';
test('check that playback control renders', async () => { test('check that playback control renders', async () => {
// need to inject the socket provider to make component // need to inject the socket provider to make component
// render without failing // render without failing
render( render(
<SocketProvider> <QueryClientProvider client={queryClientMock}>
<PlaybackControl /> <SocketProvider>
</SocketProvider> <PlaybackControl />
</SocketProvider>
</QueryClientProvider>
); );
// Text labels for times // Text labels for times
@@ -1,4 +1,4 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { IconButton } from '@chakra-ui/button'; import { IconButton } from '@chakra-ui/button';
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable'; import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
import { Tooltip } from '@chakra-ui/tooltip'; import { Tooltip } from '@chakra-ui/tooltip';
@@ -11,14 +11,27 @@ import style from './MessageControl.module.scss';
export default function InputRow(props) { export default function InputRow(props) {
const { label, placeholder, text, visible, actionHandler, changeHandler } = props; const { label, placeholder, text, visible, actionHandler, changeHandler } = props;
const [inputText, setInputText] = useState(text || '');
const handleInputChange = (newValue) => {
setInputText(newValue);
changeHandler(newValue);
};
useEffect(() => {
if (text) {
setInputText(text);
}
}, [text]);
return ( return (
<div className={`${visible ? style.inputRowActive: ''}`}> <div className={`${visible ? style.inputRowActive: ''}`}>
<span className={style.label}>{label}</span> <span className={style.label}>{label}</span>
<div className={style.inputItems}> <div className={style.inputItems}>
<Editable <Editable
onChange={(event) => changeHandler(event)} onChange={(newValue) => handleInputChange(newValue)}
value={text} value={inputText}
placeholder={placeholder} placeholder={placeholder}
className={style.inline} className={style.inline}
color={text === '' ? '#666' : 'inherit'} color={text === '' ? '#666' : 'inherit'}
@@ -1,10 +1,10 @@
import React, { useCallback, useEffect, useState } from 'react'; import React from 'react';
import { IconButton } from '@chakra-ui/button'; import { IconButton } from '@chakra-ui/button';
import { Tooltip } from '@chakra-ui/tooltip'; import { Tooltip } from '@chakra-ui/tooltip';
import { IoMicOffOutline } from '@react-icons/all-files/io5/IoMicOffOutline'; import { IoMicOffOutline } from '@react-icons/all-files/io5/IoMicOffOutline';
import { IoMicSharp } from '@react-icons/all-files/io5/IoMicSharp'; import { IoMicSharp } from '@react-icons/all-files/io5/IoMicSharp';
import { useSocket } from 'common/context/socketContext';
import { useMessageControlProvider } from '../../../common/hooks/useSocketProvider';
import { tooltipDelayMid } from '../../../ontimeConfig'; import { tooltipDelayMid } from '../../../ontimeConfig';
import InputRow from './InputRow'; import InputRow from './InputRow';
@@ -12,88 +12,7 @@ import InputRow from './InputRow';
import style from './MessageControl.module.scss'; import style from './MessageControl.module.scss';
export default function MessageControl() { export default function MessageControl() {
const socket = useSocket(); const { data, setMessage } = useMessageControlProvider();
const [pres, setPres] = useState({
text: '',
visible: false,
});
const [publ, setPubl] = useState({
text: '',
visible: false,
});
const [lower, setLower] = useState({
text: '',
visible: false,
});
const [onAir, setonAir] = useState(false);
useEffect(() => {
if (socket == null) return;
// Handle timer messages
socket.on('messages-timer', (data) => {
setPres({ ...data });
});
// Handle public messages
socket.on('messages-public', (data) => {
setPubl({ ...data });
});
// Handle lower third messages
socket.on('messages-lower', (data) => {
setLower({ ...data });
});
// Handle lower third messages
socket.on('onAir', (data) => {
setonAir(data);
});
// Ask for up to date data
socket.emit('get-messages');
// Ask for onAir state
socket.emit('get-onAir');
// Clear listeners
return () => {
socket.off('messages-public');
socket.off('messages-timer');
socket.off('messages-lower');
socket.off('onAir');
};
}, [socket]);
const messageControl = useCallback(
(action, payload) => {
switch (action) {
case 'pres-text':
socket.emit('set-timer-message-text', payload);
break;
case 'toggle-pres-visible':
socket.emit('set-timer-message-visible', payload);
break;
case 'publ-text':
socket.emit('set-public-message-text', payload);
break;
case 'toggle-publ-visible':
socket.emit('set-public-message-visible', payload);
break;
case 'lower-text':
socket.emit('set-lower-message-text', payload);
break;
case 'toggle-lower-visible':
socket.emit('set-lower-message-visible', payload);
break;
case 'toggle-onAir':
socket.emit('set-onAir', payload);
break;
default:
break;
}
},
[socket]
);
return ( return (
<> <>
@@ -101,37 +20,37 @@ export default function MessageControl() {
<InputRow <InputRow
label='Timer screen message' label='Timer screen message'
placeholder='only the presenter screens see this' placeholder='only the presenter screens see this'
text={pres.text} text={data.presenter.text}
visible={pres.visible} visible={data.presenter.visible}
changeHandler={(event) => messageControl('pres-text', event)} changeHandler={(newValue) => setMessage.presenterText(newValue)}
actionHandler={() => messageControl('toggle-pres-visible', !pres.visible)} actionHandler={() => setMessage.presenterVisible(!data.presenter.visible)}
/> />
<InputRow <InputRow
label='Public screen message' label='Public screen message'
placeholder='public screens will render this' placeholder='public screens will render this'
text={publ.text} text={data.public.text}
visible={publ.visible} visible={data.public.visible}
changeHandler={(event) => messageControl('publ-text', event)} changeHandler={(newValue) => setMessage.publicText(newValue)}
actionHandler={() => messageControl('toggle-publ-visible', !publ.visible)} actionHandler={() => setMessage.publicVisible(!data.public.visible)}
/> />
<InputRow <InputRow
label='Lower third message' label='Lower third message'
placeholder='visible in lower third screen' placeholder='visible in lower third screen'
text={lower.text} text={data.lower.text}
visible={lower.visible} visible={data.lower.visible}
changeHandler={(event) => messageControl('lower-text', event)} changeHandler={(newValue) => setMessage.lowerText(newValue)}
actionHandler={() => messageControl('toggle-lower-visible', !lower.visible)} actionHandler={() => setMessage.lowerVisible(!data.lower.visible)}
/> />
</div> </div>
<div className={style.onAirToggle}> <div className={style.onAirToggle}>
<Tooltip label={onAir ? 'Go Off Air' : 'Go On Air'} openDelay={tooltipDelayMid}> <Tooltip label={data.onAir ? 'Go Off Air' : 'Go On Air'} openDelay={tooltipDelayMid}>
<IconButton <IconButton
className={style.btn} className={style.btn}
size='md' size='md'
icon={onAir ? <IoMicSharp size='24px' /> : <IoMicOffOutline size='24px' />} icon={data.onAir ? <IoMicSharp size='24px' /> : <IoMicOffOutline size='24px' />}
colorScheme='blue' colorScheme='blue'
variant={onAir ? 'solid' : 'outline'} variant={data.onAir ? 'solid' : 'outline'}
onClick={() => messageControl('toggle-onAir', !onAir)} onClick={() => setMessage.onAir(!data.onAir)}
aria-label='Toggle On Air' aria-label='Toggle On Air'
/> />
</Tooltip> </Tooltip>
@@ -29,6 +29,7 @@
border-radius: 4px; border-radius: 4px;
background-color: $input-bg; background-color: $input-bg;
border: $input-border; border: $input-border;
overflow-x: hidden;
} }
.padleft { .padleft {
@@ -42,11 +43,12 @@
.inputRowActive { .inputRowActive {
.label { .label {
color: $light-bg; color: $action-blue;
} }
.inline { .inline {
background-color: $light-bg-transparent; background-color: $light-bg-transparent;
border-color: $action-blue;
} }
} }
} }
@@ -15,18 +15,18 @@ export default function Playback(props) {
<div className={style.playbackContainer}> <div className={style.playbackContainer}>
<StartIconBtn <StartIconBtn
active={playback === 'start'} active={playback === 'start'}
clickhandler={() => playbackControl('start')} clickhandler={() => playbackControl.start()}
disabled={!selectedId || isRolling || noEvents} disabled={!selectedId || isRolling || noEvents}
/> />
<PauseIconBtn <PauseIconBtn
active={playback === 'pause'} active={playback === 'pause'}
clickhandler={() => playbackControl('pause')} clickhandler={() => playbackControl.pause()}
disabled={!selectedId || isRolling || noEvents || playback !== 'start'} disabled={!selectedId || isRolling || noEvents || playback !== 'start'}
/> />
<RollIconBtn <RollIconBtn
active={playback === 'roll'} active={playback === 'roll'}
disabled={playback === 'roll' || noEvents} disabled={playback === 'roll' || noEvents}
clickhandler={() => playbackControl('roll')} clickhandler={() => playbackControl.roll()}
/> />
</div> </div>
); );
@@ -35,6 +35,10 @@ export default function Playback(props) {
Playback.propTypes = { Playback.propTypes = {
playback: PropTypes.string, playback: PropTypes.string,
selectedId: PropTypes.string, selectedId: PropTypes.string,
playbackControl: PropTypes.func.isRequired, playbackControl: PropTypes.shape({
start: PropTypes.func,
pause: PropTypes.func,
roll: PropTypes.func,
}).isRequired,
noEvents: PropTypes.bool.isRequired, noEvents: PropTypes.bool.isRequired,
}; };
@@ -37,6 +37,6 @@ export default memo(PlaybackButtons, areEqual);
PlaybackButtons.propTypes = { PlaybackButtons.propTypes = {
playback: PropTypes.string, playback: PropTypes.string,
selectedId: PropTypes.string, selectedId: PropTypes.string,
playbackControl: PropTypes.func.isRequired, playbackControl: PropTypes.object,
noEvents: PropTypes.bool.isRequired, noEvents: PropTypes.bool.isRequired,
}; };
@@ -1,5 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react'; import React from 'react';
import { useSocket } from 'common/context/socketContext';
import { usePlaybackControlProvider } from '../../../common/hooks/useSocketProvider';
import PlaybackButtons from './PlaybackButtons'; import PlaybackButtons from './PlaybackButtons';
import PlaybackTimer from './PlaybackTimer'; import PlaybackTimer from './PlaybackTimer';
@@ -7,112 +8,20 @@ import PlaybackTimer from './PlaybackTimer';
import style from './PlaybackControl.module.scss'; import style from './PlaybackControl.module.scss';
export default function PlaybackControl() { export default function PlaybackControl() {
const socket = useSocket(); const { data, setPlayback } = usePlaybackControlProvider();
const [playback, setPlayback] = useState(null);
const [timer, setTimer] = useState({
clock: null,
running: null,
startedAt: null,
expectedFinish: null,
secondary: null,
});
const [selectedId, setSelectedId] = useState(null);
const [numEvents, setNumEvents] = useState(0);
const resetTimer = () => {
setTimer({
running: null,
startedAt: null,
expectedFinish: null,
secondary: null,
});
};
// handle incoming messages
useEffect(() => {
if (socket == null) return;
socket.emit('get-timer');
socket.emit('get-playstate');
socket.emit('get-selected-id');
socket.emit('get-numevents');
// Handle playstate
socket.on('playstate', (data) => {
setPlayback(data);
});
// Handle timer
socket.on('timer', (data) => {
setTimer(data);
});
// Handle selected event
socket.on('selected-id', (data) => {
setSelectedId(data);
});
socket.on('numevents', (data) => {
setNumEvents(data);
});
// Clear listener
return () => {
socket.off('playstate');
socket.off('timer');
socket.off('selected-id');
socket.off('numevents');
};
}, [socket]);
const playbackControl = useCallback(
(action) => {
switch (action) {
case 'start':
socket.emit('set-playstate', 'start');
break;
case 'pause':
socket.emit('set-playstate', 'pause');
break;
case 'roll':
socket.emit('set-playstate', 'roll');
break;
case 'previous':
socket.emit('set-playstate', 'previous');
resetTimer();
break;
case 'next':
socket.emit('set-playstate', 'next');
resetTimer();
break;
case 'unload':
socket.emit('set-playstate', 'unload');
resetTimer();
break;
case 'reload':
socket.emit('set-playstate', 'reload');
resetTimer();
break;
default:
break;
}
},
[socket]
);
return ( return (
<div className={style.mainContainer}> <div className={style.mainContainer}>
<PlaybackTimer <PlaybackTimer
timer={timer} playback={data.playback}
playback={playback} selectedId={data.selectedEventId}
selectedId={selectedId} handleIncrement={(amount) => setPlayback.delay(amount)}
handleIncrement={(amount) => socket.emit('increment-timer', amount)}
/> />
<PlaybackButtons <PlaybackButtons
playback={playback} playback={data.playback}
selectedId={selectedId} selectedId={data.selectedEventId}
noEvents={numEvents < 1} noEvents={data.numEvents < 1}
playbackControl={playbackControl} playbackControl={setPlayback}
/> />
</div> </div>
); );
@@ -4,6 +4,8 @@ import { Tooltip } from '@chakra-ui/react';
import TimerDisplay from 'common/components/countdown/TimerDisplay'; import TimerDisplay from 'common/components/countdown/TimerDisplay';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTimerProvider } from '../../../common/hooks/useSocketProvider';
import { millisToSeconds } from '../../../common/utils/dateConfig';
import { stringFromMillis } from '../../../common/utils/time'; import { stringFromMillis } from '../../../common/utils/time';
import { tooltipDelayMid } from '../../../ontimeConfig'; import { tooltipDelayMid } from '../../../ontimeConfig';
@@ -11,12 +13,7 @@ import style from './PlaybackControl.module.scss';
const areEqual = (prevProps, nextProps) => { const areEqual = (prevProps, nextProps) => {
return ( return (
prevProps.timer.running === nextProps.timer.running &&
prevProps.timer.isNegative === nextProps.timer.isNegative &&
prevProps.timer.expectedFinish === nextProps.timer.expectedFinish &&
prevProps.timer.startedAt === nextProps.timer.startedAt &&
prevProps.playback === nextProps.playback && prevProps.playback === nextProps.playback &&
prevProps.timer.secondary === nextProps.timer.secondary &&
prevProps.selectedId === nextProps.selectedId prevProps.selectedId === nextProps.selectedId
); );
}; };
@@ -29,12 +26,14 @@ const incrementProps = {
}; };
const PlaybackTimer = (props) => { const PlaybackTimer = (props) => {
const { timer, playback, handleIncrement, selectedId } = props; const { playback, handleIncrement, selectedId } = props;
const started = stringFromMillis(timer.startedAt, true); const timerData = useTimerProvider();
const finish = stringFromMillis(timer.expectedFinish, true); const started = stringFromMillis(timerData.startedAt, true);
const finish = stringFromMillis(timerData.expectedFinish, true);
const isRolling = playback === 'roll'; const isRolling = playback === 'roll';
const isWaiting = timer.secondary > 0 && timer.running == null; const isWaiting = timerData.secondaryTimer > 0 && timerData.current == null;
const disableButtons = selectedId == null || isRolling; const disableButtons = selectedId == null || isRolling;
const isOvertime = timerData.current < 0;
return ( return (
<div className={style.timeContainer}> <div className={style.timeContainer}>
@@ -42,13 +41,13 @@ const PlaybackTimer = (props) => {
<Tooltip label='Roll mode active'> <Tooltip label='Roll mode active'>
<div className={isRolling ? style.indRollActive : style.indRoll} /> <div className={isRolling ? style.indRollActive : style.indRoll} />
</Tooltip> </Tooltip>
<div className={timer.isNegative ? style.indNegativeActive : style.indNegative} /> <div className={isOvertime ? style.indNegativeActive : style.indNegative} />
<div className={style.indDelay} /> <div className={style.indDelay} />
</div> </div>
<div className={style.timer}> <div className={style.timer}>
<TimerDisplay <TimerDisplay
time={isWaiting ? timer.secondary : timer.running} time={isWaiting ? millisToSeconds(timerData.secondaryTimer) : millisToSeconds(timerData.current)}
isNegative={timer.isNegative} isNegative={isOvertime}
small small
/> />
</div> </div>
@@ -118,7 +117,6 @@ const PlaybackTimer = (props) => {
export default memo(PlaybackTimer, areEqual); export default memo(PlaybackTimer, areEqual);
PlaybackTimer.propTypes = { PlaybackTimer.propTypes = {
timer: PropTypes.object.isRequired,
playback: PropTypes.string, playback: PropTypes.string,
handleIncrement: PropTypes.func.isRequired, handleIncrement: PropTypes.func.isRequired,
selectedId: PropTypes.string, selectedId: PropTypes.string,
@@ -16,25 +16,25 @@ export default function Transport(props) {
return ( return (
<div className={style.playbackContainer}> <div className={style.playbackContainer}>
<TransportIconBtn <TransportIconBtn
clickHandler={() => playbackControl('previous')} clickHandler={() => playbackControl.previous()}
disabled={isRolling || noEvents} disabled={isRolling || noEvents}
tooltip='Previous event' tooltip='Previous event'
icon={<IoPlaySkipBack size='22px' />} icon={<IoPlaySkipBack size='22px' />}
/> />
<TransportIconBtn <TransportIconBtn
clickHandler={() => playbackControl('next')} clickHandler={() => playbackControl.next()}
disabled={isRolling || noEvents} disabled={isRolling || noEvents}
tooltip='Next event' tooltip='Next event'
icon={<IoPlaySkipForward size='22px' />} icon={<IoPlaySkipForward size='22px' />}
/> />
<TransportIconBtn <TransportIconBtn
clickHandler={() => playbackControl('reload')} clickHandler={() => playbackControl.reload()}
disabled={selectedId == null || isRolling || noEvents} disabled={selectedId == null || isRolling || noEvents}
tooltip='Reload event' tooltip='Reload event'
icon={<IoArrowUndo size='22px' />} icon={<IoArrowUndo size='22px' />}
/> />
<UnloadIconBtn <UnloadIconBtn
clickHandler={() => playbackControl('unload')} clickHandler={() => playbackControl.stop()}
disabled={(selectedId == null && !isRolling) || noEvents} disabled={(selectedId == null && !isRolling) || noEvents}
/> />
</div> </div>
@@ -44,6 +44,11 @@ export default function Transport(props) {
Transport.propTypes = { Transport.propTypes = {
playback: PropTypes.string, playback: PropTypes.string,
selectedId: PropTypes.string, selectedId: PropTypes.string,
playbackControl: PropTypes.func.isRequired, playbackControl: PropTypes.shape({
previous: PropTypes.func,
next: PropTypes.func,
reload: PropTypes.func,
stop: PropTypes.func,
}).isRequired,
noEvents: PropTypes.bool.isRequired, noEvents: PropTypes.bool.isRequired,
}; };
+8 -34
View File
@@ -1,11 +1,11 @@
import React, { createRef, useCallback, useContext, useEffect, useState } from 'react'; import React, { createRef, useCallback, useContext, useEffect } from 'react';
import { DragDropContext, Droppable } from 'react-beautiful-dnd'; import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import Empty from 'common/components/state/Empty'; import Empty from 'common/components/state/Empty';
import { useSocket } from 'common/context/socketContext';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { showQuickEntryAtom } from '../../../common/atoms/LocalEventSettings'; import { showQuickEntryAtom } from '../../../common/atoms/LocalEventSettings';
import { CursorContext } from '../../../common/context/CursorContext'; import { CursorContext } from '../../../common/context/CursorContext';
import { useEventListProvider } from '../../../common/hooks/useSocketProvider';
import EntryBlock from '../EntryBlock/EntryBlock'; import EntryBlock from '../EntryBlock/EntryBlock';
import EventListItem from './EventListItem'; import EventListItem from './EventListItem';
@@ -16,11 +16,9 @@ export default function EventList(props) {
const { events, eventsHandler } = props; const { events, eventsHandler } = props;
const { cursor, moveCursorUp, moveCursorDown, setCursor, isCursorLocked } = const { cursor, moveCursorUp, moveCursorDown, setCursor, isCursorLocked } =
useContext(CursorContext); useContext(CursorContext);
const socket = useSocket();
const [selectedId, setSelectedId] = useState(null);
const [nextId, setNextId] = useState(null);
const cursorRef = createRef(); const cursorRef = createRef();
const showQuickEntry = useAtomValue(showQuickEntryAtom); const showQuickEntry = useAtomValue(showQuickEntryAtom);
const data = useEventListProvider();
const insertAtCursor = useCallback( const insertAtCursor = useCallback(
(type, cursor) => { (type, cursor) => {
@@ -90,30 +88,6 @@ export default function EventList(props) {
}; };
}, [handleKeyPress, cursor, events, setCursor]); }, [handleKeyPress, cursor, events, setCursor]);
// handle incoming messages
useEffect(() => {
if (socket == null) return;
// ask for playstate
socket.emit('get-selected');
socket.emit('get-next-id');
// Handle playstate
socket.on('selected', (data) => {
setSelectedId(data.id);
});
socket.on('next-id', (data) => {
setNextId(data);
});
// Clear listener
return () => {
socket.off('selected');
socket.off('next-id');
};
}, [socket]);
// when cursor moves, view should follow // when cursor moves, view should follow
useEffect(() => { useEffect(() => {
if (cursorRef.current == null) return; if (cursorRef.current == null) return;
@@ -129,14 +103,14 @@ export default function EventList(props) {
// or cursor settings changed // or cursor settings changed
useEffect(() => { useEffect(() => {
// and if we are locked // and if we are locked
if (!isCursorLocked || selectedId == null) return; if (!isCursorLocked || data.selectedEventId == null) return;
// move cursor // move cursor
let gotoIndex = -1; let gotoIndex = -1;
let found = false; let found = false;
for (const e of events) { for (const e of events) {
gotoIndex++; gotoIndex++;
if (e.id === selectedId) { if (e.id === data.selectedEventId) {
found = true; found = true;
break; break;
} }
@@ -146,7 +120,7 @@ export default function EventList(props) {
setCursor(gotoIndex); setCursor(gotoIndex);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedId, isCursorLocked]); }, [data.selectedEventId, isCursorLocked]);
// DND // DND
const handleOnDragEnd = useCallback( const handleOnDragEnd = useCallback(
@@ -210,8 +184,8 @@ export default function EventList(props) {
index={index} index={index}
eventIndex={eventIndex} eventIndex={eventIndex}
data={e} data={e}
selected={selectedId === e.id} selected={data.selectedEventId === e.id}
next={nextId === e.id} next={data.nextEventId === e.id}
eventsHandler={eventsHandler} eventsHandler={eventsHandler}
delay={cumulativeDelay} delay={cumulativeDelay}
previousEnd={previousEnd} previousEnd={previousEnd}
@@ -1,5 +1,5 @@
import React, { useCallback, useContext, useEffect, useState } from 'react'; import React, { useCallback, useContext, useEffect, useState } from 'react';
import { useMutation, useQueryClient } from 'react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { EVENTS_TABLE } from 'common/api/apiConstants'; import { EVENTS_TABLE } from 'common/api/apiConstants';
import { import {
fetchAllEvents, fetchAllEvents,
+20 -66
View File
@@ -1,5 +1,6 @@
import React, { useEffect,useState } from 'react'; import React from 'react';
import { useSocket } from 'common/context/socketContext';
import { useInfoProvider } from '../../common/hooks/useSocketProvider';
import InfoLogger from './InfoLogger'; import InfoLogger from './InfoLogger';
import InfoNif from './InfoNif'; import InfoNif from './InfoNif';
@@ -8,75 +9,28 @@ import InfoTitle from './InfoTitle';
import style from './Info.module.scss'; import style from './Info.module.scss';
export default function Info() { export default function Info() {
const socket = useSocket(); const data = useInfoProvider();
const [titles, setTitles] = useState({
titleNow: '',
subtitleNow: '',
presenterNow: '',
noteNow: '',
titleNext: '',
subtitleNext: '',
presenterNext: '',
noteNext: ''
});
const [selected, setSelected] = useState('No events');
const [playback, setPlayback] = useState(null);
// handle incoming messages
useEffect(() => {
if (socket == null) return;
// Ask for titles
socket.emit('get-titles');
// Handle titles
socket.on('titles', (data) => {
setTitles(data);
});
// Handle playstate
socket.on('playstate', (data) => {
setPlayback(data);
});
// Ask for selection data
socket.emit('get-selected');
// Handle selection data
socket.on('selected', (data) => {
if (data.total === 0 || data.total == null) {
setSelected('No events');
} else {
const formattedCurrent = `Event ${
data.index != null ? data.index + 1 : '-'
}/${data.total ? data.total : '-'}`;
setSelected(formattedCurrent);
}
});
// Clear listener
return () => {
socket.off('titles');
socket.off('selected');
socket.off('playstate');
};
}, [socket]);
// prepare data
const titlesNow = { const titlesNow = {
title: titles.titleNow, title: data.titles.titleNow,
subtitle: titles.subtitleNow, subtitle: data.titles.subtitleNow,
presenter: titles.presenterNow, presenter: data.titles.presenterNow,
note: titles.noteNow note: data.titles.noteNow,
}; };
const titlesNext = { const titlesNext = {
title: titles.titleNext, title: data.titles.titleNext,
subtitle: titles.subtitleNext, subtitle: data.titles.subtitleNext,
presenter: titles.presenterNext, presenter: data.titles.presenterNext,
note: titles.noteNext note: data.titles.noteNext,
}; };
const selected = !data.numEvents
? 'No events'
: `Event ${data.selectedEventIndex != null ? data.selectedEventIndex + 1 : '-'}/${
data.numEvents ? data.numEvents : '-'
}`;
return ( return (
<> <>
<div className={style.main}> <div className={style.main}>
@@ -84,8 +38,8 @@ export default function Info() {
<span>{selected}</span> <span>{selected}</span>
</div> </div>
<InfoNif /> <InfoNif />
<InfoTitle title='Now' data={titlesNow} roll={playback === 'roll'} /> <InfoTitle title='Now' data={titlesNow} roll={data.playback === 'roll'} />
<InfoTitle title='Next' data={titlesNext} roll={playback === 'roll'} /> <InfoTitle title='Next' data={titlesNext} roll={data.playback === 'roll'} />
<InfoLogger /> <InfoLogger />
</> </>
); );
@@ -1,17 +1,16 @@
import React from 'react'; import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query'; import { QueryClientProvider } from '@tanstack/react-query';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import SocketProvider from 'common/context/socketContext'; import SocketProvider from 'common/context/socketContext';
import { queryClientMock } from '../../../__mocks__/QueryClient.mock';
import Info from '../Info'; import Info from '../Info';
const queryClient = new QueryClient();
test('check static info render', async () => { test('check static info render', async () => {
// need to inject the socket provider to make component // need to inject the socket provider to make component
// render without failing // render without failing
render( render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClientMock}>
<SocketProvider> <SocketProvider>
<Info /> <Info />
</SocketProvider> </SocketProvider>
+1 -1
View File
@@ -1,5 +1,4 @@
import React, { useCallback, useContext, useEffect, useRef } from 'react'; import React, { useCallback, useContext, useEffect, useRef } from 'react';
import { useMutation, useQueryClient } from 'react-query';
import { VStack } from '@chakra-ui/react'; import { VStack } from '@chakra-ui/react';
import { FiDownload } from '@react-icons/all-files/fi/FiDownload'; import { FiDownload } from '@react-icons/all-files/fi/FiDownload';
import { FiHelpCircle } from '@react-icons/all-files/fi/FiHelpCircle'; import { FiHelpCircle } from '@react-icons/all-files/fi/FiHelpCircle';
@@ -7,6 +6,7 @@ import { FiMaximize } from '@react-icons/all-files/fi/FiMaximize';
import { FiMinimize } from '@react-icons/all-files/fi/FiMinimize'; import { FiMinimize } from '@react-icons/all-files/fi/FiMinimize';
import { FiSettings } from '@react-icons/all-files/fi/FiSettings'; import { FiSettings } from '@react-icons/all-files/fi/FiSettings';
import { FiUpload } from '@react-icons/all-files/fi/FiUpload'; import { FiUpload } from '@react-icons/all-files/fi/FiUpload';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { EVENTS_TABLE } from 'common/api/apiConstants'; import { EVENTS_TABLE } from 'common/api/apiConstants';
import { downloadEvents, uploadEvents } from 'common/api/ontimeApi'; import { downloadEvents, uploadEvents } from 'common/api/ontimeApi';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { QueryClientProvider } from 'react-query'; import { QueryClientProvider } from '@tanstack/react-query';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { queryClientMock } from '../../../__mocks__/QueryClient.mock'; import { queryClientMock } from '../../../__mocks__/QueryClient.mock';
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { QueryClientProvider } from 'react-query'; import { QueryClientProvider } from '@tanstack/react-query';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { queryClientMock } from '../../../__mocks__/QueryClient.mock'; import { queryClientMock } from '../../../__mocks__/QueryClient.mock';
+1 -1
View File
@@ -31,7 +31,7 @@ import { defaultColumnOrder, defaultHiddenColumns } from './defaults';
import style from './Table.module.scss'; import style from './Table.module.scss';
export default function OntimeTable({ tableData, userFields, handleUpdate, selectedId }) { export default function OntimeTable({ tableData, userFields, selectedId, handleUpdate }) {
const { followSelected, showSettings } = useContext(TableSettingsContext); const { followSelected, showSettings } = useContext(TableSettingsContext);
const [columnOrder, saveColumnOrder] = useLocalStorage('table-order', defaultColumnOrder); const [columnOrder, saveColumnOrder] = useLocalStorage('table-order', defaultColumnOrder);
const [columnSize, saveColumnSize] = useLocalStorage('table-sizes', {}); const [columnSize, saveColumnSize] = useLocalStorage('table-sizes', {});
+21 -81
View File
@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext } from 'react';
import { Divider } from '@chakra-ui/layout'; import { Divider } from '@chakra-ui/layout';
import { Tooltip } from '@chakra-ui/tooltip'; import { Tooltip } from '@chakra-ui/tooltip';
import { FiSettings } from '@react-icons/all-files/fi/FiSettings'; import { FiSettings } from '@react-icons/all-files/fi/FiSettings';
@@ -10,10 +10,10 @@ import PropTypes from 'prop-types';
import { EVENT_TABLE } from '../../common/api/apiConstants'; import { EVENT_TABLE } from '../../common/api/apiConstants';
import { fetchEvent } from '../../common/api/eventApi'; import { fetchEvent } from '../../common/api/eventApi';
import { useSocket } from '../../common/context/socketContext';
import { TableSettingsContext } from '../../common/context/TableSettingsContext'; import { TableSettingsContext } from '../../common/context/TableSettingsContext';
import { useFetch } from '../../common/hooks/useFetch'; import { useFetch } from '../../common/hooks/useFetch';
import useFullscreen from '../../common/hooks/useFullscreen'; import useFullscreen from '../../common/hooks/useFullscreen';
import { useTimerProvider } from '../../common/hooks/useSocketProvider';
import { formatDisplay } from '../../common/utils/dateConfig'; import { formatDisplay } from '../../common/utils/dateConfig';
import { formatTime } from '../../common/utils/time'; import { formatTime } from '../../common/utils/time';
import { tooltipDelayFast } from '../../ontimeConfig'; import { tooltipDelayFast } from '../../ontimeConfig';
@@ -22,85 +22,18 @@ import PlaybackIcon from './tableElements/PlaybackIcon';
import style from './Table.module.scss'; import style from './Table.module.scss';
export default function TableHeader({ handleCSVExport }) { export default function TableHeader({handleCSVExport, featureData}) {
const { followSelected, showSettings, toggleTheme, toggleSettings, toggleFollow } = const { followSelected, showSettings, toggleTheme, toggleSettings, toggleFollow } =
useContext(TableSettingsContext); useContext(TableSettingsContext);
const { data } = useFetch(EVENT_TABLE, fetchEvent); const timer = useTimerProvider();
const { isFullScreen, toggleFullScreen } = useFullscreen(); const { isFullScreen, toggleFullScreen } = useFullscreen();
const { data: event } = useFetch(EVENT_TABLE, fetchEvent);
const socket = useSocket(); const selected = !featureData.numEvents
const [timer, setTimer] = useState({ ? 'No events'
clock: null, : `Event ${featureData.selectedEventIndex != null ? featureData.selectedEventIndex + 1 : '-'}/${
running: null, featureData.numEvents ? featureData.numEvents : '-'
startedAt: null, }`;
expectedFinish: null,
secondary: null,
});
const [titles, setTitles] = useState({
titleNow: '',
subtitleNow: '',
presenterNow: '',
noteNow: '',
titleNext: '',
subtitleNext: '',
presenterNext: '',
noteNext: '',
});
const [playback, setPlayback] = useState('');
const [selected, setSelected] = useState('');
/**
* Handle incoming data from socket
*/
useEffect(() => {
if (socket == null) return;
// Ask for titles
socket.emit('get-titles');
// Ask for timer
socket.emit('get-timer');
// Ask for playback state
socket.emit('get-playstate');
// Handle titles
socket.on('titles', (data) => {
setTitles(data);
});
// Handle timer
socket.on('timer', (data) => {
setTimer(data);
});
// Handle playstate
socket.on('playstate', (data) => {
setPlayback(data);
});
// Handle selection data
socket.on('selected', (data) => {
if (data.total === 0 || data.total == null) {
setSelected('');
} else {
const formattedCurrent = `${data.index != null ? data.index + 1 : '-'}/${
data.total ? data.total : '-'
}`;
setSelected(formattedCurrent);
}
});
// Clear listener
return () => {
socket.off('titles');
socket.off('timer');
socket.off('playstate');
socket.off('selected');
};
}, [socket]);
// prepare presentation variables // prepare presentation variables
const timerNow = `${timer.running < 0 ? '-' : ''}${formatDisplay(timer.running)}`; const timerNow = `${timer.running < 0 ? '-' : ''}${formatDisplay(timer.running)}`;
@@ -111,12 +44,12 @@ export default function TableHeader({ handleCSVExport }) {
return ( return (
<div className={style.header}> <div className={style.header}>
<div className={style.headerName}>{data?.title || ''}</div> <div className={style.headerName}>{event?.title || ''}</div>
<div className={style.headerNow}>{titles.titleNow}</div> <div className={style.headerNow}>{featureData.titleNow}</div>
<div className={style.headerPlayback}> <div className={style.headerPlayback}>
<span className={style.label}>{selected}</span> <span className={style.label}>{selected}</span>
<br /> <br />
<PlaybackIcon state={playback} /> <PlaybackIcon state={featureData.playback} />
</div> </div>
<div className={style.headerRunning}> <div className={style.headerRunning}>
<span className={style.label}>Running Timer</span> <span className={style.label}>Running Timer</span>
@@ -155,7 +88,7 @@ export default function TableHeader({ handleCSVExport }) {
</Tooltip> </Tooltip>
<Divider /> <Divider />
<Tooltip openDelay={tooltipDelayFast} label='Export to CSV'> <Tooltip openDelay={tooltipDelayFast} label='Export to CSV'>
<span className={style.actionText} onClick={() => handleCSVExport(data)}> <span className={style.actionText} onClick={() => handleCSVExport(event)}>
CSV CSV
</span> </span>
</Tooltip> </Tooltip>
@@ -166,4 +99,11 @@ export default function TableHeader({ handleCSVExport }) {
TableHeader.propTypes = { TableHeader.propTypes = {
handleCSVExport: PropTypes.func.isRequired, handleCSVExport: PropTypes.func.isRequired,
featureData: PropTypes.shape({
playback: PropTypes.string,
selectedEventId: PropTypes.string,
selectedEventIndex: PropTypes.string,
numEvents: PropTypes.number,
titleNow: PropTypes.string,
}),
}; };
+25 -46
View File
@@ -1,12 +1,12 @@
import React, { useCallback, useContext, useEffect, useState } from 'react'; import React, { useCallback, useContext,useEffect } from 'react';
import { EVENTS_TABLE, USERFIELDS } from '../../common/api/apiConstants'; import { EVENTS_TABLE, USERFIELDS } from '../../common/api/apiConstants';
import { fetchAllEvents, requestPatch } from '../../common/api/eventsApi'; import { fetchAllEvents, requestPatch } from '../../common/api/eventsApi';
import { getUserFields } from '../../common/api/ontimeApi'; import { getUserFields } from '../../common/api/ontimeApi';
import { useSocket } from '../../common/context/socketContext';
import { TableSettingsContext } from '../../common/context/TableSettingsContext'; import { TableSettingsContext } from '../../common/context/TableSettingsContext';
import { useFetch } from '../../common/hooks/useFetch'; import { useFetch } from '../../common/hooks/useFetch';
import useMutateEvents from '../../common/hooks/useMutateEvents'; import useMutateEvents from '../../common/hooks/useMutateEvents';
import { useCuesheetProvider } from '../../common/hooks/useSocketProvider';
import OntimeTable from './OntimeTable'; import OntimeTable from './OntimeTable';
import TableHeader from './TableHeader'; import TableHeader from './TableHeader';
@@ -15,35 +15,16 @@ import { makeCSV, makeTable } from './utils';
import style from './Table.module.scss'; import style from './Table.module.scss';
export default function TableWrapper() { export default function TableWrapper() {
const { data: tableData } = useFetch(EVENTS_TABLE, fetchAllEvents); const { data: events } = useFetch(EVENTS_TABLE, fetchAllEvents);
const { data: userFields } = useFetch(USERFIELDS, getUserFields); const { data: userFields } = useFetch(USERFIELDS, getUserFields);
const mutation = useMutateEvents(requestPatch); const mutation = useMutateEvents(requestPatch);
const socket = useSocket();
const [selectedId, setSelectedId] = useState(null);
const { theme } = useContext(TableSettingsContext); const { theme } = useContext(TableSettingsContext);
const featureData = useCuesheetProvider();
// Set window title // Set window title
document.title = 'ontime - Cuesheet';
/**
* Handle incoming data from socket
*/
useEffect(() => { useEffect(() => {
if (socket == null) return; document.title = 'ontime - Cuesheet';
}, []);
// Ask for selected
socket.emit('get-selected');
// Handle selected
socket.on('selected', (data) => {
setSelectedId(data.id);
});
// Clear listener
return () => {
socket.off('selected');
};
}, [socket]);
const handleUpdate = useCallback( const handleUpdate = useCallback(
async (rowIndex, accessor, payload) => { async (rowIndex, accessor, payload) => {
@@ -51,11 +32,11 @@ export default function TableWrapper() {
return; return;
} }
// check if value is the same // check if value is the same
const event = tableData[rowIndex]; const event = events[rowIndex];
if (event == null) { if (event == null) {
return; return;
} }
if (event[accessor] === payload) { if (event[accessor] === payload) {
return; return;
@@ -73,23 +54,21 @@ export default function TableWrapper() {
[accessor]: cleanVal, [accessor]: cleanVal,
}; };
// submit // submit
try { try {
await mutation.mutateAsync(mutationObject); await mutation.mutateAsync(mutationObject);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}, }, [mutation, events]);
[mutation, tableData]
);
const exportHandler = useCallback( const exportHandler = useCallback(
(headerData) => { (headerData) => {
if (!headerData || !tableData || !userFields) { if (!headerData || !events || !userFields) {
return; return;
} }
const sheetData = makeTable(headerData, tableData, userFields); const sheetData = makeTable(headerData, events, userFields);
const csvContent = makeCSV(sheetData); const csvContent = makeCSV(sheetData);
const encodedUri = encodeURI(csvContent); const encodedUri = encodeURI(csvContent);
const link = document.createElement('a'); const link = document.createElement('a');
@@ -98,20 +77,20 @@ export default function TableWrapper() {
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
}, },
[tableData, userFields] [events, userFields]
); );
if (typeof tableData === 'undefined' || typeof userFields === 'undefined') { if (typeof events === 'undefined' || typeof userFields === 'undefined') {
return <span>loading...</span>; return <span>loading...</span>;
} }
return ( return (
<div className={theme === 'dark' ? style.tableWrapper__dark : style.tableWrapper}> <div className={theme === 'dark' ? style.tableWrapper__dark : style.tableWrapper}>
<TableHeader handleCSVExport={exportHandler} /> <TableHeader handleCSVExport={exportHandler} featureData={featureData} />
<OntimeTable <OntimeTable
tableData={tableData} tableData={events}
userFields={userFields} userFields={userFields}
handleUpdate={handleUpdate} handleUpdate={handleUpdate}
selectedId={selectedId} selectedId={featureData.selectedEventId}
/> />
</div> </div>
); );
+1
View File
@@ -23,6 +23,7 @@ $ontime-pink: #ff7597;
$ontime-pink-variant: #ff6969; $ontime-pink-variant: #ff6969;
$ontime-roll: #2b6cb0; $ontime-roll: #2b6cb0;
$ontime-delay: #dd6b20; $ontime-delay: #dd6b20;
$action-blue: #3182ce;
//rgba(255, 255, 255, 0.39); - $bg-gray-700 //rgba(255, 255, 255, 0.39); - $bg-gray-700
+72 -88
View File
@@ -1052,7 +1052,7 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2": "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.7.2":
version "7.17.2" version "7.17.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
@@ -2339,6 +2339,36 @@
"@svgr/plugin-svgo" "^5.5.0" "@svgr/plugin-svgo" "^5.5.0"
loader-utils "^2.0.0" loader-utils "^2.0.0"
"@tanstack/match-sorter-utils@^8.0.0-alpha.82":
version "8.1.1"
resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.1.1.tgz#895f407813254a46082a6bbafad9b39b943dc834"
integrity sha512-IdmEekEYxQsoLOR0XQyw3jD1GujBpRRYaGJYQUw1eOT1eUugWxdc7jomh1VQ1EKHcdwDLpLaCz/8y4KraU4T9A==
dependencies:
remove-accents "0.4.2"
"@tanstack/query-core@^4.0.0-beta.1":
version "4.1.3"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.1.3.tgz#06b24315577971376b5a15124fc132c0b3ebc1b3"
integrity sha512-mSWGovlZnEiVwC99t+Q9uLwGdV9hnq+Z2+sHNAr8tsujzMPRLFNSmWAh/AayAaxIRWPwF4Ce9dmhV2BrYmuK1A==
"@tanstack/react-query-devtools@^4.0.10":
version "4.0.10"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-4.0.10.tgz#d1b3e5b1917f1c22bcee5830ef7af1ccfc4879f4"
integrity sha512-3J7LLYQjfjTI0DbPo0bA+M3l4kdvYSWAqihpeG1u93WVyZj0OEFviUv+4cK7+k2AVgQJAPMZ5xvtewKxOOFVrw==
dependencies:
"@tanstack/match-sorter-utils" "^8.0.0-alpha.82"
"@types/use-sync-external-store" "^0.0.3"
use-sync-external-store "^1.2.0"
"@tanstack/react-query@^4.1.3":
version "4.1.3"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.1.3.tgz#ebe5b668e321ba63a21ef369b5d263e2466c0e14"
integrity sha512-BHI/dV3LOuVOZAaifM6u680Wi5rmiHu4aD3WkvrQT/9fYhHDhTzMdJO8l2Xh0UR7JhM8e9O3f89SJhD3T8Ejgw==
dependencies:
"@tanstack/query-core" "^4.0.0-beta.1"
"@types/use-sync-external-store" "^0.0.3"
use-sync-external-store "^1.2.0"
"@testing-library/dom@^8.5.0": "@testing-library/dom@^8.5.0":
version "8.13.0" version "8.13.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.13.0.tgz#bc00bdd64c7d8b40841e27a70211399ad3af46f5" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.13.0.tgz#bc00bdd64c7d8b40841e27a70211399ad3af46f5"
@@ -2729,6 +2759,11 @@
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
"@types/use-sync-external-store@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
"@types/ws@^8.2.2": "@types/ws@^8.2.2":
version "8.5.3" version "8.5.3"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
@@ -3287,12 +3322,13 @@ axe-core@^4.3.5:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413"
integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
axios@^0.25.0: axios@^0.27.2:
version "0.25.0" version "0.27.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
dependencies: dependencies:
follow-redirects "^1.14.7" follow-redirects "^1.14.9"
form-data "^4.0.0"
axobject-query@^2.2.0: axobject-query@^2.2.0:
version "2.2.0" version "2.2.0"
@@ -3476,11 +3512,6 @@ bfj@^7.0.2:
hoopy "^0.1.4" hoopy "^0.1.4"
tryer "^1.0.1" tryer "^1.0.1"
big-integer@^1.6.16:
version "1.6.51"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==
big.js@^5.2.2: big.js@^5.2.2:
version "5.2.2" version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@@ -3544,20 +3575,6 @@ braces@^3.0.2, braces@~3.0.2:
dependencies: dependencies:
fill-range "^7.0.1" fill-range "^7.0.1"
broadcast-channel@^3.4.1:
version "3.7.0"
resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937"
integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==
dependencies:
"@babel/runtime" "^7.7.2"
detect-node "^2.1.0"
js-sha3 "0.8.0"
microseconds "0.2.0"
nano-time "1.0.0"
oblivious-set "1.0.0"
rimraf "3.0.2"
unload "2.2.0"
browser-process-hrtime@^1.0.0: browser-process-hrtime@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
@@ -4423,7 +4440,7 @@ detect-node-es@^1.1.0:
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
detect-node@^2.0.4, detect-node@^2.1.0: detect-node@^2.0.4:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
@@ -5277,10 +5294,10 @@ follow-redirects@^1.0.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
follow-redirects@^1.14.7: follow-redirects@^1.14.9:
version "1.14.7" version "1.15.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
fork-ts-checker-webpack-plugin@^6.5.0: fork-ts-checker-webpack-plugin@^6.5.0:
version "6.5.0" version "6.5.0"
@@ -5310,6 +5327,15 @@ form-data@^3.0.0:
combined-stream "^1.0.8" combined-stream "^1.0.8"
mime-types "^2.1.12" mime-types "^2.1.12"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
forwarded@0.2.0: forwarded@0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@@ -6624,14 +6650,9 @@ jest@^27.4.3:
jest-cli "^27.5.1" jest-cli "^27.5.1"
jotai@^1.7.8: jotai@^1.7.8:
version "1.7.8" version "1.8.3"
resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.7.8.tgz#1ac4daa2731e0f8e7fab8abb96aebf94b9dfc1a3" resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.8.3.tgz#06595dd66bcf64dd370386eee9c7b386c2ccb2e7"
integrity sha512-rXwWz6uLqyZUCRzWIPiYTjI1hjskVxVpDN3lBoOHi66z6uQFkFmKwR8YhcQd+Ex4lODlReuKNIx6WwsF9aKy3g== integrity sha512-bNAs/DMD6BEB4xxVku33CI+ZIFyCWG82Ms6JnvYunEvGCnviTTDBl+m0HKgijoHmcNM3fxQ5MARdG+wwokNxog==
js-sha3@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
@@ -6934,9 +6955,9 @@ lru-cache@^6.0.0:
yallist "^4.0.0" yallist "^4.0.0"
luxon@^3.0.1: luxon@^3.0.1:
version "3.0.1" version "3.0.3"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.0.1.tgz#6901111d10ad06fd267ad4e4128a84bef8a77299" resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.0.3.tgz#573e65531efd3d92265feb640f02ba7a192e2388"
integrity sha512-hF3kv0e5gwHQZKz4wtm4c+inDtyc7elkanAsBq+fundaCdUBNJB1dHEGUZIM6SfSBUlbVFduPwEtNjFK8wLtcw== integrity sha512-+EfHWnF+UT7GgTnq5zXg3ldnTKL2zdv7QJgsU5bjjpbH17E3qi/puMhQyJVYuCq+FRkogvB5WB6iVvUr+E4a7w==
lz-string@^1.4.4: lz-string@^1.4.4:
version "1.4.4" version "1.4.4"
@@ -6974,14 +6995,6 @@ map-obj@^4.0.0:
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a"
integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==
match-sorter@^6.0.2:
version "6.3.1"
resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda"
integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==
dependencies:
"@babel/runtime" "^7.12.5"
remove-accents "0.4.2"
mathml-tag-names@^2.1.3: mathml-tag-names@^2.1.3:
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
@@ -7060,11 +7073,6 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
braces "^3.0.2" braces "^3.0.2"
picomatch "^2.3.1" picomatch "^2.3.1"
microseconds@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39"
integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==
mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
version "1.52.0" version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
@@ -7167,13 +7175,6 @@ multicast-dns@^6.0.1:
dns-packet "^1.3.1" dns-packet "^1.3.1"
thunky "^1.0.2" thunky "^1.0.2"
nano-time@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef"
integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=
dependencies:
big-integer "^1.6.16"
nanoid@^3.3.1: nanoid@^3.3.1:
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
@@ -7365,11 +7366,6 @@ object.values@^1.1.0, object.values@^1.1.5:
define-properties "^1.1.3" define-properties "^1.1.3"
es-abstract "^1.19.1" es-abstract "^1.19.1"
oblivious-set@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566"
integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==
obuf@^1.0.0, obuf@^1.1.2: obuf@^1.0.0, obuf@^1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
@@ -8500,15 +8496,6 @@ react-qr-code@^2.0.5:
prop-types "^15.7.2" prop-types "^15.7.2"
qr.js "0.0.0" qr.js "0.0.0"
react-query@^3.38.0:
version "3.38.0"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.38.0.tgz#e7f4081e45ba47a4119e548ddcfd0fab18f0580e"
integrity sha512-VRbCTRrDfC5FsB70+JfZuxFRv9SAvkZ1h36MsN8+QaDN+NWB6s1vJndqpoLQnJqN0COTG2zsInMq0KFdYze6TA==
dependencies:
"@babel/runtime" "^7.5.5"
broadcast-channel "^3.4.1"
match-sorter "^6.0.2"
react-redux@^7.2.0: react-redux@^7.2.0:
version "7.2.4" version "7.2.4"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
@@ -8899,7 +8886,7 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
@@ -9192,10 +9179,10 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0" astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0" is-fullwidth-code-point "^3.0.0"
socket.io-client@^4.5.0: socket.io-client@^4.5.1:
version "4.5.0" version "4.5.1"
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.0.tgz#3858b6183bab31c5c4eaf3efd0fa50840ebb4504" resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.1.tgz#cab8da71976a300d3090414e28c2203a47884d84"
integrity sha512-HW61c1G7OrYGxaI79WRn17+b03iBCdvhBj4iqyXHBoL5M8w2MSO/vChsjA93knG4GYEai1/vbXWJna9dzxXtSg== integrity sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA==
dependencies: dependencies:
"@socket.io/component-emitter" "~3.1.0" "@socket.io/component-emitter" "~3.1.0"
debug "~4.3.2" debug "~4.3.2"
@@ -10031,14 +10018,6 @@ universalify@^2.0.0:
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
unload@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7"
integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==
dependencies:
"@babel/runtime" "^7.6.2"
detect-node "^2.0.4"
unpipe@1.0.0, unpipe@~1.0.0: unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -10086,6 +10065,11 @@ use-sidecar@^1.0.1, use-sidecar@^1.0.5:
detect-node-es "^1.1.0" detect-node-es "^1.1.0"
tslib "^1.9.3" tslib "^1.9.3"
use-sync-external-store@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ontime", "name": "ontime",
"version": "1.7.0", "version": "1.7.1",
"author": "Carlos Valente", "author": "Carlos Valente",
"description": "Time keeping for live events", "description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime", "repository": "https://github.com/cpvalente/ontime",
+143 -49
View File
@@ -149,10 +149,99 @@ export class EventTimer extends Timer {
this.io.emit('timer', this.getTimeObject()); this.io.emit('timer', this.getTimeObject());
} }
/**
* @description Broadcast timer data
* @private
*/
_broadcastFeatureTimer() {
const featureData = {
clock: this.clock,
current: this.current,
secondaryTimer: this.secondaryTimer,
duration: this.duration,
expectedFinish: this._getExpectedFinish(),
startedAt: this._startedAt,
};
this.io.emit('ontime-timer', featureData);
}
/**
* @description Broadcast data for Event List feature
* @private
*/
_broadcastFeatureEventList() {
const featureData = {
selectedEventId: this.selectedEventId,
nextEventId: this.nextEventId,
};
this.io.emit('ontime-feat-eventlist', featureData);
}
/**
* @description Broadcast data for Message Control feature
* @private
*/
_broadcastFeatureMessageControl() {
const featureData = {
presenter: this.presenter,
public: this.public,
lower: this.lower,
onAir: this.onAir,
};
this.io.emit('ontime-feat-messagecontrol', featureData);
}
/**
* @description Broadcast data for Playback Control feature
* @private
*/
_broadcastFeaturePlaybackControl() {
const featureData = {
playback: this.state,
selectedEventId: this.selectedEventId,
numEvents: this._eventlist.length,
};
this.io.emit('ontime-feat-playbackcontrol', featureData);
}
/**
* @description Broadcast data for Info feature
* @private
*/
_broadcastFeatureInfo() {
const featureData = {
titles: this.titles,
playback: this.state,
selectedEventId: this.selectedEventId,
selectedEventIndex: this.selectedEventIndex,
numEvents: this._eventlist.length,
};
this.io.emit('ontime-feat-info', featureData);
}
_broadcastFeatureCuesheet() {
const featureData = {
playback: this.state,
selectedEventId: this.selectedEventId,
selectedEventIndex: this.selectedEventIndex,
numEvents: this._eventlist.length,
titleNow: this.titles.titleNow,
};
this.io.emit('ontime-feat-cuesheet', featureData);
}
/** /**
* Broadcasts complete object state * Broadcasts complete object state
*/ */
broadcastState() { broadcastState() {
// feature sync
this._broadcastFeatureEventList();
this._broadcastFeatureMessageControl();
this._broadcastFeaturePlaybackControl();
this._broadcastFeatureInfo();
this._broadcastFeatureCuesheet();
this._broadcastFeatureTimer();
const numEvents = this._eventlist.length; const numEvents = this._eventlist.length;
this.broadcastTimer(); this.broadcastTimer();
this.io.emit('playstate', this.state); this.io.emit('playstate', this.state);
@@ -333,9 +422,6 @@ export class EventTimer extends Timer {
} }
break; break;
case 'onLoad': case 'onLoad':
// broadcast change
this.broadcastState();
// check integrations - http // check integrations - http
if (h?.onLoad?.enabled) { if (h?.onLoad?.enabled) {
if (h?.onLoad?.url != null || h?.onLoad?.url !== '') { if (h?.onLoad?.url != null || h?.onLoad?.url !== '') {
@@ -347,8 +433,6 @@ export class EventTimer extends Timer {
this.ontimeCycle = this.cycleState.armed; this.ontimeCycle = this.cycleState.armed;
break; break;
case 'onStart': case 'onStart':
// broadcast current state
this.broadcastState();
// send OSC if there is something running // send OSC if there is something running
// _finish at is only set when an event is loaded // _finish at is only set when an event is loaded
if (this._finishAt > 0) { if (this._finishAt > 0) {
@@ -368,8 +452,6 @@ export class EventTimer extends Timer {
case 'onUpdate': case 'onUpdate':
// call update // call update
this.update(); this.update();
// broadcast current state
this.broadcastTimer();
// through OSC, only if running // through OSC, only if running
if (this.state === 'start' || this.state === 'roll') { if (this.state === 'start' || this.state === 'roll') {
if (this.current != null && this.secondaryTimer == null) { if (this.current != null && this.secondaryTimer == null) {
@@ -389,9 +471,6 @@ export class EventTimer extends Timer {
break; break;
case 'onPause': case 'onPause':
// broadcast current state
this.broadcastState();
// send OSC // send OSC
if (this.prevCycle === this.cycleState.onUpdate) { if (this.prevCycle === this.cycleState.onUpdate) {
this.sendOsc(this.osc.implemented.pause); this.sendOsc(this.osc.implemented.pause);
@@ -409,9 +488,6 @@ export class EventTimer extends Timer {
break; break;
case 'onStop': case 'onStop':
// broadcast change
this.broadcastState();
// send OSC if something was actually stopped // send OSC if something was actually stopped
if (this.prevCycle === this.cycleState.onUpdate) { if (this.prevCycle === this.cycleState.onUpdate) {
this.sendOsc(this.osc.implemented.stop); this.sendOsc(this.osc.implemented.stop);
@@ -428,8 +504,6 @@ export class EventTimer extends Timer {
this.ontimeCycle = this.cycleState.idle; this.ontimeCycle = this.cycleState.idle;
break; break;
case 'onFinish': case 'onFinish':
// broadcast change
this.broadcastState();
// finished an event // finished an event
this.sendOsc(this.osc.implemented.finished); this.sendOsc(this.osc.implemented.finished);
@@ -464,6 +538,7 @@ export class EventTimer extends Timer {
// update // update
this.update(); this.update();
this.broadcastState();
// reset cycle // reset cycle
this.prevCycle = this.ontimeCycle; this.prevCycle = this.ontimeCycle;
@@ -472,6 +547,8 @@ export class EventTimer extends Timer {
update() { update() {
// if there is nothing selected, update clock // if there is nothing selected, update clock
this.clock = this._getCurrentTime(); this.clock = this._getCurrentTime();
this._broadcastFeatureTimer();
this.broadcastTimer();
// if we are not updating, send the timers // if we are not updating, send the timers
if (this.ontimeCycle !== this.cycleState.onUpdate) { if (this.ontimeCycle !== this.cycleState.onUpdate) {
@@ -694,10 +771,11 @@ export class EventTimer extends Timer {
try { try {
const d = JSON.parse(data); const d = JSON.parse(data);
this.onAir = !!d; this.onAir = !!d;
this.broadcastThis('onAir', this.onAir);
} catch (error) { } catch (error) {
this.error('RX', `Failed to parse message ${data}`); this.error('RX', `Failed to parse message ${data}`);
} }
this.broadcastThis('onAir', this.onAir);
this._broadcastFeatureMessageControl();
}); });
/*******************************************/ /*******************************************/
@@ -712,15 +790,13 @@ export class EventTimer extends Timer {
}); });
/*******************************************/ /*******************************************/
// timer
socket.on('get-current', () => {
socket.emit('current', this.getCurrentInSeconds());
});
// ** TO BE DEPRECATED ** //
socket.on('get-timer', () => { socket.on('get-timer', () => {
socket.emit('timer', this.getTimeObject()); socket.emit('timer', this.getTimeObject());
}); });
// ** TO BE DEPRECATED IN FAVOR OF DELAY ** //
socket.on('increment-timer', (data) => { socket.on('increment-timer', (data) => {
if (isNaN(parseInt(data, 10))) return; if (isNaN(parseInt(data, 10))) return;
if (data < -5 || data > 5) return; if (data < -5 || data > 5) return;
@@ -731,6 +807,8 @@ export class EventTimer extends Timer {
// playstate // playstate
socket.on('set-playstate', (data) => { socket.on('set-playstate', (data) => {
this.trigger(data); this.trigger(data);
this._broadcastFeaturePlaybackControl();
this._broadcastFeatureInfo();
}); });
socket.on('get-playstate', () => { socket.on('get-playstate', () => {
@@ -755,28 +833,15 @@ export class EventTimer extends Timer {
socket.emit('selected-id', this.selectedEventId); socket.emit('selected-id', this.selectedEventId);
}); });
socket.on('get-numevents', () => {
socket.emit('numevents', this._eventlist.length);
});
socket.on('get-next-id', () => { socket.on('get-next-id', () => {
socket.emit('next-id', this.nextEventId); socket.emit('next-id', this.nextEventId);
}); });
socket.on('get-publicselected-id', () => {
socket.emit('publicselected-id', this.selectedPublicEventId);
});
socket.on('get-publicnext-id', () => {
socket.emit('publicnext-id', this.nextPublicEventId);
});
// title data // title data
socket.on('get-titles', () => { socket.on('get-titles', () => {
socket.emit('titles', this.titles); socket.emit('titles', this.titles);
}); });
// title data
socket.on('get-publictitles', () => { socket.on('get-publictitles', () => {
socket.emit('publictitles', this.titlesPublic); socket.emit('publictitles', this.titlesPublic);
}); });
@@ -788,51 +853,79 @@ export class EventTimer extends Timer {
/*******************************************/ /*******************************************/
// Messages
socket.on('get-messages', () => {
this.broadcastThis('messages-timer', this.presenter);
this.broadcastThis('messages-public', this.public);
this.broadcastThis('messages-lower', this.lower);
});
// Presenter message // Presenter message
socket.on('set-timer-message-text', (data) => { socket.on('set-timer-message-text', (data) => {
this._setTitles('set-timer-text', data); this._setTitles('set-timer-text', data);
this._broadcastFeatureMessageControl();
}); });
socket.on('set-timer-message-visible', (data) => { socket.on('set-timer-message-visible', (data) => {
this._setTitles('set-timer-visible', data); this._setTitles('set-timer-visible', data);
this._broadcastFeatureMessageControl();
}); });
socket.on('get-timer', () => {
this.broadcastThis('messages-timer', this.presenter);
});
/*******************************************/ /*******************************************/
// Public message // Public message
socket.on('set-public-message-text', (data) => { socket.on('set-public-message-text', (data) => {
this._setTitles('set-public-text', data); this._setTitles('set-public-text', data);
this._broadcastFeatureMessageControl();
}); });
socket.on('set-public-message-visible', (data) => { socket.on('set-public-message-visible', (data) => {
this._setTitles('set-public-visible', data); this._setTitles('set-public-visible', data);
}); this._broadcastFeatureMessageControl();
socket.on('get-public', () => {
socket.emit('messages-public', this.public);
}); });
/*******************************************/ /*******************************************/
// Lower third message // Lower third message
socket.on('set-lower-message-text', (data) => { socket.on('set-lower-message-text', (data) => {
this._setTitles('set-lower-text', data); this._setTitles('set-lower-text', data);
this._broadcastFeatureMessageControl();
}); });
socket.on('set-lower-message-visible', (data) => { socket.on('set-lower-message-visible', (data) => {
this._setTitles('set-lower-visible', data); this._setTitles('set-lower-visible', data);
this._broadcastFeatureMessageControl();
}); });
socket.on('get-lower', () => { /* MOLECULAR ENDPOINTS
socket.emit('messages-lower', this.lower); * =====================
* 1. EVENT LIST
* 2. MESSAGE CONTROL
* 3. PLAYBACK CONTROL
* 4. INFO
* 5. CUESHEET
* 6. TIMER OBJECT
* */
// 1. EVENT LIST
socket.on('get-ontime-feat-eventlist', () => {
this._broadcastFeatureEventList();
});
// 2. MESSAGE CONTROL
socket.on('get-ontime-feat-messagecontrol', () => {
this._broadcastFeatureMessageControl();
});
// 3. PLAYBACK CONTROL
socket.on('get-ontime-feat-playbackcontrol', () => {
this._broadcastFeaturePlaybackControl();
});
// 4. INFO
socket.on('get-ontime-feat-info', () => {
this._broadcastFeatureInfo();
});
// 5. CUE SHEET
socket.on('get-ontime-feat-cuesheet', () => {
this._broadcastFeatureCuesheet();
});
// 6. TIMER
socket.on('get-ontime-timer', () => {
this._broadcastFeatureTimer();
}); });
}); });
} }
@@ -1394,6 +1487,7 @@ export class EventTimer extends Timer {
// call super // call super
super.stop(); super.stop();
this._resetSelection();
// update lifecycle: onPause // update lifecycle: onPause
this.ontimeCycle = this.cycleState.onStop; this.ontimeCycle = this.cycleState.onStop;
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "ontime-server", "name": "ontime-server",
"type": "module", "type": "module",
"version": "1.2.1", "version": "1.3.0",
"dependencies": { "dependencies": {
"body-parser": "^1.20.0", "body-parser": "^1.20.0",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",