mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-11 18:33:53 +00:00
V2 ws store (#310)
* Update TimerService.ts * refactor: message service publishes to store * refactor: several type improvements * V2 ws store wss (#309) * refactor: shared logging types * refactor: simplify message service consumption * refactor: create discrete logging system * refactor: move socket.io > websocket
This commit is contained in:
@@ -9,40 +9,40 @@ import InputRow from './InputRow';
|
||||
import style from './MessageControl.module.scss';
|
||||
|
||||
export default function MessageControl() {
|
||||
const { data } = useMessageControl();
|
||||
const data = useMessageControl();
|
||||
|
||||
return (
|
||||
<div className={style.messageContainer}>
|
||||
<InputRow
|
||||
label='Timer screen message'
|
||||
placeholder='Shown in stage timer'
|
||||
text={data?.messages.presenter.text || ''}
|
||||
visible={data?.messages.presenter.visible || false}
|
||||
text={data.timerMessage.text || ''}
|
||||
visible={data.timerMessage.visible || false}
|
||||
changeHandler={(newValue) => setMessage.presenterText(newValue)}
|
||||
actionHandler={() => setMessage.presenterVisible(!data?.messages.presenter.visible)}
|
||||
actionHandler={() => setMessage.presenterVisible(!data.timerMessage.visible)}
|
||||
/>
|
||||
<InputRow
|
||||
label='Public / Backstage screen message'
|
||||
placeholder='Shown in public and backstage screens'
|
||||
text={data?.messages.public.text || ''}
|
||||
visible={data?.messages.public.visible || false}
|
||||
text={data.publicMessage.text || ''}
|
||||
visible={data.publicMessage.visible || false}
|
||||
changeHandler={(newValue) => setMessage.publicText(newValue)}
|
||||
actionHandler={() => setMessage.publicVisible(!data?.messages.public.visible)}
|
||||
actionHandler={() => setMessage.publicVisible(!data.publicMessage.visible)}
|
||||
/>
|
||||
<InputRow
|
||||
label='Lower third message'
|
||||
placeholder='Shown in lower third'
|
||||
text={data?.messages.lower.text || ''}
|
||||
visible={data?.messages.lower.visible || false}
|
||||
text={data.lowerMessage.text || ''}
|
||||
visible={data.lowerMessage.visible || false}
|
||||
changeHandler={(newValue) => setMessage.lowerText(newValue)}
|
||||
actionHandler={() => setMessage.lowerVisible(!data?.messages.lower.visible)}
|
||||
actionHandler={() => setMessage.lowerVisible(!data.lowerMessage.visible)}
|
||||
/>
|
||||
<div className={style.onAirSection}>
|
||||
<label className={style.label}>Toggle On Air state</label>
|
||||
<Button
|
||||
variant={data?.onAir ? 'ontime-filled' : 'ontime-subtle'}
|
||||
leftIcon={data?.onAir ? <IoMicSharp size='24px' /> : <IoMicOffOutline size='24px' />}
|
||||
onClick={() => setMessage.onAir(!data?.onAir)}
|
||||
variant={data.onAir ? 'ontime-filled' : 'ontime-subtle'}
|
||||
leftIcon={data.onAir ? <IoMicSharp size='24px' /> : <IoMicOffOutline size='24px' />}
|
||||
onClick={() => setMessage.onAir(!data.onAir)}
|
||||
>
|
||||
{data?.onAir ? 'Ontime is On Air' : 'Ontime is Off Air'}
|
||||
</Button>
|
||||
|
||||
@@ -5,24 +5,15 @@ import Transport from './Transport';
|
||||
|
||||
interface PlaybackButtonsProps {
|
||||
playback: Playback;
|
||||
selectedId: string | null;
|
||||
noEvents: boolean;
|
||||
}
|
||||
|
||||
export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
||||
const { playback, selectedId, noEvents } = props;
|
||||
const { playback, noEvents } = props;
|
||||
return (
|
||||
<>
|
||||
<PlaybackDisplay
|
||||
playback={playback}
|
||||
selectedId={selectedId}
|
||||
noEvents={noEvents}
|
||||
/>
|
||||
<Transport
|
||||
playback={playback}
|
||||
selectedId={selectedId}
|
||||
noEvents={noEvents}
|
||||
/>
|
||||
<PlaybackDisplay playback={playback} noEvents={noEvents} />
|
||||
<Transport playback={playback} noEvents={noEvents} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,19 +8,12 @@ import PlaybackTimer from './PlaybackTimer';
|
||||
import style from './PlaybackControl.module.scss';
|
||||
|
||||
export default function PlaybackControl() {
|
||||
const { data } = usePlaybackControl();
|
||||
const data = usePlaybackControl();
|
||||
|
||||
return (
|
||||
<div className={style.mainContainer}>
|
||||
<PlaybackTimer
|
||||
playback={data.playback as Playback}
|
||||
selectedId={data.selectedEventId}
|
||||
/>
|
||||
<PlaybackButtons
|
||||
playback={data.playback}
|
||||
selectedId={data.selectedEventId}
|
||||
noEvents={data.numEvents < 1}
|
||||
/>
|
||||
<PlaybackTimer playback={data.playback as Playback} />
|
||||
<PlaybackButtons playback={data.playback} noEvents={data.numEvents < 1} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,23 +11,23 @@ import style from './PlaybackControl.module.scss';
|
||||
|
||||
interface PlaybackProps {
|
||||
playback: Playback;
|
||||
selectedId: string | null;
|
||||
noEvents: boolean;
|
||||
}
|
||||
|
||||
export default function PlaybackDisplay(props: PlaybackProps) {
|
||||
const { playback, selectedId, noEvents } = props;
|
||||
const isRolling = playback === 'roll';
|
||||
const isPlaying = playback === 'play';
|
||||
const isPaused = playback === 'pause';
|
||||
const isArmed = playback === 'armed';
|
||||
const { playback, noEvents } = props;
|
||||
const isRolling = playback === Playback.Roll;
|
||||
const isPlaying = playback === Playback.Play;
|
||||
const isPaused = playback === Playback.Pause;
|
||||
const isArmed = playback === Playback.Armed;
|
||||
const isStopped = playback === Playback.Stop;
|
||||
|
||||
return (
|
||||
<div className={style.playbackContainer}>
|
||||
<TapButton
|
||||
onClick={() => setPlayback.start()}
|
||||
disabled={!selectedId || isRolling}
|
||||
theme='play'
|
||||
disabled={isStopped || isRolling}
|
||||
theme={Playback.Play}
|
||||
active={isPlaying}
|
||||
>
|
||||
<IoPlay />
|
||||
@@ -35,8 +35,8 @@ export default function PlaybackDisplay(props: PlaybackProps) {
|
||||
|
||||
<TapButton
|
||||
onClick={() => setPlayback.pause()}
|
||||
disabled={!selectedId || isRolling || isArmed}
|
||||
theme='pause'
|
||||
disabled={isStopped || isRolling || isArmed}
|
||||
theme={Playback.Pause}
|
||||
active={isPaused}
|
||||
>
|
||||
<IoPause />
|
||||
@@ -44,8 +44,8 @@ export default function PlaybackDisplay(props: PlaybackProps) {
|
||||
|
||||
<TapButton
|
||||
onClick={() => setPlayback.roll()}
|
||||
disabled={noEvents}
|
||||
theme='roll'
|
||||
disabled={!isStopped || noEvents}
|
||||
theme={Playback.Roll}
|
||||
active={isRolling}
|
||||
>
|
||||
<IoTimeOutline />
|
||||
|
||||
@@ -4,33 +4,33 @@ import { Playback } from 'ontime-types';
|
||||
import TimerDisplay from '../../../common/components/timer-display/TimerDisplay';
|
||||
import { setPlayback, useTimer } from '../../../common/hooks/useSocket';
|
||||
import { millisToMinutes } from '../../../common/utils/dateConfig';
|
||||
import { stringFromMillis } from '../../../common/utils/time';
|
||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||
|
||||
import TapButton from './TapButton';
|
||||
|
||||
import style from './PlaybackControl.module.scss';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
interface PlaybackTimerProps {
|
||||
playback: Playback;
|
||||
selectedId: string | null;
|
||||
}
|
||||
|
||||
export default function PlaybackTimer(props: PlaybackTimerProps) {
|
||||
const { playback, selectedId } = props;
|
||||
const { data: timerData } = useTimer();
|
||||
const { playback } = props;
|
||||
const data = useTimer();
|
||||
|
||||
// TODO: checkout typescript in utilities
|
||||
const started = stringFromMillis(timerData?.startedAt, true);
|
||||
const finish = stringFromMillis(timerData.expectedFinish, true);
|
||||
const isRolling = playback === 'roll';
|
||||
const isWaiting = timerData.secondaryTimer !== null && timerData.secondaryTimer > 0 && timerData.current === null;
|
||||
const disableButtons = selectedId === null || isRolling;
|
||||
const isOvertime = timerData.current !== null && timerData.current < 0;
|
||||
const hasAddedTime = Boolean(timerData.addedTime);
|
||||
const started = millisToString(data.timer.startedAt);
|
||||
const finish = millisToString(data.timer.expectedFinish);
|
||||
const isRolling = playback === Playback.Roll;
|
||||
const isStopped = playback === Playback.Stop;
|
||||
const isWaiting = data.timer.secondaryTimer !== null && data.timer.secondaryTimer > 0 && data.timer.current === null;
|
||||
const disableButtons = isStopped || isRolling;
|
||||
const isOvertime = data.timer.current !== null && data.timer.current < 0;
|
||||
const hasAddedTime = Boolean(data.timer.addedTime);
|
||||
|
||||
const rollLabel = isRolling ? 'Roll mode active' : '';
|
||||
const addedTimeLabel = hasAddedTime ? `Added ${millisToMinutes(timerData.addedTime)} minutes` : '';
|
||||
const addedTimeLabel = hasAddedTime ? `Added ${millisToMinutes(data.timer.addedTime)} minutes` : '';
|
||||
|
||||
return (
|
||||
<div className={style.timeContainer}>
|
||||
@@ -44,7 +44,7 @@ export default function PlaybackTimer(props: PlaybackTimerProps) {
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={style.timer}>
|
||||
<TimerDisplay time={isWaiting ? timerData.secondaryTimer : timerData.current} />
|
||||
<TimerDisplay time={isWaiting ? data.timer.secondaryTimer : data.timer.current} />
|
||||
</div>
|
||||
{isWaiting ? (
|
||||
<div className={style.roll}>
|
||||
|
||||
@@ -14,46 +14,33 @@ import style from './PlaybackControl.module.scss';
|
||||
|
||||
interface TransportProps {
|
||||
playback: Playback;
|
||||
selectedId: string | null;
|
||||
noEvents: boolean;
|
||||
}
|
||||
|
||||
export default function Transport(props: TransportProps) {
|
||||
const { playback, selectedId, noEvents } = props;
|
||||
const isRolling = playback === 'roll';
|
||||
const { playback, noEvents } = props;
|
||||
const isRolling = playback === Playback.Roll;
|
||||
const isStopped = playback === Playback.Stop;
|
||||
|
||||
return (
|
||||
<div className={style.playbackContainer}>
|
||||
<Tooltip label='Previous event' openDelay={tooltipDelayMid}>
|
||||
<TapButton
|
||||
onClick={() => setPlayback.previous()}
|
||||
disabled={isRolling || noEvents}
|
||||
>
|
||||
<TapButton onClick={() => setPlayback.previous()} disabled={isRolling || noEvents}>
|
||||
<IoPlaySkipBack />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Next event' openDelay={tooltipDelayMid}>
|
||||
<TapButton
|
||||
onClick={() => setPlayback.next()}
|
||||
disabled={isRolling || noEvents}
|
||||
>
|
||||
<TapButton onClick={() => setPlayback.next()} disabled={isRolling || noEvents}>
|
||||
<IoPlaySkipForward />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Reload event' openDelay={tooltipDelayMid}>
|
||||
<TapButton
|
||||
onClick={() => setPlayback.reload()}
|
||||
disabled={!selectedId || isRolling}
|
||||
>
|
||||
<TapButton onClick={() => setPlayback.reload()} disabled={isStopped || isRolling}>
|
||||
<IoReload className={style.invertX} />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Unload Event' openDelay={tooltipDelayMid}>
|
||||
<TapButton
|
||||
onClick={() => setPlayback.stop()}
|
||||
disabled={!selectedId && !isRolling}
|
||||
theme='stop'
|
||||
>
|
||||
<TapButton onClick={() => setPlayback.stop()} disabled={isStopped && !isRolling} theme={Playback.Stop}>
|
||||
<IoStop />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button, Select, Switch } from '@chakra-ui/react';
|
||||
import { IoBan } from '@react-icons/all-files/io5/IoBan';
|
||||
import { useAtom } from 'jotai';
|
||||
import { OntimeEvent, TimerType } from 'ontime-types';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
import { editorEventId } from '../../common/atoms/LocalEventSettings';
|
||||
import CopyTag from '../../common/components/copy-tag/CopyTag';
|
||||
import ColourInput from '../../common/components/input/colour-input/ColourInput';
|
||||
import TextInput from '../../common/components/input/text-input/TextInput';
|
||||
import TimeInput from '../../common/components/input/time-input/TimeInput';
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
import { useEventAction } from '../../common/hooks/useEventAction';
|
||||
import useRundown from '../../common/hooks-query/useRundown';
|
||||
import { useEmitLog } from '../../common/stores/logger';
|
||||
import { millisToMinutes } from '../../common/utils/dateConfig';
|
||||
import getDelayTo from '../../common/utils/getDelayTo';
|
||||
import { stringFromMillis } from '../../common/utils/time';
|
||||
import { calculateDuration, TimeEntryField, validateEntry } from '../../common/utils/timesManager';
|
||||
|
||||
import style from './EventEditor.module.scss';
|
||||
@@ -25,7 +25,7 @@ export type EventEditorSubmitActions = keyof OntimeEvent | 'durationOverride';
|
||||
export default function EventEditor() {
|
||||
const [openId] = useAtom(editorEventId);
|
||||
const { data } = useRundown();
|
||||
const { emitWarning, emitError } = useContext(LoggingContext);
|
||||
const { emitWarning, emitError } = useEmitLog();
|
||||
const { updateEvent } = useEventAction();
|
||||
const [event, setEvent] = useState<OntimeEvent | null>(null);
|
||||
const [delay, setDelay] = useState(0);
|
||||
@@ -121,8 +121,8 @@ export default function EventEditor() {
|
||||
|
||||
const delayed = delay !== 0;
|
||||
const addedTime = delayed ? `${delay >= 0 ? '+' : '-'} ${millisToMinutes(Math.abs(delay))} minutes` : null;
|
||||
const newStart = delayed ? `New start ${stringFromMillis(event.timeStart + delay)}` : null;
|
||||
const newEnd = delayed ? `New end ${stringFromMillis(event.timeEnd + delay)}` : null;
|
||||
const newStart = delayed ? `New start ${millisToString(event.timeStart + delay)}` : null;
|
||||
const newEnd = delayed ? `New end ${millisToString(event.timeEnd + delay)}` : null;
|
||||
|
||||
return (
|
||||
<div className={style.eventEditor}>
|
||||
|
||||
@@ -1,52 +1,21 @@
|
||||
import { useState } from 'react';
|
||||
import { PropsWithChildren, useState } from 'react';
|
||||
|
||||
import CollapseBar from '../../common/components/collapse-bar/CollapseBar';
|
||||
|
||||
import style from './Info.module.scss';
|
||||
|
||||
type TitleShape = {
|
||||
title: string;
|
||||
presenter: string;
|
||||
subtitle: string;
|
||||
note: string;
|
||||
}
|
||||
|
||||
interface CollapsableInfoProps {
|
||||
title: string;
|
||||
data: TitleShape;
|
||||
}
|
||||
|
||||
export default function CollapsableInfo(props: CollapsableInfoProps) {
|
||||
const { title, data } = props;
|
||||
export default function CollapsableInfo(props: PropsWithChildren<CollapsableInfoProps>) {
|
||||
const { title, children } = props;
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={style.container}>
|
||||
<CollapseBar
|
||||
title={title}
|
||||
isCollapsed={collapsed}
|
||||
onClick={() => setCollapsed((prev) => !prev)}
|
||||
/>
|
||||
{!collapsed && (
|
||||
<div className={style.labels}>
|
||||
<div>
|
||||
<span className={style.label}>Title:</span>
|
||||
<span className={style.content}>{data.title}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className={style.label}>Presenter:</span>
|
||||
<span className={style.content}>{data.presenter}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className={style.label}>Subtitle:</span>
|
||||
<span className={style.content}>{data.subtitle}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className={style.label}>Note:</span>
|
||||
<span className={style.content}>{data.note}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<CollapseBar title={title} isCollapsed={collapsed} onClick={() => setCollapsed((prev) => !prev)} />
|
||||
{!collapsed && children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { useInfoPanel } from '../../common/hooks/useSocket';
|
||||
|
||||
import InfoTitle from './CollapsableInfo';
|
||||
import InfoLogger from './InfoLogger';
|
||||
import InfoNif from './InfoNif';
|
||||
|
||||
import style from './Info.module.scss';
|
||||
|
||||
export default function Info() {
|
||||
const { data } = useInfoPanel();
|
||||
|
||||
const titlesNow = {
|
||||
title: data.titles.titleNow,
|
||||
subtitle: data.titles.subtitleNow,
|
||||
presenter: data.titles.presenterNow,
|
||||
note: data.titles.noteNow,
|
||||
};
|
||||
|
||||
const titlesNext = {
|
||||
title: data.titles.titleNext,
|
||||
subtitle: data.titles.subtitleNext,
|
||||
presenter: data.titles.presenterNext,
|
||||
note: data.titles.noteNext,
|
||||
};
|
||||
|
||||
const selected = !data.numEvents
|
||||
? 'No events'
|
||||
: `Event ${data.selectedEventIndex != null ? data.selectedEventIndex + 1 : '-'} / ${
|
||||
data.numEvents ? data.numEvents : '-'
|
||||
}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={style.panelHeader}>
|
||||
<span>Ontime running on port 4001</span>
|
||||
<span>{selected}</span>
|
||||
</div>
|
||||
<InfoNif />
|
||||
<InfoTitle title='Playing Now' data={titlesNow} />
|
||||
<InfoTitle title='Playing Next' data={titlesNext} />
|
||||
<InfoLogger />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useInfoPanel } from '../../common/hooks/useSocket';
|
||||
|
||||
import CollapsableInfo from './CollapsableInfo';
|
||||
import InfoLogger from './InfoLogger';
|
||||
import InfoNif from './InfoNif';
|
||||
import InfoTitles from './InfoTitles';
|
||||
|
||||
import style from './Info.module.scss';
|
||||
|
||||
export default function Info() {
|
||||
const data = useInfoPanel();
|
||||
|
||||
const titlesNow = {
|
||||
title: data.titles.titleNow || '',
|
||||
subtitle: data.titles.subtitleNow || '',
|
||||
presenter: data.titles.presenterNow || '',
|
||||
note: data.titles.noteNow || '',
|
||||
};
|
||||
|
||||
const titlesNext = {
|
||||
title: data.titles.titleNext || '',
|
||||
subtitle: data.titles.subtitleNext || '',
|
||||
presenter: data.titles.presenterNext || '',
|
||||
note: data.titles.noteNext || '',
|
||||
};
|
||||
|
||||
const selected = !data.numEvents
|
||||
? 'No events'
|
||||
: `Event ${data.selectedEventIndex !== null ? data.selectedEventIndex + 1 : '-'} / ${
|
||||
data.numEvents ? data.numEvents : '-'
|
||||
}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={style.panelHeader}>
|
||||
<span>Ontime running on port 4001</span>
|
||||
<span>{selected}</span>
|
||||
</div>
|
||||
<CollapsableInfo title='Network Info'>
|
||||
<InfoNif />
|
||||
</CollapsableInfo>
|
||||
<CollapsableInfo title='Playing Now'>
|
||||
<InfoTitles data={titlesNow} />
|
||||
</CollapsableInfo>
|
||||
<CollapsableInfo title='Playing Next'>
|
||||
<InfoTitles data={titlesNext} />
|
||||
</CollapsableInfo>
|
||||
<CollapsableInfo title='Log'>
|
||||
<InfoLogger />
|
||||
</CollapsableInfo>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -6,12 +6,7 @@ $info-hover: $section-white;
|
||||
|
||||
.infoLoggerContainer {
|
||||
max-height: 80%;
|
||||
margin-top: 32px;
|
||||
|
||||
&.expanded {
|
||||
min-height: 50%;
|
||||
height: 100%
|
||||
}
|
||||
height: 100%
|
||||
}
|
||||
|
||||
.log {
|
||||
@@ -24,6 +19,7 @@ $info-hover: $section-white;
|
||||
.logEntry {
|
||||
display: flex;
|
||||
margin-bottom: 2px;
|
||||
|
||||
&.INFO {
|
||||
color: $info-gray;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
|
||||
import CollapseBar from '../../common/components/collapse-bar/CollapseBar';
|
||||
import { Log, LoggingContext } from '../../common/context/LoggingContext';
|
||||
import { clearLogs, useLogData } from '../../common/stores/logger';
|
||||
|
||||
import style from './InfoLogger.module.scss';
|
||||
|
||||
enum LOG_FILTER {
|
||||
USER = 'USER',
|
||||
CLIENT = 'CLIENT',
|
||||
SERVER = 'SERVER',
|
||||
enum LogFilter {
|
||||
User = 'USER',
|
||||
Client = 'CLIENT',
|
||||
Server = 'SERVER',
|
||||
RX = 'RX',
|
||||
TX = 'TX',
|
||||
PLAYBACK = 'PLAYBACK',
|
||||
Playback = 'PLAYBACK',
|
||||
}
|
||||
|
||||
export default function InfoLogger() {
|
||||
const { logData, clearLog } = useContext(LoggingContext);
|
||||
const [data, setData] = useState<Log[]>([]);
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const { logs: logData } = useLogData();
|
||||
|
||||
const [showClient, setShowClient] = useState(true);
|
||||
const [showServer, setShowServer] = useState(true);
|
||||
const [showRx, setShowRx] = useState(true);
|
||||
@@ -26,123 +24,107 @@ export default function InfoLogger() {
|
||||
const [showPlayback, setShowPlayback] = useState(true);
|
||||
const [showUser, setShowUser] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!logData) {
|
||||
return;
|
||||
}
|
||||
const matchers: LogFilter[] = [];
|
||||
if (showUser) {
|
||||
matchers.push(LogFilter.User);
|
||||
}
|
||||
if (showClient) {
|
||||
matchers.push(LogFilter.Client);
|
||||
}
|
||||
if (showServer) {
|
||||
matchers.push(LogFilter.Server);
|
||||
}
|
||||
if (showRx) {
|
||||
matchers.push(LogFilter.RX);
|
||||
}
|
||||
if (showTx) {
|
||||
matchers.push(LogFilter.TX);
|
||||
}
|
||||
if (showPlayback) {
|
||||
matchers.push(LogFilter.Playback);
|
||||
}
|
||||
|
||||
const matchers: LOG_FILTER[] = [];
|
||||
if (showUser) {
|
||||
matchers.push(LOG_FILTER.USER);
|
||||
}
|
||||
if (showClient) {
|
||||
matchers.push(LOG_FILTER.CLIENT);
|
||||
}
|
||||
if (showServer) {
|
||||
matchers.push(LOG_FILTER.SERVER);
|
||||
}
|
||||
if (showRx) {
|
||||
matchers.push(LOG_FILTER.RX);
|
||||
}
|
||||
if (showTx) {
|
||||
matchers.push(LOG_FILTER.TX);
|
||||
}
|
||||
if (showPlayback) {
|
||||
matchers.push(LOG_FILTER.PLAYBACK);
|
||||
}
|
||||
const filteredData = logData.filter((entry) => matchers.some((match) => entry.origin === match));
|
||||
|
||||
const filteredData = logData.filter((entry) => matchers.some((match) => entry.origin === match));
|
||||
setData(filteredData);
|
||||
}, [logData, showUser, showClient, showServer, showPlayback, showRx, showTx]);
|
||||
|
||||
const disableOthers = useCallback((toEnable: LOG_FILTER) => {
|
||||
toEnable === LOG_FILTER.USER ? setShowUser(true) : setShowUser(false);
|
||||
toEnable === LOG_FILTER.CLIENT ? setShowClient(true) : setShowClient(false);
|
||||
toEnable === LOG_FILTER.SERVER ? setShowServer(true) : setShowServer(false);
|
||||
toEnable === LOG_FILTER.RX ? setShowRx(true) : setShowRx(false);
|
||||
toEnable === LOG_FILTER.TX ? setShowTx(true) : setShowTx(false);
|
||||
toEnable === LOG_FILTER.PLAYBACK ? setShowPlayback(true) : setShowPlayback(false);
|
||||
const disableOthers = useCallback((toEnable: LogFilter) => {
|
||||
toEnable === LogFilter.User ? setShowUser(true) : setShowUser(false);
|
||||
toEnable === LogFilter.Client ? setShowClient(true) : setShowClient(false);
|
||||
toEnable === LogFilter.Server ? setShowServer(true) : setShowServer(false);
|
||||
toEnable === LogFilter.RX ? setShowRx(true) : setShowRx(false);
|
||||
toEnable === LogFilter.TX ? setShowTx(true) : setShowTx(false);
|
||||
toEnable === LogFilter.Playback ? setShowPlayback(true) : setShowPlayback(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`${style.infoLoggerContainer} ${collapsed? '' : style.expanded}`}>
|
||||
<CollapseBar title='Log' isCollapsed={collapsed} onClick={() => setCollapsed((prev) => !prev)} />
|
||||
{!collapsed && (
|
||||
<>
|
||||
<div className={style.buttonBar}>
|
||||
<Button
|
||||
variant={showUser ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowUser((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LOG_FILTER.USER)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
USER
|
||||
</Button>
|
||||
<Button
|
||||
variant={showClient ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowClient((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LOG_FILTER.CLIENT)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
CLIENT
|
||||
</Button>
|
||||
<Button
|
||||
variant={showServer ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowServer((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LOG_FILTER.SERVER)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
SERVER
|
||||
</Button>
|
||||
<Button
|
||||
variant={showPlayback ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowPlayback((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LOG_FILTER.PLAYBACK)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
PLAYBACK
|
||||
</Button>
|
||||
<Button
|
||||
variant={showRx ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowRx((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LOG_FILTER.RX)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
RX
|
||||
</Button>
|
||||
<Button
|
||||
variant={showTx ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowTx((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LOG_FILTER.TX)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
TX
|
||||
</Button>
|
||||
<Button
|
||||
variant='ontime-outlined'
|
||||
size='xs'
|
||||
onClick={clearLog}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
<ul className={style.log}>
|
||||
{data.map((logEntry) => (
|
||||
<li key={logEntry.id} className={`${style.logEntry} ${style[logEntry.level]} `}>
|
||||
<span className={style.time}>{logEntry.time}</span>
|
||||
<span className={style.origin}>{logEntry.origin}</span>
|
||||
<span className={style.msg}>{logEntry.text}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
<div className={style.infoLoggerContainer}>
|
||||
<div className={style.buttonBar}>
|
||||
<Button
|
||||
variant={showUser ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowUser((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LogFilter.User)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
{LogFilter.User}
|
||||
</Button>
|
||||
<Button
|
||||
variant={showClient ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowClient((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LogFilter.Client)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
{LogFilter.Client}
|
||||
</Button>
|
||||
<Button
|
||||
variant={showServer ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowServer((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LogFilter.Server)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
{LogFilter.Server}
|
||||
</Button>
|
||||
<Button
|
||||
variant={showPlayback ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowPlayback((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LogFilter.Playback)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
{LogFilter.Playback}
|
||||
</Button>
|
||||
<Button
|
||||
variant={showRx ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowRx((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LogFilter.RX)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
{LogFilter.RX}
|
||||
</Button>
|
||||
<Button
|
||||
variant={showTx ? 'ontime-filled' : 'ontime-subtle'}
|
||||
size='xs'
|
||||
onClick={() => setShowTx((s) => !s)}
|
||||
onAuxClick={() => disableOthers(LogFilter.TX)}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
{LogFilter.TX}
|
||||
</Button>
|
||||
<Button variant='ontime-outlined' size='xs' onClick={clearLogs}>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
<ul className={style.log}>
|
||||
{filteredData.map((logEntry) => (
|
||||
<li key={logEntry.id} className={`${style.logEntry} ${style[logEntry.level]} `}>
|
||||
<span className={style.time}>{logEntry.time}</span>
|
||||
<span className={style.origin}>{logEntry.origin}</span>
|
||||
<span className={style.msg}>{logEntry.text}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||
|
||||
import CollapseBar from '../../common/components/collapse-bar/CollapseBar';
|
||||
import useInfo from '../../common/hooks-query/useInfo';
|
||||
import { openLink } from '../../common/utils/linkUtils';
|
||||
|
||||
@@ -9,7 +7,6 @@ import style from './Info.module.scss';
|
||||
|
||||
export default function InfoNif() {
|
||||
const { data } = useInfo();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
const handleClick = (address: string) => {
|
||||
const baseURL = 'http://__IP__:4001';
|
||||
@@ -17,18 +14,13 @@ export default function InfoNif() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.container}>
|
||||
<CollapseBar title='Network Info' isCollapsed={collapsed} onClick={() => setCollapsed((prev) => !prev)} />
|
||||
{!collapsed && (
|
||||
<div className={style.interfaceList}>
|
||||
{data?.networkInterfaces.map((nif) => (
|
||||
<span key={nif.address} onClick={() => handleClick(nif.address)} className={style.interface}>
|
||||
{`${nif.name} - ${nif.address}`}
|
||||
<IoArrowUp className={style.linkIcon} />
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className={style.interfaceList}>
|
||||
{data?.networkInterfaces.map((nif) => (
|
||||
<span key={nif.address} onClick={() => handleClick(nif.address)} className={style.interface}>
|
||||
{`${nif.name} - ${nif.address}`}
|
||||
<IoArrowUp className={style.linkIcon} />
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import style from './Info.module.scss';
|
||||
|
||||
type TitleShape = {
|
||||
title: string;
|
||||
presenter: string;
|
||||
subtitle: string;
|
||||
note: string;
|
||||
};
|
||||
|
||||
interface InfoTitleProps {
|
||||
data: TitleShape;
|
||||
}
|
||||
|
||||
export default function InfoTitles(props: InfoTitleProps) {
|
||||
const { data } = props;
|
||||
return (
|
||||
<div className={style.labels}>
|
||||
<div>
|
||||
<span className={style.label}>Title:</span>
|
||||
<span className={style.content}>{data.title}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className={style.label}>Presenter:</span>
|
||||
<span className={style.content}>{data.presenter}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className={style.label}>Subtitle:</span>
|
||||
<span className={style.content}>{data.subtitle}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className={style.label}>Note:</span>
|
||||
<span className={style.content}>{data.note}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
/* eslint-disable jsx-a11y/anchor-has-content */
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button, IconButton, Input, ModalBody, Tooltip } from '@chakra-ui/react';
|
||||
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
||||
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
|
||||
import { IoSunny } from '@react-icons/all-files/io5/IoSunny';
|
||||
|
||||
import { useEmitLog } from '@/common/stores/logger';
|
||||
|
||||
import { viewerLocations } from '../../appConstants';
|
||||
import { postAliases } from '../../common/api/ontimeApi';
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
import useAliases from '../../common/hooks-query/useAliases';
|
||||
import { validateAlias } from '../../common/utils/aliases';
|
||||
import { handleLinks, host } from '../../common/utils/linkUtils';
|
||||
@@ -19,7 +20,7 @@ import style from './Modals.module.scss';
|
||||
|
||||
export default function AliasesModal() {
|
||||
const { data, status, refetch } = useAliases();
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { emitError } = useEmitLog();
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [aliases, setAliases] = useState([]);
|
||||
@@ -111,29 +112,32 @@ export default function AliasesModal() {
|
||||
* @param {string} id - object id
|
||||
* @param {boolean} isEnabled - whether to enable / disable flag
|
||||
*/
|
||||
const setEnabled = useCallback((id, isEnabled) => {
|
||||
const aliasesState = [...aliases];
|
||||
for (const a of aliasesState) {
|
||||
if (a.id === id) {
|
||||
if (isEnabled) {
|
||||
if (a.alias === '' || a.pathAndParams === '') {
|
||||
emitError('Alias incomplete');
|
||||
break;
|
||||
}
|
||||
const setEnabled = useCallback(
|
||||
(id, isEnabled) => {
|
||||
const aliasesState = [...aliases];
|
||||
for (const a of aliasesState) {
|
||||
if (a.id === id) {
|
||||
if (isEnabled) {
|
||||
if (a.alias === '' || a.pathAndParams === '') {
|
||||
emitError('Alias incomplete');
|
||||
break;
|
||||
}
|
||||
|
||||
const isRepeated = aliases.some((r) => a.alias === r.alias && r.enabled);
|
||||
if (isRepeated) {
|
||||
emitError('There is already an alias with this name');
|
||||
break;
|
||||
const isRepeated = aliases.some((r) => a.alias === r.alias && r.enabled);
|
||||
if (isRepeated) {
|
||||
emitError('There is already an alias with this name');
|
||||
break;
|
||||
}
|
||||
}
|
||||
a.enabled = isEnabled;
|
||||
break;
|
||||
}
|
||||
a.enabled = isEnabled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setChanged(true);
|
||||
setAliases(aliasesState);
|
||||
}, [aliases, emitError]);
|
||||
setChanged(true);
|
||||
setAliases(aliasesState);
|
||||
},
|
||||
[aliases, emitError],
|
||||
);
|
||||
|
||||
/**
|
||||
* Reverts local state equals to server state
|
||||
@@ -194,16 +198,16 @@ export default function AliasesModal() {
|
||||
eg. a lower third url with some custom parameters
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={style.labelNote} style={{ width: '30%' }}>
|
||||
Alias
|
||||
</td>
|
||||
<td className={style.labelNote}>Page URL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mylower</td>
|
||||
<td>lower?bg=ff2&text=f00&size=0.6&transition=5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={style.labelNote} style={{ width: '30%' }}>
|
||||
Alias
|
||||
</td>
|
||||
<td className={style.labelNote}>Page URL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mylower</td>
|
||||
<td>lower?bg=ff2&text=f00&size=0.6&transition=5</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
@@ -212,16 +216,16 @@ export default function AliasesModal() {
|
||||
eg. an unattended screen that you would need to change route from the app
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={style.labelNote} style={{ width: '30%' }}>
|
||||
Alias
|
||||
</td>
|
||||
<td className={style.labelNote}>Page URL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>thirdfloor</td>
|
||||
<td>public</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={style.labelNote} style={{ width: '30%' }}>
|
||||
Alias
|
||||
</td>
|
||||
<td className={style.labelNote}>Page URL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>thirdfloor</td>
|
||||
<td>public</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -254,12 +258,7 @@ export default function AliasesModal() {
|
||||
onChange={(event) => handleChange(index, 'pathAndParams', event.target.value)}
|
||||
/>
|
||||
<Tooltip label={`Test /${alias.pathAndParams}`} openDelay={tooltipDelayFast}>
|
||||
<a
|
||||
href='#!'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
onClick={(e) => handleLinks(e, alias.pathAndParams)}
|
||||
/>
|
||||
<a href='#!' target='_blank' rel='noreferrer' onClick={(e) => handleLinks(e, alias.pathAndParams)} />
|
||||
</Tooltip>
|
||||
<Tooltip label='Enable alias' openDelay={tooltipDelayFast}>
|
||||
<IconButton
|
||||
@@ -281,12 +280,8 @@ export default function AliasesModal() {
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{alias.aliasError ? (
|
||||
<div className={style.error}>{`Alias error: ${alias.aliasError}`}</div>
|
||||
) : null}
|
||||
{alias.urlError ? (
|
||||
<div className={style.error}>{`URL error: ${alias.urlError}`}</div>
|
||||
) : null}
|
||||
{alias.aliasError ? <div className={style.error}>{`Alias error: ${alias.aliasError}`}</div> : null}
|
||||
{alias.urlError ? <div className={style.error}>{`URL error: ${alias.urlError}`}</div> : null}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -296,12 +291,7 @@ export default function AliasesModal() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<SubmitContainer
|
||||
revert={revert}
|
||||
submitting={submitting}
|
||||
changed={changed}
|
||||
status={status}
|
||||
/>
|
||||
<SubmitContainer revert={revert} submitting={submitting} changed={changed} status={status} />
|
||||
</form>
|
||||
</ModalBody>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import isEqual from 'react-fast-compare';
|
||||
import {
|
||||
Button,
|
||||
@@ -16,11 +16,12 @@ import { FiEye } from '@react-icons/all-files/fi/FiEye';
|
||||
import { FiX } from '@react-icons/all-files/fi/FiX';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
import { useEmitLog } from '@/common/stores/logger';
|
||||
|
||||
import { version } from '../../../package.json';
|
||||
import { getLatestVersion, postSettings } from '../../common/api/ontimeApi';
|
||||
import { eventSettingsAtom } from '../../common/atoms/LocalEventSettings';
|
||||
import TooltipActionBtn from '../../common/components/buttons/TooltipActionBtn';
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
import useSettings from '../../common/hooks-query/useSettings';
|
||||
import { ontimePlaceholderSettings } from '../../common/models/OntimeSettings';
|
||||
|
||||
@@ -31,7 +32,7 @@ import style from './Modals.module.scss';
|
||||
|
||||
export default function AppSettingsModal() {
|
||||
const { data, status, refetch } = useSettings();
|
||||
const { emitError, emitWarning } = useContext(LoggingContext);
|
||||
const { emitError, emitWarning } = useEmitLog();
|
||||
const [formData, setFormData] = useState(ontimePlaceholderSettings);
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { FormLabel, Input, ModalBody, Textarea } from '@chakra-ui/react';
|
||||
|
||||
import { useEmitLog } from '@/common/stores/logger';
|
||||
|
||||
import { postEventData } from '../../common/api/eventDataApi';
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
import useEventData from '../../common/hooks-query/useEventData';
|
||||
import { eventDataPlaceholder } from '../../common/models/EventData';
|
||||
|
||||
@@ -13,7 +14,7 @@ import style from './Modals.module.scss';
|
||||
|
||||
export default function SettingsModal() {
|
||||
const { data, status, refetch } = useEventData();
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { emitError } = useEmitLog();
|
||||
const [formData, setFormData] = useState(eventDataPlaceholder);
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Input, ModalBody } from '@chakra-ui/react';
|
||||
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
||||
|
||||
import { useEmitLog } from '@/common/stores/logger';
|
||||
|
||||
import { postUserFields } from '../../common/api/ontimeApi';
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
import useUserFields from '../../common/hooks-query/useUserFields';
|
||||
import { userFieldsPlaceholder } from '../../common/models/UserFields';
|
||||
import { handleLinks, host } from '../../common/utils/linkUtils';
|
||||
@@ -14,7 +15,7 @@ import style from './Modals.module.scss';
|
||||
|
||||
export default function TableOptionsModal() {
|
||||
const { data, status, refetch } = useUserFields();
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { emitError } = useEmitLog();
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [userFields, setUserFields] = useState(userFieldsPlaceholder);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { FormControl, FormLabel, ModalBody } from '@chakra-ui/react';
|
||||
import { IoCheckmarkSharp } from '@react-icons/all-files/io5/IoCheckmarkSharp';
|
||||
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
||||
|
||||
import { useEmitLog } from '@/common/stores/logger';
|
||||
|
||||
import { postView } from '../../common/api/ontimeApi';
|
||||
import EnableBtn from '../../common/components/buttons/EnableBtn';
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
||||
import { viewsSettingsPlaceholder } from '../../common/models/ViewSettings.type';
|
||||
import { openLink } from '../../common/utils/linkUtils';
|
||||
@@ -17,7 +18,7 @@ import style from './Modals.module.scss';
|
||||
export default function ViewsSettingsModal() {
|
||||
const { data, status, refetch } = useViewSettings();
|
||||
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { emitError } = useEmitLog();
|
||||
const [formData, setFormData] = useState(viewsSettingsPlaceholder);
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { useContext } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Button, FormControl, Input, ModalBody, ModalFooter, Switch } from '@chakra-ui/react';
|
||||
|
||||
import { postOSC } from '../../../common/api/ontimeApi';
|
||||
import { LoggingContext } from '../../../common/context/LoggingContext';
|
||||
import useOscSettings from '../../../common/hooks-query/useOscSettings';
|
||||
import { PlaceholderSettings } from '../../../common/models/OscSettings';
|
||||
import { useEmitLog } from '../../../common/stores/logger';
|
||||
import { isIPAddress, isOnlyNumbers } from '../../../common/utils/regex';
|
||||
|
||||
import styles from '../Modal.module.scss';
|
||||
|
||||
export default function OscIntegrationSettings() {
|
||||
const { data } = useOscSettings();
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { emitError } = useEmitLog();
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { FormControl, FormLabel, Input, ModalBody } from '@chakra-ui/react';
|
||||
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
||||
|
||||
import { useEmitLog } from '@/common/stores/logger';
|
||||
|
||||
import { postOSC } from '../../../common/api/ontimeApi';
|
||||
import EnableBtn from '../../../common/components/buttons/EnableBtn';
|
||||
import { LoggingContext } from '../../../common/context/LoggingContext';
|
||||
import useOscSettings from '../../../common/hooks-query/useOscSettings';
|
||||
import { oscPlaceholderSettings } from '../../../common/models/OscSettings';
|
||||
import { inputProps, portInputProps } from '../modalHelper';
|
||||
@@ -76,7 +77,7 @@ const oscTriggerEndpoints = [
|
||||
|
||||
export default function OscSettingsModal() {
|
||||
const { data, status, refetch } = useOscSettings();
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { emitError } = useEmitLog();
|
||||
const [formData, setFormData] = useState(oscPlaceholderSettings);
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
@@ -122,8 +123,8 @@ export default function OscSettingsModal() {
|
||||
} else {
|
||||
try {
|
||||
await postOSC(formData);
|
||||
} catch (error){
|
||||
emitError(`Error setting OSC: ${error}`)
|
||||
} catch (error) {
|
||||
emitError(`Error setting OSC: ${error}`);
|
||||
} finally {
|
||||
await refetch();
|
||||
setChanged(false);
|
||||
@@ -131,7 +132,7 @@ export default function OscSettingsModal() {
|
||||
}
|
||||
setSubmitting(false);
|
||||
},
|
||||
[emitError, formData, refetch]
|
||||
[emitError, formData, refetch],
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -154,7 +155,7 @@ export default function OscSettingsModal() {
|
||||
setFormData(temp);
|
||||
setChanged(true);
|
||||
},
|
||||
[formData]
|
||||
[formData],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -283,12 +284,7 @@ export default function OscSettingsModal() {
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<SubmitContainer
|
||||
revert={revert}
|
||||
submitting={submitting}
|
||||
changed={changed}
|
||||
status={status}
|
||||
/>
|
||||
<SubmitContainer revert={revert} submitting={submitting} changed={changed} status={status} />
|
||||
</form>
|
||||
</ModalBody>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ interface RundownProps {
|
||||
|
||||
export default function Rundown(props: RundownProps) {
|
||||
const { entries } = props;
|
||||
const { data } = useRundownEditor();
|
||||
const data = useRundownEditor();
|
||||
const { cursor, moveCursorUp, moveCursorDown, moveCursorTo, isCursorLocked } = useContext(CursorContext);
|
||||
const startTimeIsLastEnd = useAtomValue(startTimeIsLastEndAtom);
|
||||
const defaultPublic = useAtomValue(defaultPublicAtom);
|
||||
|
||||
@@ -4,8 +4,8 @@ import { OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent } from 'ontim
|
||||
|
||||
import { defaultPublicAtom, editorEventId, startTimeIsLastEndAtom } from '../../common/atoms/LocalEventSettings';
|
||||
import { CursorContext } from '../../common/context/CursorContext';
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
import { useEventAction } from '../../common/hooks/useEventAction';
|
||||
import { useEmitLog } from '../../common/stores/logger';
|
||||
import { cloneEvent } from '../../common/utils/eventsManager';
|
||||
import { calculateDuration } from '../../common/utils/timesManager';
|
||||
|
||||
@@ -31,7 +31,7 @@ interface RundownEntryProps {
|
||||
|
||||
export default function RundownEntry(props: RundownEntryProps) {
|
||||
const { index, eventIndex, data, selected, hasCursor, next, delay, previousEnd, previousEventId, playback } = props;
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { emitError } = useEmitLog();
|
||||
const startTimeIsLastEnd = useAtomValue(startTimeIsLastEndAtom);
|
||||
const defaultPublic = useAtomValue(defaultPublicAtom);
|
||||
const { addEvent, updateEvent, deleteEvent } = useEventAction();
|
||||
|
||||
@@ -111,7 +111,7 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
[title, updateEvent, eventId],
|
||||
);
|
||||
|
||||
const eventIsPlaying = selected && playback === 'play';
|
||||
const eventIsPlaying = selected && playback === Playback.Play;
|
||||
const playBtnStyles = { _hover: {} };
|
||||
if (!skip && eventIsPlaying) {
|
||||
playBtnStyles._hover = { bg: '#c05621' };
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useEmitLog } from '@/common/stores/logger';
|
||||
|
||||
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
|
||||
import { LoggingContext } from '../../../../common/context/LoggingContext';
|
||||
import { millisToMinutes } from '../../../../common/utils/dateConfig';
|
||||
import { stringFromMillis } from '../../../../common/utils/time';
|
||||
import { validateEntry } from '../../../../common/utils/timesManager';
|
||||
|
||||
import style from '../EventBlock.module.scss';
|
||||
|
||||
export default function EventBlockTimers(props) {
|
||||
const { timeStart, timeEnd, duration, delay, actionHandler, previousEnd } = props;
|
||||
const { emitWarning } = useContext(LoggingContext);
|
||||
const { emitWarning } = useEmitLog();
|
||||
|
||||
const delayTime = `${delay >= 0 ? '+' : '-'} ${millisToMinutes(Math.abs(delay))}`;
|
||||
const newTime = stringFromMillis(timeStart + delay);
|
||||
const newTime = millisToString(timeStart + delay);
|
||||
|
||||
/**
|
||||
* @description Validates a time input against its pair
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useCallback, useContext, useRef } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { Button, Checkbox, Tooltip } from '@chakra-ui/react';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { SupportedEvent } from 'ontime-types';
|
||||
|
||||
import { defaultPublicAtom, startTimeIsLastEndAtom } from '../../../common/atoms/LocalEventSettings';
|
||||
import { LoggingContext } from '../../../common/context/LoggingContext';
|
||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||
import { useEmitLog } from '../../../common/stores/logger';
|
||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||
|
||||
import style from './QuickAddBlock.module.scss';
|
||||
@@ -21,7 +21,7 @@ interface QuickAddBlockProps {
|
||||
export default function QuickAddBlock(props: QuickAddBlockProps) {
|
||||
const { showKbd, eventId, previousEventId, disableAddDelay = true, disableAddBlock } = props;
|
||||
const { addEvent } = useEventAction();
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { emitError } = useEmitLog();
|
||||
const startTimeIsLastEnd = useAtomValue(startTimeIsLastEndAtom);
|
||||
const defaultPublic = useAtomValue(defaultPublicAtom);
|
||||
const doStartTime = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { FiCheck } from '@react-icons/all-files/fi/FiCheck';
|
||||
|
||||
import { stringFromMillis } from '../../common/utils/time.js';
|
||||
|
||||
import EditableCell from './tableElements/EditableCell';
|
||||
|
||||
import style from './Table.module.scss';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
/**
|
||||
* React - Table column object
|
||||
@@ -22,19 +21,19 @@ export const makeColumns = (sizes, userFields) => {
|
||||
{
|
||||
Header: 'Start',
|
||||
accessor: 'timeStart',
|
||||
Cell: ({ cell: { value, delayed } }) => stringFromMillis(delayed || value),
|
||||
Cell: ({ cell: { value, delayed } }) => millisToString(delayed || value),
|
||||
width: sizes?.timeStart || 90,
|
||||
},
|
||||
{
|
||||
Header: 'End',
|
||||
accessor: 'timeEnd',
|
||||
Cell: ({ cell: { value, delayed } }) => stringFromMillis(delayed || value),
|
||||
Cell: ({ cell: { value, delayed } }) => millisToString(delayed || value),
|
||||
width: sizes?.timeEnd || 90,
|
||||
},
|
||||
{
|
||||
Header: 'Duration',
|
||||
accessor: 'duration',
|
||||
Cell: ({ cell: { value } }) => stringFromMillis(value),
|
||||
Cell: ({ cell: { value } }) => millisToString(value),
|
||||
width: sizes?.duration || 90,
|
||||
},
|
||||
{ Header: 'Title', accessor: 'title', width: sizes?.title || 400 },
|
||||
|
||||
+10
-10
@@ -3,14 +3,18 @@ import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
||||
import { IoTimeOutline } from '@react-icons/all-files/io5/IoTimeOutline';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Playback } from 'ontime-types';
|
||||
|
||||
import { tooltipDelayFast } from '../../../ontimeConfig';
|
||||
|
||||
export default function PlaybackIcon(props) {
|
||||
interface PlaybackIconProps {
|
||||
state: Playback;
|
||||
}
|
||||
|
||||
export default function PlaybackIcon(props: PlaybackIconProps) {
|
||||
const { state } = props;
|
||||
|
||||
if (state === 'stop') {
|
||||
if (state === Playback.Stop) {
|
||||
return (
|
||||
<Tooltip openDelay={tooltipDelayFast} label='Timer Stopped' shouldWrapChildren>
|
||||
<IoStop />
|
||||
@@ -18,7 +22,7 @@ export default function PlaybackIcon(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (state === 'start') {
|
||||
if (state === Playback.Play) {
|
||||
return (
|
||||
<Tooltip openDelay={tooltipDelayFast} label='Timer Playing' shouldWrapChildren>
|
||||
<IoPlay />
|
||||
@@ -26,7 +30,7 @@ export default function PlaybackIcon(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (state === 'pause') {
|
||||
if (state === Playback.Pause) {
|
||||
return (
|
||||
<Tooltip openDelay={tooltipDelayFast} label='Timer Paused' shouldWrapChildren>
|
||||
<IoPause />
|
||||
@@ -34,7 +38,7 @@ export default function PlaybackIcon(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (state === 'roll') {
|
||||
if (state === Playback.Roll) {
|
||||
return (
|
||||
<Tooltip openDelay={tooltipDelayFast} label='Timer Rolling' shouldWrapChildren>
|
||||
<IoTimeOutline />
|
||||
@@ -44,7 +48,3 @@ export default function PlaybackIcon(props) {
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
PlaybackIcon.propTypes = {
|
||||
state: PropTypes.string,
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { stringify } from 'csv-stringify/browser/esm/sync';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
/**
|
||||
* @description parses a field for export
|
||||
@@ -6,14 +7,13 @@ import { stringify } from 'csv-stringify/browser/esm/sync';
|
||||
* @param {*} data
|
||||
* @return {string}
|
||||
*/
|
||||
import { stringFromMillis } from '../../common/utils/time';
|
||||
|
||||
export const parseField = (field, data) => {
|
||||
let val;
|
||||
switch (field) {
|
||||
case 'timeStart':
|
||||
case 'timeEnd':
|
||||
val = stringFromMillis(data);
|
||||
val = millisToString(data);
|
||||
break;
|
||||
case 'isPublic':
|
||||
val = data ? 'x' : '';
|
||||
|
||||
+23
-61
@@ -1,68 +1,33 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { Playback } from 'ontime-types';
|
||||
|
||||
import { useMessageControl } from '../../common/hooks/useSocket';
|
||||
import useSubscription from '../../common/hooks/useSubscription';
|
||||
import useEventData from '../../common/hooks-query/useEventData';
|
||||
import useRundown from '../../common/hooks-query/useRundown';
|
||||
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
||||
import socket from '../../common/utils/socket';
|
||||
import { useRuntimeStore } from '../../common/stores/runtime';
|
||||
|
||||
const withSocket = (Component) => {
|
||||
const withData = (Component: ReactNode) => {
|
||||
return (props) => {
|
||||
|
||||
// HTTP API data
|
||||
const { data: eventsData } = useRundown();
|
||||
const { data: genData } = useEventData();
|
||||
const { data: viewSettings } = useViewSettings();
|
||||
const { data: messageControl } = useMessageControl();
|
||||
|
||||
const [publicSelectedId, setPublicSelectedId] = useState(null);
|
||||
|
||||
const [timer] = useSubscription('timer', {
|
||||
clock: null,
|
||||
current: null,
|
||||
elapsed: null ,
|
||||
expectedFinish: null,
|
||||
addedTime: 0,
|
||||
startedAt: null,
|
||||
finishedAt: null,
|
||||
secondaryTimer: null,
|
||||
});
|
||||
const [titles] = useSubscription('titles', {
|
||||
titleNow: '',
|
||||
subtitleNow: '',
|
||||
presenterNow: '',
|
||||
titleNext: '',
|
||||
subtitleNext: '',
|
||||
presenterNext: '',
|
||||
});
|
||||
const [publicTitles] = useSubscription('titlesPublic', {
|
||||
titleNow: '',
|
||||
subtitleNow: '',
|
||||
presenterNow: '',
|
||||
titleNext: '',
|
||||
subtitleNext: '',
|
||||
presenterNext: '',
|
||||
});
|
||||
const [selectedId] = useSubscription('selected-id', null);
|
||||
const [nextId] = useSubscription('next-id', null);
|
||||
const [playback] = useSubscription('playback', null);
|
||||
|
||||
// Ask for update on load
|
||||
useEffect(() => {
|
||||
// todo: remove
|
||||
socket.on('publicselected-id', (data) => {
|
||||
setPublicSelectedId(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
const publicEvents = useMemo(() => {
|
||||
if (Array.isArray(eventsData)) {
|
||||
return eventsData.filter((d) => d.type === 'event' && d.title !== '' && d.isPublic);
|
||||
return eventsData.filter((e) => e.type === 'event' && e.title && e.isPublic);
|
||||
}
|
||||
return [];
|
||||
}, [eventsData]);
|
||||
|
||||
// websocket data
|
||||
const data = useRuntimeStore();
|
||||
const { timer, titles, titlesPublic, publicMessage, timerMessage, lowerMessage, playback, onAir } = data;
|
||||
const publicSelectedId = data.loaded.selectedPublicEventId;
|
||||
const selectedId = data.loaded.selectedEventId;
|
||||
const nextId = data.loaded.nextEventId;
|
||||
|
||||
/********************************************/
|
||||
/*** + titleManager ***/
|
||||
/*** WRAP INFORMATION RELATED TO TITLES ***/
|
||||
@@ -85,16 +50,14 @@ const withSocket = (Component) => {
|
||||
/********************************************/
|
||||
// is there a now field?
|
||||
let showPublicNow = true;
|
||||
if (!publicTitles.titleNow && !publicTitles.subtitleNow && !publicTitles.presenterNow)
|
||||
showPublicNow = false;
|
||||
if (!titlesPublic.titleNow && !titlesPublic.subtitleNow && !titlesPublic.presenterNow) showPublicNow = false;
|
||||
|
||||
// is there a next field?
|
||||
let showPublicNext = true;
|
||||
if (!publicTitles.titleNext && !publicTitles.subtitleNext && !publicTitles.presenterNext)
|
||||
showPublicNext = false;
|
||||
if (!titlesPublic.titleNext && !titlesPublic.subtitleNext && !titlesPublic.presenterNext) showPublicNext = false;
|
||||
|
||||
const publicTitleManager = {
|
||||
...publicTitles,
|
||||
...titlesPublic,
|
||||
showNow: showPublicNow,
|
||||
showNext: showPublicNext,
|
||||
};
|
||||
@@ -110,7 +73,7 @@ const withSocket = (Component) => {
|
||||
// get clock string
|
||||
const TimeManagerType = {
|
||||
...timer,
|
||||
finished: playback === 'play' && timer.current < 0 && timer.startedAt,
|
||||
finished: playback === Playback.Play && (timer.current ?? 0) < 0 && timer.startedAt,
|
||||
playback,
|
||||
};
|
||||
|
||||
@@ -119,13 +82,12 @@ const withSocket = (Component) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
Component.displayName = 'ComponentWithData';
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
pres={messageControl.messages.presenter}
|
||||
publ={messageControl.messages.public}
|
||||
lower={messageControl.messages.lower}
|
||||
pres={timerMessage}
|
||||
publ={publicMessage}
|
||||
lower={lowerMessage}
|
||||
title={titleManager}
|
||||
publicTitle={publicTitleManager}
|
||||
time={TimeManagerType}
|
||||
@@ -136,10 +98,10 @@ const withSocket = (Component) => {
|
||||
viewSettings={viewSettings}
|
||||
nextId={nextId}
|
||||
general={genData}
|
||||
onAir={messageControl.onAir}
|
||||
onAir={onAir}
|
||||
/>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default withSocket;
|
||||
export default withData;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useAtom } from 'jotai';
|
||||
import { OntimeEvent, OntimeRundownEntry, SupportedEvent } from 'ontime-types';
|
||||
import { OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent } from 'ontime-types';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
@@ -91,7 +91,7 @@ export default function Countdown(props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const standby = time.playback !== 'play' && selectedId === follow?.id;
|
||||
const standby = time.playback !== Playback.Play && selectedId === follow?.id;
|
||||
const isRunningFinished = time.finished && runningMessage === TimerMessage.running;
|
||||
const isSelected = runningMessage === TimerMessage.running;
|
||||
const delayedTimerStyles = delay > 0 ? 'aux-timers__value--delayed' : '';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OntimeEvent } from 'ontime-types';
|
||||
import { OntimeEvent, Playback } from 'ontime-types';
|
||||
|
||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
|
||||
@@ -25,7 +25,7 @@ export const fetchTimerData = (time: TimeManagerType, follow: OntimeEvent, selec
|
||||
|
||||
if (selectedId === follow.id) {
|
||||
// check that is not running
|
||||
message = time.playback === 'pause' ? TimerMessage.waiting : TimerMessage.running;
|
||||
message = time.playback === Playback.Pause ? TimerMessage.waiting : TimerMessage.running;
|
||||
timer = time.current ?? 0;
|
||||
|
||||
} else if (time.clock < follow.timeStart) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useAtom } from 'jotai';
|
||||
import { EventData, Message, TimerType, ViewSettings } from 'ontime-types';
|
||||
import { EventData, Message, Playback, TimerType, ViewSettings } from 'ontime-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||
@@ -127,7 +127,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
userOptions.hideEndMessage = Boolean(hideEndMessage);
|
||||
|
||||
const showOverlay = pres.text !== '' && pres.visible;
|
||||
const isPlaying = time.playback !== 'pause';
|
||||
const isPlaying = time.playback !== Playback.Pause;
|
||||
const isNegative =
|
||||
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
|
||||
const showEndMessage = time.current < 0 && general.endMessage && !hideEndMessage;
|
||||
|
||||
@@ -10,9 +10,10 @@ import useFitText from '../../../common/hooks/useFitText';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { formatDisplay } from '../../../common/utils/dateConfig';
|
||||
import { formatEventList, getEventsWithDelay, trimEventlist } from '../../../common/utils/eventsManager';
|
||||
import { formatTime, stringFromMillis } from '../../../common/utils/time';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
|
||||
import './StudioClock.scss';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
const formatOptions = {
|
||||
showSeconds: false,
|
||||
@@ -68,7 +69,7 @@ export default function StudioClock(props) {
|
||||
}, [backstageEvents, nextId, selectedId]);
|
||||
|
||||
const clock = formatTime(time.clock, formatOptions);
|
||||
const [, , secondsNow] = stringFromMillis(time.clock).split(':');
|
||||
const [, , secondsNow] = millisToString(time.clock).split(':');
|
||||
const isNegative = (time.current ?? 0) < 0;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useAtom } from 'jotai';
|
||||
import { TimerType } from 'ontime-types';
|
||||
import { Playback, TimerType } from 'ontime-types';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
@@ -61,12 +61,12 @@ export default function Timer(props) {
|
||||
|
||||
const clock = formatTime(time.clock, formatOptions);
|
||||
const showOverlay = pres.text !== '' && pres.visible;
|
||||
const isPlaying = time.playback !== 'pause';
|
||||
const isPlaying = time.playback !== Playback.Pause;
|
||||
const isNegative =
|
||||
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
|
||||
|
||||
const showEndMessage = time.current < 0 && general.endMessage;
|
||||
const showProgress = time.playback !== 'stop';
|
||||
const showProgress = time.playback !== Playback.Stop;
|
||||
const showFinished = time.finished && (time.timerType !== TimerType.Clock || showEndMessage);
|
||||
const showClock = time.timerType !== TimerType.Clock;
|
||||
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''}`;
|
||||
|
||||
Reference in New Issue
Block a user