mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-18 13:44:12 +00:00
Ux/54 help (#88)
* ux/54-help tooltips * ux/54-help refetch after mutation * ux/54-help broadcast event index * ux/54-help prevent multiple keypresses when key held * ux/54-help fat clock * ux/54-help progress bar user defined * ux/54-help onAir is button * ux/54-help OSC: add missing presenter message * ux/54-help OSC: enable / disable OSC * ux/54-help handle timezone in excel date import * ux/54-help tray left click to show app * ux/54-help create queriable endpoint * ux/54-help fix memoisation in lower thirds * ux/54-help revise icons * ux/54-help clarify text * ux/54-help forgiving text parsing * ux/54-help add smart keywords * ux/54-help version bump
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import {Editable, EditableInput, EditablePreview} from '@chakra-ui/editable';
|
||||
import {Switch} from "@chakra-ui/react";
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useSocket} from 'app/context/socketContext';
|
||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSocket } from 'app/context/socketContext';
|
||||
import VisibleIconBtn from 'common/components/buttons/VisibleIconBtn';
|
||||
import OnAirIconBtn from '../../common/components/buttons/OnAirIconBtn';
|
||||
import style from './MessageControl.module.scss';
|
||||
|
||||
const inputProps = {
|
||||
@@ -10,7 +10,7 @@ const inputProps = {
|
||||
};
|
||||
|
||||
const InputRow = (props) => {
|
||||
const {label, placeholder, text, visible} = props;
|
||||
const { label, placeholder, text, visible } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -23,8 +23,8 @@ const InputRow = (props) => {
|
||||
className={style.inline}
|
||||
color={text === '' ? '#666' : 'inherit'}
|
||||
>
|
||||
<EditablePreview className={style.padleft}/>
|
||||
<EditableInput className={style.padleft}/>
|
||||
<EditablePreview className={style.padleft} />
|
||||
<EditableInput className={style.padleft} />
|
||||
</Editable>
|
||||
<VisibleIconBtn
|
||||
active={visible || undefined}
|
||||
@@ -55,19 +55,19 @@ export default function MessageControl() {
|
||||
useEffect(() => {
|
||||
if (socket == null) return;
|
||||
|
||||
// Handle presenter messages
|
||||
socket.on('messages-presenter', (data) => {
|
||||
setPres({...data});
|
||||
// Handle timer messages
|
||||
socket.on('messages-timer', (data) => {
|
||||
setPres({ ...data });
|
||||
});
|
||||
|
||||
// Handle public messages
|
||||
socket.on('messages-public', (data) => {
|
||||
setPubl({...data});
|
||||
setPubl({ ...data });
|
||||
});
|
||||
|
||||
// Handle lower third messages
|
||||
socket.on('messages-lower', (data) => {
|
||||
setLower({...data});
|
||||
setLower({ ...data });
|
||||
});
|
||||
|
||||
// Handle lower third messages
|
||||
@@ -83,7 +83,7 @@ export default function MessageControl() {
|
||||
// Clear listeners
|
||||
return () => {
|
||||
socket.off('messages-public');
|
||||
socket.off('messages-presenter');
|
||||
socket.off('messages-timer');
|
||||
socket.off('messages-lower');
|
||||
socket.off('onAir');
|
||||
};
|
||||
@@ -92,10 +92,10 @@ export default function MessageControl() {
|
||||
const messageControl = async (action, payload) => {
|
||||
switch (action) {
|
||||
case 'pres-text':
|
||||
socket.emit('set-presenter-text', payload);
|
||||
socket.emit('set-timer-text', payload);
|
||||
break;
|
||||
case 'toggle-pres-visible':
|
||||
socket.emit('set-presenter-visible', !pres.visible);
|
||||
socket.emit('set-timer-visible', !pres.visible);
|
||||
break;
|
||||
case 'publ-text':
|
||||
socket.emit('set-public-text', payload);
|
||||
@@ -146,13 +146,13 @@ export default function MessageControl() {
|
||||
/>
|
||||
</div>
|
||||
<div className={style.onAirToggle}>
|
||||
<Switch
|
||||
colorScheme='green'
|
||||
<OnAirIconBtn
|
||||
className={style.btn}
|
||||
active={onAir}
|
||||
size='md'
|
||||
isChecked={onAir}
|
||||
onChange={() => messageControl('toggle-onAir')}>
|
||||
On Air?
|
||||
</Switch>
|
||||
actionHandler={() => messageControl('toggle-onAir')}
|
||||
/>
|
||||
<span className={style.onAirLabel}>On Air</span>
|
||||
<span className={style.oscLabel}>
|
||||
{`/ontime/offAir << OSC >> /ontime/onAir`}
|
||||
</span>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
@use '../../styles/main' as *;
|
||||
@use '../../styles/mixins' as *;
|
||||
|
||||
.messageContainer,
|
||||
.onAirToggle {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
@include main-container;
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
padding: 0.5em;
|
||||
|
||||
}
|
||||
|
||||
.messageContainer {
|
||||
@@ -17,17 +17,20 @@
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 0.9em;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.inline {
|
||||
border-radius: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.padleft {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
@@ -35,20 +38,25 @@
|
||||
|
||||
.onAirToggle {
|
||||
margin-top: 1em;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
line-height: 3em;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
'btn label'
|
||||
'btn osc';
|
||||
grid-template-columns: 2.5em 1fr;
|
||||
grid-template-rows: 1.2em 0.8em;
|
||||
|
||||
.btn {
|
||||
grid-area: btn;
|
||||
}
|
||||
|
||||
.onAirLabel {
|
||||
grid-area: label;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.oscLabel {
|
||||
color: #4bffabcc;
|
||||
font-size: 0.8em;
|
||||
float: right;
|
||||
padding-right: 1em;
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
@include osc-label;
|
||||
grid-area: osc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
import style from './PlaybackControl.module.scss';
|
||||
import Countdown from 'common/components/countdown/Countdown';
|
||||
import { stringFromMillis } from 'ontime-utils/time';
|
||||
import {Tooltip} from '@chakra-ui/react';
|
||||
import {Button} from '@chakra-ui/button';
|
||||
import {memo} from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { Button } from '@chakra-ui/button';
|
||||
import { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const areEqual = (prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.timer.running === nextProps.timer.running
|
||||
&& prevProps.timer.expectedFinish === nextProps.timer.expectedFinish
|
||||
&& prevProps.timer.startedAt === nextProps.timer.startedAt
|
||||
&& prevProps.playback === nextProps.playback
|
||||
&& prevProps.timer.secondary === nextProps.timer.secondary
|
||||
&& prevProps.selectedId === nextProps.selectedId
|
||||
prevProps.timer.running === nextProps.timer.running &&
|
||||
prevProps.timer.expectedFinish === nextProps.timer.expectedFinish &&
|
||||
prevProps.timer.startedAt === nextProps.timer.startedAt &&
|
||||
prevProps.playback === nextProps.playback &&
|
||||
prevProps.timer.secondary === nextProps.timer.secondary &&
|
||||
prevProps.selectedId === nextProps.selectedId
|
||||
);
|
||||
};
|
||||
|
||||
const PlaybackTimer = (props) => {
|
||||
const {timer, playback, handleIncrement, selectedId} = props;
|
||||
const { timer, playback, handleIncrement, selectedId } = props;
|
||||
const started = stringFromMillis(timer.startedAt, true);
|
||||
const finish = stringFromMillis(timer.expectedFinish, true);
|
||||
const isNegative = timer.running < 0;
|
||||
const isRolling = playback === 'roll';
|
||||
const isWaiting = timer.secondary > 0 && timer.running == null;
|
||||
const disableButtons = (selectedId == null || isRolling);
|
||||
const disableButtons = selectedId == null || isRolling;
|
||||
|
||||
const incrementProps = {
|
||||
size: 'sm',
|
||||
width: '2.9em',
|
||||
colorScheme: 'whiteAlpha',
|
||||
variant: 'outline',
|
||||
_focus: {boxShadow: 'none'},
|
||||
_focus: { boxShadow: 'none' },
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -39,12 +39,12 @@ const PlaybackTimer = (props) => {
|
||||
<div className={style.timeContainer}>
|
||||
<div className={style.indicators}>
|
||||
<Tooltip label='Roll mode active'>
|
||||
<div className={isRolling ? style.indRollActive : style.indRoll}/>
|
||||
<div className={isRolling ? style.indRollActive : style.indRoll} />
|
||||
</Tooltip>
|
||||
<div
|
||||
className={isNegative ? style.indNegativeActive : style.indNegative}
|
||||
/>
|
||||
<div className={style.indDelay}/>
|
||||
<div className={style.indDelay} />
|
||||
</div>
|
||||
<div className={style.timer}>
|
||||
<Countdown
|
||||
@@ -71,34 +71,58 @@ const PlaybackTimer = (props) => {
|
||||
</>
|
||||
)}
|
||||
<div className={style.btn}>
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(-1)}
|
||||
<Tooltip
|
||||
label={'Remove 1 minute'}
|
||||
delay={500}
|
||||
shouldWrapChildren={disableButtons}
|
||||
>
|
||||
-1
|
||||
</Button>
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(1)}
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(-1)}
|
||||
>
|
||||
-1
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Add 1 minute'}
|
||||
delay={500}
|
||||
shouldWrapChildren={disableButtons}
|
||||
>
|
||||
+1
|
||||
</Button>
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(-5)}
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(1)}
|
||||
>
|
||||
+1
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Remove 5 minutes'}
|
||||
delay={500}
|
||||
shouldWrapChildren={disableButtons}
|
||||
>
|
||||
-5
|
||||
</Button>
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(5)}
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(-5)}
|
||||
>
|
||||
-5
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label={'Add 5 minutes'}
|
||||
delay={500}
|
||||
shouldWrapChildren={disableButtons}
|
||||
>
|
||||
+5
|
||||
</Button>
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(5)}
|
||||
>
|
||||
+5
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
+9
-1
@@ -1,8 +1,9 @@
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { FiMoreVertical } from 'react-icons/fi';
|
||||
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
||||
import ActionButtons from './ActionButtons';
|
||||
import ActionButtons from '../list/ActionButtons';
|
||||
import style from './BlockBlock.module.css';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default function BlockBlock(props) {
|
||||
const { index, data, actionHandler } = props;
|
||||
@@ -27,3 +28,10 @@ export default function BlockBlock(props) {
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
BlockBlock.propTypes = {
|
||||
index: PropTypes.number.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
+12
-14
@@ -1,11 +1,12 @@
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { FiMoreVertical } from 'react-icons/fi';
|
||||
import { millisToMinutes } from 'common/utils/dateConfig';
|
||||
import ActionButtons from './ActionButtons';
|
||||
import ActionButtons from '../list/ActionButtons';
|
||||
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
||||
import ApplyIconBtn from 'common/components/buttons/ApplyIconBtn';
|
||||
import DelayInput from 'common/input/DelayInput';
|
||||
import style from './DelayBlock.module.css';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default function DelayBlock(props) {
|
||||
const { eventsHandler, data, index, actionHandler } = props;
|
||||
@@ -14,25 +15,15 @@ export default function DelayBlock(props) {
|
||||
eventsHandler('applyDelay', { id: data.id, duration: data.duration });
|
||||
};
|
||||
|
||||
let delayValue =
|
||||
data.duration != null ? millisToMinutes(data.duration) : undefined;
|
||||
|
||||
let delayValue = data.duration != null ? millisToMinutes(data.duration) : undefined;
|
||||
return (
|
||||
<Draggable key={data.id} draggableId={data.id} index={index}>
|
||||
{(provided) => (
|
||||
<div
|
||||
className={style.delay}
|
||||
{...provided.draggableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
<div className={style.delay} {...provided.draggableProps} ref={provided.innerRef}>
|
||||
<span className={style.drag} {...provided.dragHandleProps}>
|
||||
<FiMoreVertical />
|
||||
</span>
|
||||
<DelayInput
|
||||
className={style.input}
|
||||
value={delayValue}
|
||||
actionHandler={actionHandler}
|
||||
/>
|
||||
<DelayInput className={style.input} value={delayValue} actionHandler={actionHandler} />
|
||||
<div className={style.actionOverlay}>
|
||||
<ApplyIconBtn clickhandler={applyDelayHandler} />
|
||||
<DeleteIconBtn actionHandler={actionHandler} />
|
||||
@@ -43,3 +34,10 @@ export default function DelayBlock(props) {
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
|
||||
DelayBlock.propTypes = {
|
||||
eventsHandler: PropTypes.func.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
+54
-51
@@ -5,17 +5,17 @@ import { Draggable } from 'react-beautiful-dnd';
|
||||
import EventTimes from 'common/components/eventTimes/EventTimes';
|
||||
import EventTimesVertical from 'common/components/eventTimes/EventTimesVertical';
|
||||
import EditableText from 'common/input/EditableText';
|
||||
import ActionButtons from './ActionButtons';
|
||||
import ActionButtons from '../list/ActionButtons';
|
||||
import PublicIconBtn from 'common/components/buttons/PublicIconBtn';
|
||||
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
||||
import { millisToMinutes } from 'common/utils/dateConfig';
|
||||
import style from './EventBlock.module.css';
|
||||
import { SelectCollapse, HandleCollapse } from 'app/context/collapseAtom';
|
||||
import { HandleCollapse, SelectCollapse } from 'app/context/collapseAtom';
|
||||
import { useAtom } from 'jotai';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ExpandedBlock = (props) => {
|
||||
const { provided, data, eventIndex, next, delay, delayValue, actionHandler } =
|
||||
props;
|
||||
const { provided, data, eventIndex, next, delay, delayValue, previousEnd, actionHandler } = props;
|
||||
|
||||
const oscid = data.id.length > 4 ? '...' : data.id;
|
||||
|
||||
@@ -28,14 +28,12 @@ const ExpandedBlock = (props) => {
|
||||
return (
|
||||
<>
|
||||
<span className={style.drag} {...provided.dragHandleProps}>
|
||||
<FiMoreVertical />
|
||||
<FiMoreVertical />
|
||||
</span>
|
||||
|
||||
<div className={style.indicators}>
|
||||
<span className={next ? style.next : style.nextDisabled}>Next</span>
|
||||
{delayValue != null && (
|
||||
<span className={style.delayValue}>+ {delayValue}</span>
|
||||
)}
|
||||
{delayValue != null && <span className={style.delayValue}>+ {delayValue}</span>}
|
||||
</div>
|
||||
<div className={style.timeExpanded}>
|
||||
<EventTimesVertical
|
||||
@@ -44,6 +42,7 @@ const ExpandedBlock = (props) => {
|
||||
timeEnd={data.timeEnd}
|
||||
duration={duration}
|
||||
delay={delay}
|
||||
previousEnd={previousEnd}
|
||||
className={style.time}
|
||||
/>
|
||||
</div>
|
||||
@@ -53,25 +52,19 @@ const ExpandedBlock = (props) => {
|
||||
label='Title'
|
||||
defaultValue={data.title}
|
||||
placeholder='Add Title'
|
||||
submitHandler={(v) =>
|
||||
actionHandler('update', { field: 'title', value: v })
|
||||
}
|
||||
submitHandler={(v) => actionHandler('update', { field: 'title', value: v })}
|
||||
/>
|
||||
<EditableText
|
||||
label='Presenter'
|
||||
defaultValue={data.presenter}
|
||||
placeholder='Add Presenter name'
|
||||
submitHandler={(v) =>
|
||||
actionHandler('update', { field: 'presenter', value: v })
|
||||
}
|
||||
submitHandler={(v) => actionHandler('update', { field: 'presenter', value: v })}
|
||||
/>
|
||||
<EditableText
|
||||
label='Subtitle'
|
||||
defaultValue={data.subtitle}
|
||||
placeholder='Add Subtitle'
|
||||
submitHandler={(v) =>
|
||||
actionHandler('update', { field: 'subtitle', value: v })
|
||||
}
|
||||
submitHandler={(v) => actionHandler('update', { field: 'subtitle', value: v })}
|
||||
/>
|
||||
<EditableText
|
||||
label='Note'
|
||||
@@ -79,9 +72,7 @@ const ExpandedBlock = (props) => {
|
||||
placeholder='Add Note'
|
||||
style={{ color: '#d69e2e' }}
|
||||
maxchar={160}
|
||||
submitHandler={(v) =>
|
||||
actionHandler('update', { field: 'note', value: v })
|
||||
}
|
||||
submitHandler={(v) => actionHandler('update', { field: 'note', value: v })}
|
||||
/>
|
||||
<span className={style.oscLabel}>
|
||||
{`/ontime/goto ${eventIndex + 1} << OSC >> /ontime/gotoid ${oscid}`}
|
||||
@@ -89,38 +80,43 @@ const ExpandedBlock = (props) => {
|
||||
</div>
|
||||
<div className={style.actionOverlay}>
|
||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<ActionButtons
|
||||
showAdd
|
||||
showDelay
|
||||
showBlock
|
||||
actionHandler={actionHandler}
|
||||
/>
|
||||
<ActionButtons showAdd showDelay showBlock actionHandler={actionHandler} />
|
||||
<DeleteIconBtn actionHandler={actionHandler} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ExpandedBlock.propTypes = {
|
||||
provided: PropTypes.any.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
eventIndex: PropTypes.number.isRequired,
|
||||
next: PropTypes.bool.isRequired,
|
||||
delay: PropTypes.number,
|
||||
delayValue: PropTypes.number,
|
||||
previousEnd: PropTypes.number.isRequired,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const CollapsedBlock = (props) => {
|
||||
const { provided, data, next, delay, delayValue, actionHandler } = props;
|
||||
const { provided, data, next, delay, delayValue, previousEnd, actionHandler } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={style.drag} {...provided.dragHandleProps}>
|
||||
<FiMoreVertical />
|
||||
<FiMoreVertical />
|
||||
</span>
|
||||
|
||||
<div className={style.indicators}>
|
||||
<span className={next ? style.next : style.nextDisabled}>Next</span>
|
||||
{delayValue != null && (
|
||||
<span className={style.delayValue}>+ {delayValue}</span>
|
||||
)}
|
||||
{delayValue != null && <span className={style.delayValue}>+ {delayValue}</span>}
|
||||
</div>
|
||||
<EventTimes
|
||||
actionHandler={actionHandler}
|
||||
timeStart={data.timeStart}
|
||||
timeEnd={data.timeEnd}
|
||||
delay={delay}
|
||||
previousEnd={previousEnd}
|
||||
className={style.time}
|
||||
/>
|
||||
<div className={style.titleContainer}>
|
||||
@@ -128,33 +124,32 @@ const CollapsedBlock = (props) => {
|
||||
label='Title'
|
||||
defaultValue={data.title}
|
||||
placeholder='Add Title'
|
||||
submitHandler={(v) =>
|
||||
actionHandler('update', { field: 'title', value: v })
|
||||
}
|
||||
submitHandler={(v) => actionHandler('update', { field: 'title', value: v })}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.actionOverlay}>
|
||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<ActionButtons
|
||||
showAdd
|
||||
showDelay
|
||||
showBlock
|
||||
actionHandler={actionHandler}
|
||||
/>
|
||||
<ActionButtons showAdd showDelay showBlock actionHandler={actionHandler} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CollapsedBlock.propTypes = {
|
||||
provided: PropTypes.any.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
next: PropTypes.bool.isRequired,
|
||||
delay: PropTypes.any,
|
||||
delayValue: PropTypes.any,
|
||||
previousEnd: PropTypes.number.isRequired,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default function EventBlock(props) {
|
||||
const { data, selected, delay, index, eventIndex, actionHandler } = props;
|
||||
const [collapsed] = useAtom(
|
||||
useMemo(() => SelectCollapse(data.id), [data.id])
|
||||
);
|
||||
const { data, selected, delay, index, eventIndex, previousEnd, actionHandler } = props;
|
||||
const [collapsed] = useAtom(useMemo(() => SelectCollapse(data.id), [data.id]));
|
||||
const [, setCollapsed] = useAtom(HandleCollapse);
|
||||
|
||||
// TODO: should this go inside useEffect()
|
||||
// Would I then need to add this to state?
|
||||
const isSelected = selected ? style.active : '';
|
||||
const isCollapsed = collapsed ? style.collapsed : style.expanded;
|
||||
const classSelect = `${style.event} ${isCollapsed} ${isSelected}`;
|
||||
@@ -169,11 +164,7 @@ export default function EventBlock(props) {
|
||||
return (
|
||||
<Draggable key={data.id} draggableId={data.id} index={index}>
|
||||
{(provided) => (
|
||||
<div
|
||||
className={classSelect}
|
||||
{...provided.draggableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
<div className={classSelect} {...provided.draggableProps} ref={provided.innerRef}>
|
||||
<Icon
|
||||
className={collapsed ? style.moreCollapsed : style.moreExpanded}
|
||||
as={FiChevronUp}
|
||||
@@ -186,6 +177,7 @@ export default function EventBlock(props) {
|
||||
next={props.next}
|
||||
delay={delay}
|
||||
delayValue={delayValue}
|
||||
previousEnd={previousEnd}
|
||||
actionHandler={actionHandler}
|
||||
/>
|
||||
) : (
|
||||
@@ -196,6 +188,7 @@ export default function EventBlock(props) {
|
||||
next={props.next}
|
||||
delay={delay}
|
||||
delayValue={delayValue}
|
||||
previousEnd={previousEnd}
|
||||
actionHandler={actionHandler}
|
||||
/>
|
||||
)}
|
||||
@@ -204,3 +197,13 @@ export default function EventBlock(props) {
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
|
||||
EventBlock.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
selected: PropTypes.bool.isRequired,
|
||||
delay: PropTypes.number,
|
||||
index: PropTypes.number.isRequired,
|
||||
eventIndex: PropTypes.number.isRequired,
|
||||
previousEnd: PropTypes.number.isRequired,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu';
|
||||
import { IconButton } from '@chakra-ui/button';
|
||||
import { FiPlus, FiMinusCircle, FiClock } from 'react-icons/fi';
|
||||
import { FiClock, FiMinusCircle, FiPlus } from 'react-icons/fi';
|
||||
import { Tooltip } from '@chakra-ui/tooltip';
|
||||
|
||||
export default function ActionButtons(props) {
|
||||
const { showAdd, showDelay, showBlock, actionHandler } = props;
|
||||
@@ -12,16 +13,18 @@ export default function ActionButtons(props) {
|
||||
|
||||
return (
|
||||
<Menu isLazy lazyBehavior='unmount'>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label='Options'
|
||||
size='xs'
|
||||
icon={<FiPlus />}
|
||||
_expanded={{ bg: 'orange.300', color: 'white' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
backgroundColor={'orange.200'}
|
||||
color={'orange.500'}
|
||||
/>
|
||||
<Tooltip label='Add ...' delay={500}>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label='Options'
|
||||
size='xs'
|
||||
icon={<FiPlus />}
|
||||
_expanded={{ bg: 'orange.300', color: 'white' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
backgroundColor={'orange.200'}
|
||||
color={'orange.500'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<MenuList style={menuStyle}>
|
||||
<MenuItem
|
||||
icon={<FiPlus />}
|
||||
|
||||
@@ -20,8 +20,10 @@ export default function EventList(props) {
|
||||
// Handle keyboard shortcuts
|
||||
const handleKeyPress = useCallback(
|
||||
(e) => {
|
||||
// handle held key
|
||||
if (e.repeat) return;
|
||||
// Check if the alt key is pressed
|
||||
if (e.altKey) {
|
||||
if (e.altKey && (!e.ctrlKey || !e.shiftKey)) {
|
||||
// Arrow down
|
||||
if (e.keyCode === 40) {
|
||||
if (cursor == null) setCursor(0);
|
||||
@@ -145,6 +147,8 @@ export default function EventList(props) {
|
||||
|
||||
let cumulativeDelay = 0;
|
||||
let eventIndex = -1;
|
||||
let previousEnd = 0;
|
||||
let thisEnd = 0;
|
||||
|
||||
return (
|
||||
<div className={style.eventContainer}>
|
||||
@@ -167,6 +171,8 @@ export default function EventList(props) {
|
||||
cumulativeDelay = 0;
|
||||
} else if (e.type === 'event') {
|
||||
eventIndex++;
|
||||
previousEnd = thisEnd;
|
||||
thisEnd = e.timeEnd;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -184,6 +190,7 @@ export default function EventList(props) {
|
||||
next={nextId === e.id}
|
||||
eventsHandler={eventsHandler}
|
||||
delay={cumulativeDelay}
|
||||
previousEnd={previousEnd}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import DelayBlock from './DelayBlock';
|
||||
import BlockBlock from './BlockBlock';
|
||||
import EventBlock from './EventBlock';
|
||||
import DelayBlock from '../DelayBlock/DelayBlock';
|
||||
import BlockBlock from '../BlockBlock/BlockBlock';
|
||||
import EventBlock from '../EventBlock/EventBlock';
|
||||
import { memo, useContext } from 'react';
|
||||
import { LoggingContext } from '../../../app/context/LoggingContext';
|
||||
|
||||
@@ -10,7 +10,8 @@ const areEqual = (prevProps, nextProps) => {
|
||||
prevProps.selected === nextProps.selected &&
|
||||
prevProps.next === nextProps.next &&
|
||||
prevProps.index === nextProps.index &&
|
||||
prevProps.delay === nextProps.delay
|
||||
prevProps.delay === nextProps.delay &&
|
||||
prevProps.previousEnd === nextProps.previousEnd
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,6 +25,7 @@ const EventListItem = (props) => {
|
||||
next,
|
||||
eventsHandler,
|
||||
delay,
|
||||
previousEnd,
|
||||
...rest
|
||||
} = props;
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
@@ -79,6 +81,7 @@ const EventListItem = (props) => {
|
||||
next={next}
|
||||
actionHandler={actionHandler}
|
||||
delay={delay}
|
||||
previousEnd={previousEnd}
|
||||
/>
|
||||
);
|
||||
case 'block':
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu';
|
||||
import { IconButton } from '@chakra-ui/button';
|
||||
import { FiTrash2, FiPlus, FiClock, FiMinusCircle } from 'react-icons/fi';
|
||||
import { FiClock, FiMinusCircle, FiPlus, FiTrash2 } from 'react-icons/fi';
|
||||
import { Divider } from '@chakra-ui/layout';
|
||||
import { Tooltip } from '@chakra-ui/tooltip';
|
||||
|
||||
export default function MenuActionButtons(props) {
|
||||
const { actionHandler } = props;
|
||||
@@ -12,16 +13,18 @@ export default function MenuActionButtons(props) {
|
||||
|
||||
return (
|
||||
<Menu isLazy lazyBehavior='unmount'>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label='Create Menu'
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiPlus />}
|
||||
_expanded={{ bg: 'orange.300', color: 'white' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
backgroundColor={'orange.200'}
|
||||
color={'orange.500'}
|
||||
/>
|
||||
<Tooltip label='Add / Delete ...'>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label='Create Menu'
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiPlus />}
|
||||
_expanded={{ bg: 'orange.300', color: 'white' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
backgroundColor={'orange.200'}
|
||||
color={'orange.500'}
|
||||
/>
|
||||
</Tooltip>
|
||||
<MenuList style={menuStyle}>
|
||||
<MenuItem icon={<FiPlus />} onClick={() => actionHandler('event')}>
|
||||
Add Event first
|
||||
|
||||
@@ -5,7 +5,7 @@ import { FiDownload } from 'react-icons/fi';
|
||||
export default function DownloadIconBtn(props) {
|
||||
const { clickhandler, ...rest } = props;
|
||||
return (
|
||||
<Tooltip label='Download File'>
|
||||
<Tooltip label='Export event list'>
|
||||
<IconButton
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiDownload />}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { IconButton } from '@chakra-ui/button';
|
||||
import { Tooltip } from '@chakra-ui/tooltip';
|
||||
import { FiHome } from 'react-icons/fi';
|
||||
|
||||
export default function InfoIconBtn(props) {
|
||||
const { clickhandler, ...rest } = props;
|
||||
return (
|
||||
<Tooltip label='Event Main'>
|
||||
<IconButton
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiHome />}
|
||||
colorScheme='white'
|
||||
onClick={clickhandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
{...rest}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { FiUpload } from 'react-icons/fi';
|
||||
export default function UploadIconBtn(props) {
|
||||
const { clickhandler, ...rest } = props;
|
||||
return (
|
||||
<Tooltip label='Upload File'>
|
||||
<Tooltip label='Import event list'>
|
||||
<IconButton
|
||||
size={props.size || 'xs'}
|
||||
icon={<FiUpload />}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, IconButton } from '@chakra-ui/button';
|
||||
import { FiInfo, FiMinus, FiSun } from 'react-icons/fi';
|
||||
import { IoInformationCircleOutline, IoRemove, IoSunny } from 'react-icons/io5';
|
||||
import { ModalBody } from '@chakra-ui/modal';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { getAliases, postAliases } from '../../app/api/ontimeApi';
|
||||
@@ -176,7 +176,7 @@ export default function AliasesModal() {
|
||||
<div className={style.hSeparator}>Custom Aliases</div>
|
||||
<div className={style.blockNotes}>
|
||||
<span className={style.inlineFlex}>
|
||||
<FiInfo color='#2b6cb0' fontSize={'2em'} />
|
||||
<IoInformationCircleOutline color='#2b6cb0' fontSize={'2em'} />
|
||||
URL aliases are useful in two main scenarios
|
||||
</span>
|
||||
<span className={style.labelNote}>Complicated URLs</span>
|
||||
@@ -267,7 +267,7 @@ export default function AliasesModal() {
|
||||
<Tooltip label='Enable alias' openDelay={500}>
|
||||
<IconButton
|
||||
size='xs'
|
||||
icon={<FiSun />}
|
||||
icon={<IoSunny />}
|
||||
colorScheme='blue'
|
||||
variant={alias.enabled ? null : 'outline'}
|
||||
onClick={() => setEnabled(alias.id, !alias.enabled)}
|
||||
@@ -276,7 +276,7 @@ export default function AliasesModal() {
|
||||
<Tooltip label='Delete alias' openDelay={500}>
|
||||
<IconButton
|
||||
size='xs'
|
||||
icon={<FiMinus />}
|
||||
icon={<IoRemove />}
|
||||
colorScheme='red'
|
||||
onClick={() => deleteAlias(alias.id)}
|
||||
/>
|
||||
|
||||
@@ -99,7 +99,7 @@ export default function AppSettingsModal() {
|
||||
<p className={style.notes}>
|
||||
Options related to the application
|
||||
<br />
|
||||
🔥 Changes take effect after app restart 🔥
|
||||
🔥 Changes take effect on save 🔥
|
||||
</p>
|
||||
<form onSubmit={submitHandler}>
|
||||
<div className={style.modalFields}>
|
||||
|
||||
@@ -39,6 +39,7 @@ export default function SettingsModal() {
|
||||
setSubmitting(true);
|
||||
|
||||
await postEvent(formData);
|
||||
await refetch();
|
||||
|
||||
setChanged(false);
|
||||
setSubmitting(false);
|
||||
|
||||
@@ -8,7 +8,70 @@ import style from './Modals.module.scss';
|
||||
import { LoggingContext } from '../../app/context/LoggingContext';
|
||||
import SubmitContainer from './SubmitContainer';
|
||||
import { inputProps, portInputProps } from './modalHelper';
|
||||
import { IoInformationCircleOutline } from 'react-icons/io5';
|
||||
import EnableBtn from '../../common/components/buttons/EnableBtn';
|
||||
|
||||
// currently defined endpoints
|
||||
// temporary
|
||||
const oscCycleEndpoints = [
|
||||
{
|
||||
title: 'On Event Start',
|
||||
message: '/ontime/eventNumber',
|
||||
value: '8 | int',
|
||||
},
|
||||
{
|
||||
title: 'On Update',
|
||||
message: '/ontime/time',
|
||||
value: '10:12:12 | string',
|
||||
},
|
||||
{
|
||||
title: 'On Update',
|
||||
message: '/ontime/overtime',
|
||||
value: '0-1 | int',
|
||||
},
|
||||
{
|
||||
title: 'On Update',
|
||||
message: '/ontime/title',
|
||||
value: 'Title of running event | string',
|
||||
},
|
||||
{
|
||||
title: 'On Finish',
|
||||
message: '/ontime/finished',
|
||||
value: '-',
|
||||
},
|
||||
];
|
||||
const oscTriggerEndpoints = [
|
||||
{
|
||||
title: 'On Start',
|
||||
message: '/ontime/play',
|
||||
value: '-',
|
||||
},
|
||||
{
|
||||
title: 'On Pause',
|
||||
message: '/ontime/pause',
|
||||
value: '-',
|
||||
},
|
||||
{
|
||||
title: 'On Previous',
|
||||
message: '/ontime/prev',
|
||||
value: '-',
|
||||
},
|
||||
{
|
||||
title: 'On Next',
|
||||
message: '/ontime/next',
|
||||
value: '-',
|
||||
},
|
||||
{
|
||||
title: 'On Reload',
|
||||
message: '/ontime/reload',
|
||||
value: '-',
|
||||
},
|
||||
{
|
||||
title: 'On Stop',
|
||||
message: '/ontime/stop',
|
||||
value: '-',
|
||||
},
|
||||
];
|
||||
|
||||
export default function OscSettingsModal() {
|
||||
const { data, status, refetch } = useFetch(OSC_SETTINGS, getOSC);
|
||||
@@ -57,6 +120,7 @@ export default function OscSettingsModal() {
|
||||
} else {
|
||||
// Post here
|
||||
await postOSC(formData);
|
||||
await refetch();
|
||||
setChanged(false);
|
||||
}
|
||||
setSubmitting(false);
|
||||
@@ -91,25 +155,44 @@ export default function OscSettingsModal() {
|
||||
</p>
|
||||
<form onSubmit={submitHandler}>
|
||||
<div className={style.modalFields}>
|
||||
<div className={style.hSeparator}>OSC Input (control)</div>
|
||||
<div className={style.spacedEntry}>
|
||||
<FormLabel htmlFor='port'>
|
||||
OSC In Port
|
||||
<span className={style.labelNote}>
|
||||
<br />
|
||||
Open port for 3rd party control over OSC - Default 8888
|
||||
</span>
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...portInputProps}
|
||||
name='port'
|
||||
placeholder='8888'
|
||||
value={formData.port}
|
||||
onChange={(event) =>
|
||||
handleChange('port', parseInt(event.target.value))
|
||||
}
|
||||
style={{ width: '6em', textAlign: 'center' }}
|
||||
/>
|
||||
<div className={style.hSeparator}>
|
||||
OSC Input (Control ontime over OSC)
|
||||
</div>
|
||||
<div className={style.modalInline}>
|
||||
<FormControl id='oscInEnabled'>
|
||||
<FormLabel htmlFor='oscInEnabled'>
|
||||
OSC Enable
|
||||
<span className={style.labelNote}>
|
||||
<br />
|
||||
Enable / Disable control
|
||||
</span>
|
||||
</FormLabel>
|
||||
<EnableBtn
|
||||
active={formData.enabled}
|
||||
text={formData.enabled ? 'OSC IN Enabled' : 'OSC IN Disabled'}
|
||||
actionHandler={() => handleChange('enabled', !formData.enabled)}
|
||||
onClick={() => console.log('yay')}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl id='portIn'>
|
||||
<FormLabel htmlFor='portIn'>
|
||||
OSC In Port
|
||||
<span className={style.labelNote}>
|
||||
<br />
|
||||
Port - Default 8888
|
||||
</span>
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...portInputProps}
|
||||
name='port'
|
||||
placeholder='8888'
|
||||
value={formData.port}
|
||||
onChange={(event) =>
|
||||
handleChange('port', parseInt(event.target.value))
|
||||
}
|
||||
style={{ width: '6em', textAlign: 'center' }}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={style.hSeparator}>OSC Output (feedback)</div>
|
||||
<div className={style.modalInline}>
|
||||
@@ -155,6 +238,48 @@ export default function OscSettingsModal() {
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className={style.blockNotes}>
|
||||
<span className={style.inlineFlex}>
|
||||
<IoInformationCircleOutline color='#2b6cb0' fontSize={'2em'} />
|
||||
OSC Feedback messages
|
||||
</span>
|
||||
<span>
|
||||
In future OSC feedback will be user defined. <br />
|
||||
For now this is the list of OSC messages sent from ontime
|
||||
</span>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={style.labelNote} style={{ width: '30%' }}>
|
||||
Cycle
|
||||
</td>
|
||||
<td className={style.labelNote}>Message</td>
|
||||
<td className={style.labelNote}>Value (example | type)</td>
|
||||
</tr>
|
||||
{oscCycleEndpoints.map((e) => (
|
||||
<tr key={e.message}>
|
||||
<td>{e.title}</td>
|
||||
<td>{e.message}</td>
|
||||
<td>{e.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr>
|
||||
<td className={style.labelNote} style={{ width: '30%' }}>
|
||||
Trigger
|
||||
</td>
|
||||
<td className={style.labelNote}>Message</td>
|
||||
<td className={style.labelNote}>Value (example | type)</td>
|
||||
</tr>
|
||||
{oscTriggerEndpoints.map((e) => (
|
||||
<tr key={e.message}>
|
||||
<td>{e.title}</td>
|
||||
<td>{e.message}</td>
|
||||
<td>{e.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<SubmitContainer
|
||||
revert={revert}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import styles from './PreviewContainer.module.css';
|
||||
import IFrameLoader from './iframes/IFrameLoader';
|
||||
|
||||
// get origin from URL
|
||||
const serverURL = `${window.location.origin}`;
|
||||
|
||||
export default function PreviewContainer() {
|
||||
return (
|
||||
<div className={styles.previewContainer}>
|
||||
<div className={styles.previewItem}>
|
||||
<IFrameLoader title='Default Presenter' src={`${serverURL}/speaker`} />
|
||||
<a
|
||||
href={`${serverURL}/speaker`}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
className={styles.label}
|
||||
>
|
||||
Default Presenter
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className={styles.previewItem}>
|
||||
<IFrameLoader title='Audience' src={`${serverURL}/public`} />
|
||||
<a
|
||||
href={`${serverURL}/public`}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
className={styles.label}
|
||||
>
|
||||
Audience
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className={styles.previewItem}>
|
||||
<IFrameLoader title='Stage Manager' src={`${serverURL}/sm`} />
|
||||
<a
|
||||
href={`${serverURL}/sm`}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
className={styles.label}
|
||||
>
|
||||
Stage Manager
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className={styles.previewItem}>
|
||||
<IFrameLoader
|
||||
title='Lower third'
|
||||
src={`${serverURL}/lower?key=242424`}
|
||||
/>
|
||||
<a
|
||||
href={`${serverURL}/lower`}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
className={styles.label}
|
||||
>
|
||||
Lower third
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
.previewContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.previewItem {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 0.1em 4em;
|
||||
}
|
||||
|
||||
a::after {
|
||||
content: ' \2197';
|
||||
color: #ff7597;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #ff7597;
|
||||
}
|
||||
@@ -4,16 +4,12 @@ import { fetchEvent } from 'app/api/eventApi';
|
||||
import { useSocket } from 'app/context/socketContext';
|
||||
import { stringFromMillis } from 'ontime-utils/time';
|
||||
import { useFetch } from 'app/hooks/useFetch';
|
||||
import { EVENTS_TABLE, EVENT_TABLE } from 'app/api/apiConstants';
|
||||
import { EVENT_TABLE, EVENTS_TABLE } from 'app/api/apiConstants';
|
||||
|
||||
const withSocket = (Component) => {
|
||||
const WrappedComponent = (props) => {
|
||||
const {
|
||||
data: eventsData,
|
||||
} = useFetch(EVENTS_TABLE, fetchAllEvents);
|
||||
const {
|
||||
data: genData,
|
||||
} = useFetch(EVENT_TABLE, fetchEvent);
|
||||
return (props) => {
|
||||
const { data: eventsData } = useFetch(EVENTS_TABLE, fetchAllEvents);
|
||||
const { data: genData } = useFetch(EVENT_TABLE, fetchEvent);
|
||||
|
||||
const [publicEvents, setPublicEvents] = useState([]);
|
||||
const [backstageEvents, setBackstageEvents] = useState([]);
|
||||
@@ -70,8 +66,8 @@ const withSocket = (Component) => {
|
||||
useEffect(() => {
|
||||
if (socket == null) return;
|
||||
|
||||
// Handle presenter messages
|
||||
socket.on('messages-presenter', (data) => {
|
||||
// Handle timer messages
|
||||
socket.on('messages-timer', (data) => {
|
||||
setPres({ ...data });
|
||||
});
|
||||
|
||||
@@ -121,14 +117,14 @@ const withSocket = (Component) => {
|
||||
socket.emit('get-messages');
|
||||
|
||||
// Ask for up to data
|
||||
socket.emit('get-presenter');
|
||||
socket.emit('get-timer');
|
||||
|
||||
// ask for timer
|
||||
socket.emit('get-timer');
|
||||
|
||||
// ask for playstate
|
||||
socket.emit('get-playstate');
|
||||
socket.emit('get-onAir')
|
||||
socket.emit('get-onAir');
|
||||
|
||||
// Ask for up titles
|
||||
socket.emit('get-titles');
|
||||
@@ -141,7 +137,7 @@ const withSocket = (Component) => {
|
||||
// Clear listeners
|
||||
return () => {
|
||||
socket.off('messages-public');
|
||||
socket.off('messages-presenter');
|
||||
socket.off('messages-timer');
|
||||
socket.off('messages-lower');
|
||||
socket.off('timer');
|
||||
socket.off('playstate');
|
||||
@@ -255,8 +251,6 @@ const withSocket = (Component) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return WrappedComponent;
|
||||
};
|
||||
|
||||
export default withSocket;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import style from './PresenterView.module.css';
|
||||
|
||||
export default function PresenterSimple() {
|
||||
return (
|
||||
<div className={style.container__graySimple}>
|
||||
{/* <div className={style.messageOverlayActive}>
|
||||
<div className={style.message}>Remember to smile</div>
|
||||
</div> */}
|
||||
|
||||
<div className={style.timerContainer}>
|
||||
<div className={style.countdownBig}>01:03</div>
|
||||
</div>
|
||||
|
||||
<div className={style.progress}>
|
||||
<div className={style.progressed}></div>
|
||||
</div>
|
||||
|
||||
{/* <div className={style.mainContainer}>
|
||||
<div className={style.finished}>TIME UP</div>
|
||||
</div> */}
|
||||
|
||||
<div className={style.clockContainer}>
|
||||
<div className={style.label}>Time Now</div>
|
||||
<div className={style.clock}>11:00:23</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ const isEqual = require('react-fast-compare');
|
||||
const areEqual = (prevProps, nextProps) => {
|
||||
return (
|
||||
isEqual(prevProps.title, nextProps.title) &&
|
||||
isEqual(prevProps.lower && nextProps.lower)
|
||||
isEqual(prevProps.lower, nextProps.lower)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -56,6 +56,7 @@ const Lower = (props) => {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line
|
||||
}, [title.titleNow, title.subtitleNow, title.presenterNow]);
|
||||
|
||||
// TODO: sanitize data
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useEffect } from 'react';
|
||||
import { formatDisplay } from '../../../common/utils/dateConfig';
|
||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
||||
import style from './MinimalTimer.module.scss';
|
||||
|
||||
export default function MinimalTimer(props) {
|
||||
const { pres, time } = props;
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Minimal Timer';
|
||||
}, []);
|
||||
|
||||
const showOverlay = pres.text !== '' && pres.visible;
|
||||
const isPlaying = time.playstate !== 'pause';
|
||||
const timer = formatDisplay(time.running, true);
|
||||
const clean = timer.replaceAll(':', '');
|
||||
|
||||
return (
|
||||
<div className={time.finished ? style.containerFinished : style.container}>
|
||||
<div
|
||||
className={
|
||||
showOverlay ? style.messageOverlayActive : style.messageOverlay
|
||||
}
|
||||
>
|
||||
<div className={style.message}>{pres.text}</div>
|
||||
</div>
|
||||
<NavLogo />
|
||||
<div
|
||||
style={{ fontSize: `${89 / (clean.length - 1)}vw` }}
|
||||
className={isPlaying ? style.timer : style.timerPaused}
|
||||
>
|
||||
{time.running < 0 ? `-${timer}` : timer}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
@use '../../../styles/main' as *;
|
||||
|
||||
.container,
|
||||
.containerFinished {
|
||||
background: $bg-black;
|
||||
height: 100vh;
|
||||
color: $title-white;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
gap: 1vw;
|
||||
border: 1vw solid $bg-black;
|
||||
}
|
||||
|
||||
.containerFinished {
|
||||
border: 1vw solid $ontime-pink-variant;
|
||||
color: $ontime-pink-variant;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.timer,
|
||||
.timerPaused {
|
||||
font-family: "Arial Black", sans-serif;
|
||||
font-size: 20vw;
|
||||
color: inherit;
|
||||
opacity: 1;
|
||||
transition: 0.5s;
|
||||
transition-property: opacity;
|
||||
}
|
||||
|
||||
.timerPaused {
|
||||
opacity: 0.6;
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
/* =================== OVERLAY ===================*/
|
||||
|
||||
.messageOverlay,
|
||||
.messageOverlayActive {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
.messageOverlayActive {
|
||||
opacity: 1;
|
||||
transition: 0.5s;
|
||||
transition-property: opacity;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.message {
|
||||
width: inherit;
|
||||
padding: 2vw;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
color: white;
|
||||
transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
font-size: 15vw;
|
||||
line-height: 30vh;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
+25
-5
@@ -1,19 +1,35 @@
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import Countdown from 'common/components/countdown/Countdown';
|
||||
import MyProgressBar from 'common/components/myProgressBar/MyProgressBar';
|
||||
import NavLogo from 'common/components/nav/NavLogo';
|
||||
import TitleCard from 'common/components/views/TitleCard';
|
||||
import style from './PresenterView.module.css';
|
||||
import style from './Timer.module.scss';
|
||||
|
||||
export default function PresenterView(props) {
|
||||
export default function Timer(props) {
|
||||
const { general, pres, title, time } = props;
|
||||
const [elapsed, setElapsed] = useState(true);
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Presenter Screen';
|
||||
document.title = 'ontime - Timer';
|
||||
}, []);
|
||||
|
||||
// eg. http://localhost:3000/timer?progress=up
|
||||
// Check for user options
|
||||
useEffect(() => {
|
||||
// progress: selector
|
||||
// Should be 'up' or 'down'
|
||||
const progress = searchParams.get('progress');
|
||||
if (progress === 'up') {
|
||||
setElapsed(true);
|
||||
} else if (progress === 'down') {
|
||||
setElapsed(false);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const showOverlay = pres.text !== '' && pres.visible;
|
||||
const isPlaying = time.playstate !== 'pause';
|
||||
const normalisedTime = Math.max(time.running, 0);
|
||||
@@ -79,7 +95,11 @@ export default function PresenterView(props) {
|
||||
isPlaying ? style.progressContainer : style.progressContainerPaused
|
||||
}
|
||||
>
|
||||
<MyProgressBar now={normalisedTime} complete={time.durationSeconds} />
|
||||
<MyProgressBar
|
||||
now={normalisedTime}
|
||||
complete={time.durationSeconds}
|
||||
showElapsed={elapsed}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+9
-20
@@ -1,3 +1,5 @@
|
||||
@use '../../../styles/main' as *;
|
||||
|
||||
.container__gray,
|
||||
.container__grayFinished {
|
||||
margin: 0;
|
||||
@@ -5,9 +7,9 @@
|
||||
overflow: hidden;
|
||||
width: 100%; /* restrict the page width to viewport */
|
||||
|
||||
background: radial-gradient(circle, #202020 0%, #121212 80%);
|
||||
background: radial-gradient(circle, $bg-black-gradient 0%, $bg-black 80%);
|
||||
height: 100vh;
|
||||
color: #fffd;
|
||||
color: $title-white;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 5vw 1fr 1fr;
|
||||
grid-template-rows: auto 1fr auto minmax(25vh, auto);
|
||||
@@ -20,25 +22,11 @@
|
||||
padding: 1vw;
|
||||
}
|
||||
|
||||
.container__graySimple,
|
||||
.container__grayFinishedSimple {
|
||||
background: radial-gradient(circle, #202020 0%, #121212 80%);
|
||||
height: 100vh;
|
||||
color: #fffd;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 5vw 1fr 1fr;
|
||||
grid-template-rows: auto 1fr 30vh;
|
||||
grid-template-areas:
|
||||
' clck .... .... .... ....'
|
||||
' timr timr timr timr timr'
|
||||
' prog prog prog prog prog';
|
||||
gap: 1vw;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1.3vw;
|
||||
color: #ff7597;
|
||||
color: $ontime-pink;
|
||||
}
|
||||
|
||||
/* =================== TITLES ===================*/
|
||||
|
||||
.nowContainer,
|
||||
@@ -52,6 +40,7 @@
|
||||
.nowContainer {
|
||||
grid-area: now;
|
||||
}
|
||||
|
||||
.nextContainer {
|
||||
grid-area: next;
|
||||
}
|
||||
@@ -71,7 +60,7 @@
|
||||
font-size: 12vw;
|
||||
line-height: 18vw;
|
||||
font-weight: 600;
|
||||
color: #ff6969;
|
||||
color: $ontime-pink-variant;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -95,7 +84,7 @@
|
||||
}
|
||||
|
||||
.container__grayFinished {
|
||||
border: 1vw solid #ff6969;
|
||||
border: 1vw solid $ontime-pink-variant;
|
||||
}
|
||||
|
||||
/* =================== OVERLAY ===================*/
|
||||
Reference in New Issue
Block a user