Refactor/ts sockets (#210)

* refactor(ts): create socket subscription service
* refactor(ts-sockets): useSubscription hook
* refactor(ts-sockets): extract colour selection to utility
This commit is contained in:
Carlos Valente
2022-10-09 19:09:47 +02:00
committed by GitHub
parent f119fb4529
commit cd0577bf99
26 changed files with 346 additions and 254 deletions
+1
View File
@@ -54,6 +54,7 @@
"@testing-library/react": "^13.1.1",
"@testing-library/react-hooks": "^8.0.0",
"@testing-library/user-event": "^14.1.1",
"@types/color": "^3.0.3",
"@types/node": "^18.7.16",
"@types/react": "^18.0.19",
"@types/react-dom": "^18.0.6",
@@ -0,0 +1,42 @@
export interface OntimeBaseEvent {
type: 'block' | 'event' | 'delay';
id: string;
}
export interface OntimeDelay extends OntimeBaseEvent {
type: 'delay';
duration: number;
revision: number;
}
export interface OntimeBlock extends OntimeBaseEvent {
type: 'block';
}
export interface OntimeEvent extends OntimeBaseEvent {
type: 'event';
title: string,
subtitle: string,
presenter: string,
note: string,
timeStart: number,
timeEnd: number,
timeType?: string,
duration: number,
isPublic: boolean,
skip: boolean,
colour: string,
user0: string,
user1: string,
user2: string,
user3: string,
user4: string,
user5: string,
user6: string,
user7: string,
user8: string,
user9: string,
revision: number,
}
export type OntimeEventEntry = OntimeDelay | OntimeBlock | OntimeEvent;
@@ -4,11 +4,17 @@ import { Tooltip } from '@chakra-ui/tooltip';
import { FiClock } from '@react-icons/all-files/fi/FiClock';
import { FiMinusCircle } from '@react-icons/all-files/fi/FiMinusCircle';
import { FiPlus } from '@react-icons/all-files/fi/FiPlus';
import PropTypes from 'prop-types';
import { tooltipDelayMid } from '../../../ontimeConfig';
export default function ActionButtons(props) {
interface ActionButtonProps {
showAdd?: boolean;
showDelay?: boolean;
showBlock?: boolean;
actionHandler: (action: string) => void;
}
export default function ActionButtons(props: ActionButtonProps) {
const { showAdd, showDelay, showBlock, actionHandler } = props;
const menuStyle = {
@@ -18,7 +24,7 @@ export default function ActionButtons(props) {
return (
<Menu isLazy lazyBehavior='unmount'>
<Tooltip label='Add ...' delay={tooltipDelayMid}>
<Tooltip label='Add ...' openDelay={tooltipDelayMid}>
<MenuButton
as={IconButton}
aria-label='Options'
@@ -49,10 +55,3 @@ export default function ActionButtons(props) {
</Menu>
);
}
ActionButtons.propTypes = {
showAdd: PropTypes.bool,
showDelay: PropTypes.bool,
showBlock: PropTypes.bool,
actionHandler: PropTypes.func,
}
@@ -1,30 +1,29 @@
import { IconButton } from '@chakra-ui/button';
import { Tooltip } from '@chakra-ui/tooltip';
import { IoPause } from '@react-icons/all-files/io5/IoPause';
import PropTypes from 'prop-types';
import { tooltipDelayMid } from '../../../ontimeConfig';
export default function PauseIconBtn(props) {
interface PauseIconBtnProps {
clickhandler: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
active: boolean;
disabled: boolean;
}
export default function PauseIconBtn(props: PauseIconBtnProps) {
const { clickhandler, active, disabled, ...rest } = props;
return (
<Tooltip label='Pause timer' openDelay={tooltipDelayMid} shouldWrapChildren={disabled}>
<IconButton
icon={<IoPause size='24px' />}
colorScheme='orange'
_hover={!disabled && { bg: 'orange.400' }}
variant={active ? 'solid' : 'outline'}
onClick={clickhandler}
width={120}
disabled={disabled}
aria-label='Pause playback'
{...rest}
/>
</Tooltip>
);
}
PauseIconBtn.propTypes = {
clickhandler: PropTypes.func,
active: PropTypes.bool,
disabled: PropTypes.bool
}
@@ -1,39 +0,0 @@
import PropTypes from 'prop-types';
import { formatTime } from '../../utils/time';
import './Paginator.scss';
export default function TodayItem(props) {
const { selected, timeStart, timeEnd, title, backstageEvent, colour } = props;
// Format timers
const start = formatTime(timeStart, { format: 'hh:mm' });
const end = formatTime(timeEnd, { format: 'hh:mm' });
// user colours
const userColour = colour !== '' ? colour : 'transparent';
// select styling
let selectStyle = 'entry--past';
if (selected === 1) selectStyle = 'entry--now';
else if (selected === 2) selectStyle = 'entry--future';
return (
<div className={`entry ${selectStyle}`} style={{ borderLeft: `4px solid ${userColour}` }}>
<div className='entry-times'>
{`${start} · ${end}`}
</div>
<div className='entry-title'>{title}</div>
{backstageEvent && <div className='backstage-indicator' />}
</div>
);
}
TodayItem.propTypes = {
selected: PropTypes.number,
timeStart: PropTypes.number,
timeEnd: PropTypes.number,
title: PropTypes.string,
backstageEvent: PropTypes.bool,
colour: PropTypes.string,
};
@@ -1,14 +1,24 @@
import { useEffect, useState } from 'react';
import { useInterval } from 'common/hooks/useInterval';
import PropTypes from 'prop-types';
import { OntimeEvent } from '../../application-types/event';
import Empty from '../state/Empty';
import TodayItem from './TodayItem';
import './Paginator.scss';
import style from './Paginator.module.scss';
export default function Paginator(props) {
interface PaginatorProps {
events: OntimeEvent[];
selectedId: string;
limit?: number;
time?: number;
isBackstage: boolean;
setPageNumber: (page: number) => void;
setCurrentPage: (selectedPage: number) => void;
}
export default function Paginator(props: PaginatorProps) {
const {
events,
selectedId,
@@ -21,9 +31,9 @@ export default function Paginator(props) {
const LIMIT_PER_PAGE = limit;
const SCROLL_TIME = time * 1000;
const [numEvents, setNumEvents] = useState(0);
const [page, setPage] = useState([]);
const [pages, setPages] = useState(0);
const [selPage, setSelPage] = useState(0);
const [page, setPage] = useState<OntimeEvent[]>([]);
const [pages, setPages] = useState<number>(0);
const [selPage, setSelPage] = useState<number>(0);
// keep parent up to date
useEffect(() => {
@@ -70,7 +80,7 @@ export default function Paginator(props) {
}
return (
<div className='paginator entries'>
<div className={style.entries}>
{page.map((e) => {
if (e.id === selectedId) selectedState = 1;
else if (selectedState === 1) selectedState = 2;
@@ -83,19 +93,10 @@ export default function Paginator(props) {
title={e.title}
colour={isBackstage ? e.colour : ''}
backstageEvent={!e.isPublic}
skip={e.skip}
/>
);
})}
</div>
);
}
Paginator.propTypes = {
events: PropTypes.array,
selectedId: PropTypes.string,
limit: PropTypes.number,
time: PropTypes.number,
isBackstage: PropTypes.bool,
setPageNumber: PropTypes.func,
setCurrentPage: PropTypes.func,
};
@@ -0,0 +1,51 @@
import PropTypes from 'prop-types';
import { formatTime } from '../../utils/time';
import style from './Paginator.module.scss';
interface TodayItemProps {
selected: number;
timeStart: number;
timeEnd: number;
title: string;
backstageEvent: boolean;
colour: string;
skip: boolean;
}
// Todo: apply skip CSS and selector
export default function TodayItem(props: TodayItemProps) {
// @ts-ignore
const { selected, timeStart, timeEnd, title, backstageEvent, colour, skip } = props;
// Format timers
const start = formatTime(timeStart, { format: 'hh:mm' });
const end = formatTime(timeEnd, { format: 'hh:mm' });
// user colours
const userColour = colour !== '' ? colour : 'transparent';
// select styling
let selectStyle = style.entryPast;
if (selected === 1) selectStyle = style.entryNow;
else if (selected === 2) selectStyle = style.entryFuture;
return (
<div className={selectStyle} style={{ borderLeft: `4px solid ${userColour}` }}>
<div className={`${style.entryTimes} ${backstageEvent ? style.backstage : ''}`}>
{`${start} · ${end}`}
</div>
<div className={style.entryTitle}>{title}</div>
{backstageEvent && <div className={style.backstageInd} />}
</div>
);
}
TodayItem.propTypes = {
selected: PropTypes.number,
timeStart: PropTypes.number,
timeEnd: PropTypes.number,
title: PropTypes.string,
backstageEvent: PropTypes.bool,
colour: PropTypes.string,
};
@@ -1,17 +1,32 @@
import { createContext, useCallback, useMemo, useState } from 'react';
import { createContext, ReactNode, useCallback, useMemo, useState } from 'react';
import { useLocalStorage } from '../hooks/useLocalStorage';
export const CursorContext = createContext({
interface CursorContextState {
cursor: number;
isCursorLocked: boolean;
toggleCursorLocked: (newValue?: boolean) => void;
setCursor: (index: number) => void;
moveCursorUp: () => void;
moveCursorDown: () => void;
moveCursorTo: (index: number) => void;
}
export const CursorContext = createContext<CursorContextState>({
cursor: 0,
isCursorLocked: false,
toggleCursorLocked: () => undefined,
setCursor: () => undefined,
moveCursorUp: () => undefined,
moveCursorDown: () => undefined,
moveCursorTo: () => undefined,
});
export const CursorProvider = ({ children }) => {
interface CursorProviderProps {
children: ReactNode
}
export const CursorProvider = ({ children }: CursorProviderProps) => {
const [cursor, setCursor] = useState(0);
const [_cursorLocked, _setCursorLocked] = useLocalStorage('isCursorLocked', 'locked');
const isCursorLocked = useMemo(() => _cursorLocked === 'locked', [_cursorLocked]);
@@ -31,7 +46,7 @@ export const CursorProvider = ({ children }) => {
* @param {boolean | undefined} newValue
*/
const toggleCursorLocked = useCallback(
(newValue = undefined) => {
(newValue?: boolean) => {
if (typeof newValue === 'undefined') {
if (isCursorLocked) {
cursorLockedOff();
@@ -46,6 +61,11 @@ export const CursorProvider = ({ children }) => {
},
[cursorLockedOff, cursorLockedOn, isCursorLocked]
);
// moves cursor to given index
const moveCursorTo = useCallback((index: number) => {
setCursor(index);
}, []);
return (
<CursorContext.Provider
@@ -56,6 +76,7 @@ export const CursorProvider = ({ children }) => {
setCursor,
moveCursorUp,
moveCursorDown,
moveCursorTo,
}}
>
{children}
+12 -12
View File
@@ -27,7 +27,7 @@ type LoggingProviderProps = {
}
const notInitialised = () => {
throw new Error("Not initialised");
throw new Error('Not initialised');
};
export const LoggingContext = createContext<LoggingProviderState>({
@@ -35,7 +35,7 @@ export const LoggingContext = createContext<LoggingProviderState>({
emitInfo: notInitialised,
emitWarning: notInitialised,
emitError: notInitialised,
clearLog: notInitialised
clearLog: notInitialised,
});
export const LoggingProvider = ({ children }: LoggingProviderProps) => {
@@ -52,7 +52,7 @@ export const LoggingProvider = ({ children }: LoggingProviderProps) => {
socket.emit('get-logger');
socket.on('logger', (data: Log) => {
setLogData((l) => [data, ...l]);
setLogData((currentLog) => [data, ...currentLog]);
});
// Clear listener
@@ -70,21 +70,21 @@ export const LoggingProvider = ({ children }: LoggingProviderProps) => {
const _send = useCallback(
(text: string, level: LOG_LEVEL) => {
if (socket != null) {
const m: Log = {
const newLogMessage: Log = {
id: generateId(),
origin,
time: stringFromMillis(nowInMillis()),
level,
text,
};
setLogData((l) => [m, ...l]);
socket.emit('logger', m);
setLogData((currentLog) => [newLogMessage, ...currentLog]);
socket.emit('logger', newLogMessage);
}
if (logData.length > MAX_MESSAGES) {
setLogData((l) => l.slice(1));
setLogData((currentLog) => currentLog.slice(1));
}
},
[logData, socket]
[logData.length, setLogData, socket],
);
/**
@@ -95,7 +95,7 @@ export const LoggingProvider = ({ children }: LoggingProviderProps) => {
(text: string) => {
_send(text, 'INFO');
},
[_send]
[_send],
);
/**
@@ -106,7 +106,7 @@ export const LoggingProvider = ({ children }: LoggingProviderProps) => {
(text: string) => {
_send(text, 'WARN');
},
[_send]
[_send],
);
/**
@@ -117,7 +117,7 @@ export const LoggingProvider = ({ children }: LoggingProviderProps) => {
(text: string) => {
_send(text, 'ERROR');
},
[_send]
[_send],
);
/**
@@ -125,7 +125,7 @@ export const LoggingProvider = ({ children }: LoggingProviderProps) => {
*/
const clearLog = useCallback(() => {
setLogData([]);
}, []);
}, [setLogData]);
return (
<LoggingContext.Provider value={{ emitInfo, logData, emitWarning, emitError, clearLog }}>
+1 -2
View File
@@ -1,4 +1,3 @@
// @ts-nocheck
import { createContext, ReactNode, useContext, useEffect, useState } from 'react';
import { serverURL } from 'common/api/apiConstants';
import io, { Socket } from 'socket.io-client';
@@ -26,7 +25,7 @@ export const useSocket = () => {
};
function SocketProvider({ children }: SocketProviderProps) {
const [socket, setSocket] = useState(null);
const [socket, setSocket] = useState<Socket | null>(null);
useEffect(() => {
const socketInstance = io(serverURL, { transports: ["websocket"] });
@@ -0,0 +1,27 @@
import { useEffect, useState } from 'react';
import { useSocket } from './socketContext';
export default function useSubscription<T>(topic: string, initialState: T, requestString?: string) {
const socket = useSocket();
const [state, setState] = useState<T>(initialState);
useEffect(() => {
if (!socket) {
return;
}
if (requestString) {
socket.emit(requestString);
} else {
socket.emit(`get-${topic}`);
}
socket.on(topic, setState);
return () => {
socket.off(topic);
};
}, [requestString, socket, topic]);
return [state, setState] as const;
};
+1 -2
View File
@@ -1,8 +1,7 @@
// @ts-nocheck
export default function useElectronEvent() {
const isElectron = window?.process?.type === 'renderer';
const sendToElectron = (channel: string, args: any) => {
const sendToElectron = (channel: string, args?: string | Record<string, any>) => {
if (isElectron) {
window?.ipcRenderer.send(channel, args);
}
-17
View File
@@ -1,17 +0,0 @@
import { useQuery } from '@tanstack/react-query';
const refetchIntervalMs = 10000;
/**
* @description utility hook to simplify query config
* @param namespace
* @param fn
*/
export const useFetch = (namespace, fn) => {
const { data, status, isError, refetch } = useQuery(namespace, fn, {
refetchInterval: refetchIntervalMs,
cacheTime: Infinity,
});
return { data, status, isError, refetch };
};
+18
View File
@@ -0,0 +1,18 @@
import { QueryFunction, QueryKey, useQuery, UseQueryOptions } from '@tanstack/react-query';
interface UseFetchState {
data: unknown;
status: "loading" | "error" | "success";
isError: boolean;
refetch: () => void;
}
export const useFetch = ( key: QueryKey, fn: QueryFunction, options?: UseQueryOptions): UseFetchState => {
const { data, status, isError, refetch } = useQuery(key, fn, {
refetchInterval: 10000,
cacheTime: Infinity,
...options
});
return { data, status, isError, refetch };
};
@@ -1,3 +1,5 @@
import { OntimeEvent, OntimeEventEntry } from '../application-types/event';
import { formatTime } from './time';
/**
@@ -6,7 +8,7 @@ import { formatTime } from './time';
* @returns {Object[]} Filtered events with calculated delays
*/
export const getEventsWithDelay = (events) => {
export const getEventsWithDelay = (events: OntimeEventEntry[]) => {
if (events == null) return [];
const unfilteredEvents = [...events];
@@ -33,7 +35,7 @@ export const getEventsWithDelay = (events) => {
* @param {number} limit - max number of events to return
* @returns {Object[]} Event list with maximum <limit> objects
*/
export const trimEventlist = (events, selectedId, limit) => {
export const trimEventlist = (events: OntimeEventEntry[], selectedId: string, limit: number) => {
if (events == null) return [];
const BEFORE = 2;
@@ -53,6 +55,9 @@ export const trimEventlist = (events, selectedId, limit) => {
return trimmedEvents;
};
type FormatEventListOptionsProp = {
showEnd?: boolean;
}
/**
* @description Returns list of events formatted to be displayed
* @param {Object[]} events - given events
@@ -62,7 +67,7 @@ export const trimEventlist = (events, selectedId, limit) => {
* @param {boolean} [options.showEnd] - whether to show the end time
* @returns {Object[]} Formatted list of events [{time: -, title: -, isNow, isNext}]
*/
export const formatEventList = (events, selectedId, nextId, options) => {
export const formatEventList = (events: OntimeEvent[], selectedId: string, nextId: string, options: FormatEventListOptionsProp) => {
if (events == null) return [];
const { showEnd = false } = options;
@@ -71,7 +76,7 @@ export const formatEventList = (events, selectedId, nextId, options) => {
// format list
const formattedEvents = [];
for (const event of givenEvents) {
const start = formatTime(event.timeStart)
const start = formatTime(event.timeStart);
const end = formatTime(event.timeEnd);
formattedEvents.push({
@@ -86,3 +91,23 @@ export const formatEventList = (events, selectedId, nextId, options) => {
return formattedEvents;
};
/**
* @description Creates a safe duplicate of an event
* @param {object} event
* @return {object} clean event
*/
export const duplicateEvent = (event: OntimeEvent) => {
return {
type: 'event',
title: event.title,
subtitle: event.subtitle,
presenter: event.presenter,
note: event.note,
timeStart: event.timeStart,
timeEnd: event.timeEnd,
isPublic: event.isPublic,
skip: event.skip,
colour: event.colour,
};
};
+23
View File
@@ -0,0 +1,23 @@
import Color from 'color';
type ColourCombination = {
backgroundColor: string;
color: string;
}
/**
* @description Selects text colour to maintain accessible contrast
* @param bgColour
* @return {{backgroundColor, color: string}}
*/
export const getAccessibleColour = (bgColour: string): ColourCombination => {
if (bgColour) {
try {
const textColor = Color(bgColour).isLight() ? 'black' : '#fffffa';
return { backgroundColor: bgColour, color: textColor };
} catch (error) {
console.log(`Unable to parse colour: ${bgColour}`);
}
}
return { backgroundColor: '#000', color: "#fffffa" };
};
+11 -3
View File
@@ -3,8 +3,16 @@ declare module '*.scss' {
export default content;
}
declare namespace NodeJS {
export interface ProcessEnv {
type: string
declare global {
interface Window {
ipcRenderer: {
send: (channel: string, args?: string | object) => void;
};
process: {
type: string;
}
}
}
// eslint-disable-next-line import/no-anonymous-default-export
export default {}
@@ -12,7 +12,7 @@ import {
requestReorder,
} from 'common/api/eventsApi.js';
import Empty from 'common/components/state/Empty';
import { useFetch } from 'common/hooks/useFetch.js';
import { useFetch } from 'common/hooks/useFetch.ts';
import EventListMenu from 'features/menu/EventListMenu.jsx';
import { CollapseContext } from '../../../common/context/CollapseContext';
@@ -5,18 +5,22 @@ import { vi } from 'vitest';
import { queryClientMock } from '../../../__mocks__/QueryClient.mock';
import MenuBar from '../MenuBar';
const onOpenHandler = vi.fn();
const onCloseHandler = vi.fn();
const isOpen = false;
const onSettingOpenHandler = vi.fn();
const onSettingsCloseHandler = vi.fn();
const onUploadOpenHandler = vi.fn();
const isOpen = false;
const renderInMock = () => {
render(
<QueryClientProvider client={queryClientMock}>
<MenuBar isSettingsOpen={isOpen} onSettingsOpen={onOpenHandler}
onSettingsClose={onCloseHandler} isUploadOpen={isOpen}
onUploadOpen={onUploadOpenHandler} />
</QueryClientProvider>,
<MenuBar
isSettingsOpen={isOpen}
onSettingsOpen={onSettingOpenHandler}
onSettingsClose={onSettingsCloseHandler}
isUploadOpen={isOpen}
onUploadOpen={onUploadOpenHandler}
/>
</QueryClientProvider>
);
};
@@ -1,29 +1,13 @@
import Color from 'color';
import PropTypes from 'prop-types';
import style from '../Table.module.scss';
import { getAccessibleColour } from '../../../common/utils/styleUtils';
/**
* Selects text colour to maintain accessible contrast
* @param bgColour
* @return {{backgroundColor, color: string}}
*/
const selCol = (bgColour) => {
if (bgColour != null && bgColour !== '') {
try {
const textColor = Color(bgColour).isLight() ? 'black' : 'white';
return { backgroundColor: bgColour, color: textColor };
} catch (error) {
console.log(`Unable to parse colour: ${bgColour}`);
}
}
};
import style from '../Table.module.scss';
export default function EventRow(props) {
const { row, index, selectedId, delay } = props;
const selected = row.original.id === selectedId;
const colours = selCol(row.original.colour);
const colours = getAccessibleColour(row.original.colour);
return (
<tr {...row.getRowProps()} className={selected ? style.selected : ''} id={row.original.id}>
+31 -101
View File
@@ -1,22 +1,25 @@
/* eslint-disable react/display-name */
import { useEffect, useState } from 'react';
import { EVENT_TABLE, EVENTS_TABLE, VIEW_SETTINGS } from 'common/api/apiConstants';
import { fetchEvent } from 'common/api/eventApi';
import { fetchAllEvents } from 'common/api/eventsApi';
import { useSocket } from 'common/context/socketContext';
import { useFetch } from 'common/hooks/useFetch';
import { useEffect, useMemo, useState } from 'react';
import { EVENT_TABLE, EVENTS_TABLE } from '../../common/api/apiConstants';
import { fetchEvent } from '../../common/api/eventApi';
import { fetchAllEvents } from '../../common/api/eventsApi';
import { useSocket } from '../../common/context/socketContext';
import { useFetch } from '../../common/hooks/useFetch';
import { getView } from '../../common/api/ontimeApi';
import useSubscription from '../../common/context/useSubscription';
import { eventPlaceholderSettings } from '../../common/api/ontimeApi';
const withSocket = (Component) => {
return (props) => {
const { data: eventsData } = useFetch(EVENTS_TABLE, fetchAllEvents);
const { data: genData } = useFetch(EVENT_TABLE, fetchEvent);
const { data: eventsData } = useFetch(EVENTS_TABLE, fetchAllEvents, {
placeholderData: [],
});
const { data: genData } = useFetch(EVENT_TABLE, fetchEvent, {
placeholderData: eventPlaceholderSettings,
});
const { data: viewSettings } = useFetch(VIEW_SETTINGS, getView);
const [publicEvents, setPublicEvents] = useState([]);
const [backstageEvents, setBackstageEvents] = useState([]);
const socket = useSocket();
const [pres, setPres] = useState({
text: '',
@@ -30,14 +33,16 @@ const withSocket = (Component) => {
text: '',
visible: false,
});
const [timer, setTimer] = useState({
const [publicSelectedId, setPublicSelectedId] = useState(null);
const [timer] = useSubscription('timer', {
clock: 0,
running: 0,
isNegative: false,
startedAt: null,
expectedFinish: null,
});
const [titles, setTitles] = useState({
const [titles] = useSubscription('titles', {
titleNow: '',
subtitleNow: '',
presenterNow: '',
@@ -45,7 +50,7 @@ const withSocket = (Component) => {
subtitleNext: '',
presenterNext: '',
});
const [publicTitles, setPublicTitles] = useState({
const [publicTitles] = useSubscription('publictitles', {
titleNow: '',
subtitleNow: '',
presenterNow: '',
@@ -53,18 +58,10 @@ const withSocket = (Component) => {
subtitleNext: '',
presenterNext: '',
});
const [selectedId, setSelectedId] = useState(null);
const [nextId, setNextId] = useState(null);
const [publicSelectedId, setPublicSelectedId] = useState(null);
const [general, setGeneral] = useState({
title: '',
url: '',
publicInfo: '',
backstageInfo: '',
endMessage: '',
});
const [playback, setPlayback] = useState(null);
const [onAir, setOnAir] = useState(false);
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(() => {
@@ -87,96 +84,29 @@ const withSocket = (Component) => {
setLower({ ...data });
});
// Handle timer
socket.on('timer', (data) => {
setTimer({ ...data });
});
// Handle playstate
socket.on('playstate', (data) => {
setPlayback(data);
});
socket.on('onAir', (data) => {
setOnAir(data);
});
// Handle titles
socket.on('titles', (data) => {
setTitles({ ...data });
});
socket.on('publictitles', (data) => {
setPublicTitles({ ...data });
});
// Handle selected event
socket.on('selected-id', (data) => {
setSelectedId(data);
});
socket.on('publicselected-id', (data) => {
setPublicSelectedId(data);
});
socket.on('next-id', (data) => {
setNextId(data);
});
// Ask for up to date data
socket.emit('get-messages');
// Ask for up to data
socket.emit('get-timer');
// ask for timer
socket.emit('get-timer');
// ask for playstate
socket.emit('get-playstate');
socket.emit('get-onAir');
// Ask for up titles
socket.emit('get-titles');
socket.emit('get-publictitles');
// Ask for up selected
socket.emit('get-selected-id');
socket.emit('get-next-id');
// Clear listeners
return () => {
socket.off('messages-public');
socket.off('messages-timer');
socket.off('messages-lower');
socket.off('timer');
socket.off('playstate');
socket.off('onAir');
socket.off('titles');
socket.off('publictitles');
socket.off('selected-id');
socket.emit('next-id');
};
}, [socket]);
// Filter events only to pass down
useEffect(() => {
if (!eventsData) {
return;
}
// filter just events with title
const publicEvents = useMemo(() => {
if (Array.isArray(eventsData)) {
const pe = eventsData.filter((d) => d.type === 'event' && d.title !== '' && d.isPublic);
setPublicEvents(pe);
// everything goes backstage
setBackstageEvents(eventsData);
return eventsData.filter((d) => d.type === 'event' && d.title !== '' && d.isPublic);
} else {
return [];
}
}, [eventsData]);
// Set general data
useEffect(() => {
if (!genData) {
return;
}
setGeneral(genData);
}, [genData]);
},[eventsData])
/********************************************/
/*** + titleManager ***/
@@ -245,12 +175,12 @@ const withSocket = (Component) => {
publicTitle={publicTitleManager}
time={timeManager}
events={publicEvents}
backstageEvents={backstageEvents}
backstageEvents={eventsData}
selectedId={selectedId}
publicSelectedId={publicSelectedId}
viewSettings={viewSettings}
nextId={nextId}
general={general}
general={genData}
onAir={onAir}
/>
);
@@ -1,13 +1,13 @@
import { useEffect, useState } from 'react';
import QRCode from 'react-qr-code';
import NavLogo from 'common/components/nav/NavLogo';
import Paginator from 'common/components/paginator/Paginator';
import TitleSide from 'common/components/title-side/TitleSide';
import { formatDisplay } from 'common/utils/dateConfig';
import { AnimatePresence, motion } from 'framer-motion';
import PropTypes from 'prop-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import Paginator from '../../../common/components/views/Paginator';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { getEventsWithDelay } from '../../../common/utils/eventsManager';
import { formatTime } from '../../../common/utils/time';
@@ -2,12 +2,12 @@ import { useEffect, useRef, useState } from 'react';
import QRCode from 'react-qr-code';
import { ReactComponent as Emptyimage } from 'assets/images/empty.svg';
import NavLogo from 'common/components/nav/NavLogo';
import Paginator from 'common/components/paginator/Paginator';
import { formatDisplay } from 'common/utils/dateConfig';
import { AnimatePresence, motion } from 'framer-motion';
import PropTypes from 'prop-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import Paginator from '../../../common/components/views/Paginator';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { formatTime } from '../../../common/utils/time';
@@ -1,12 +1,12 @@
import { useEffect, useState } from 'react';
import QRCode from 'react-qr-code';
import NavLogo from 'common/components/nav/NavLogo';
import Paginator from 'common/components/paginator/Paginator';
import TitleSide from 'common/components/title-side/TitleSide';
import { AnimatePresence, motion } from 'framer-motion';
import PropTypes from 'prop-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import Paginator from '../../../common/components/views/Paginator';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { formatTime } from '../../../common/utils/time';
import { titleVariants } from '../common/animation';
@@ -24,7 +24,6 @@ export default function Public(props) {
const [pageNumber, setPageNumber] = useState(0);
const [currentPage, setCurrentPage] = useState(0);
// Set window title
useEffect(() => {
document.title = 'ontime - Public Screen';
}, []);
@@ -36,7 +35,6 @@ export default function Public(props) {
// Format messages
const showPubl = publ.text !== '' && publ.visible;
const clock = formatTime(time.clock, formatOptions);
return (
@@ -136,7 +134,7 @@ Public.propTypes = {
publ: PropTypes.object,
publicTitle: PropTypes.object,
time: PropTypes.object,
events: PropTypes.object,
events: PropTypes.array,
publicSelectedId: PropTypes.string,
general: PropTypes.object,
viewSettings: PropTypes.object,
+19
View File
@@ -1583,6 +1583,25 @@
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07"
integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==
"@types/color-convert@*":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.0.tgz#8f5ee6b9e863dcbee5703f5a517ffb13d3ea4e22"
integrity sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==
dependencies:
"@types/color-name" "*"
"@types/color-name@*":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/color@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.3.tgz#e6d8d72b7aaef4bb9fe80847c26c7c786191016d"
integrity sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==
dependencies:
"@types/color-convert" "*"
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"