mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-11 02:13:48 +00:00
refractor + style tweaks
- bugfix on lower - delay between 0-60 minutes - action buttons in dropdown - added button for toggling element visibility - several css and code cleanups - renamed settingsAPI
This commit is contained in:
+9
-9
@@ -1,4 +1,4 @@
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { Route } from 'react-router-dom';
|
||||
import './App.css';
|
||||
import Editor from './features/editors/Editor';
|
||||
import DefaultPresenter from './features/viewers/DefaultPresenter';
|
||||
@@ -22,14 +22,14 @@ function App() {
|
||||
return (
|
||||
<SocketProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<div className='App'>
|
||||
<Route exact path='/' component={SDefault} />
|
||||
<Route exact path='/sm' component={SStageManager} />
|
||||
<Route exact path='/speaker' component={SSpeaker} />
|
||||
<Route exact path='/speakersimple' component={SSpeakerSimple} />
|
||||
<Route exact path='/editor' component={Editor} />
|
||||
<Route path='/lower' component={SLowerThird} />
|
||||
</div>
|
||||
<div className='App'>
|
||||
<Route exact path='/' component={PresenterView} />
|
||||
<Route exact path='/sm' component={SStageManager} />
|
||||
<Route exact path='/speaker' component={SSpeaker} />
|
||||
<Route exact path='/speakersimple' component={SSpeakerSimple} />
|
||||
<Route exact path='/editor' component={Editor} />
|
||||
<Route path='/lower' component={SLowerThird} />
|
||||
</div>
|
||||
</QueryClientProvider>
|
||||
</SocketProvider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import axios from 'axios';
|
||||
import { NODE_PORT } from './apiConstants';
|
||||
|
||||
// get origin from URL
|
||||
const serverURL = window.location.origin.replace(
|
||||
window.location.port,
|
||||
`${NODE_PORT}/`
|
||||
);
|
||||
|
||||
export const eventNamespace = 'event';
|
||||
export const eventURL = serverURL + eventNamespace;
|
||||
|
||||
export const fetchEvent = async () => {
|
||||
const res = await axios.get(eventURL);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const postEvent = async (data) => {
|
||||
const res = await axios.post(eventURL, data);
|
||||
return res;
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import { NODE_PORT } from '../api/apiConstants';
|
||||
|
||||
// get origin from URL
|
||||
const serverURL = window.location.origin.replace(
|
||||
window.location.port,
|
||||
`${NODE_PORT}/`
|
||||
);
|
||||
|
||||
export const settingsNamespace = 'settings';
|
||||
export const settingsURL = serverURL + settingsNamespace;
|
||||
|
||||
export const fetchSettings = async () => {
|
||||
const res = await axios.get(settingsURL);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const postSettings = async (data) => {
|
||||
console.log('debug sending', data)
|
||||
console.log('debug await', await axios.post(settingsURL, data))
|
||||
const res = await axios.post(settingsURL, data);
|
||||
return res;
|
||||
};
|
||||
@@ -8,6 +8,7 @@ export default function AddIconBtn(props) {
|
||||
icon={<FiPlus />}
|
||||
colorScheme='blue'
|
||||
onClick={props.clickHandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export default function BlockIconBtn(props) {
|
||||
icon={<FiMinusCircle />}
|
||||
colorScheme='purple'
|
||||
onClick={props.clickHandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default function CollapseIconBtn(props) {
|
||||
colorScheme='orange'
|
||||
variant={props.active ? 'solid' : 'outline'}
|
||||
onClick={props.clickHandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export default function DelayIconBtn(props) {
|
||||
icon={<FiClock />}
|
||||
colorScheme='yellow'
|
||||
onClick={props.clickHandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export default function DeleteIconBtn(props) {
|
||||
icon={<FiMinus />}
|
||||
colorScheme='red'
|
||||
onClick={props.clickHandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ export default function NextIconBtn(props) {
|
||||
return (
|
||||
<IconButton
|
||||
icon={<FiSkipForward />}
|
||||
colorScheme= 'whiteAlpha'
|
||||
colorScheme='whiteAlpha'
|
||||
backgroundColor='#ffffff05'
|
||||
variant= 'outline'
|
||||
variant='outline'
|
||||
onClick={props.clickHandler}
|
||||
width={90}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ export default function PauseIconBtn(props) {
|
||||
colorScheme='orange'
|
||||
variant={props.active ? 'solid' : 'outline'}
|
||||
onClick={props.clickHandler}
|
||||
width={90}
|
||||
width={120}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ export default function PrevIconBtn(props) {
|
||||
return (
|
||||
<IconButton
|
||||
icon={<FiSkipBack />}
|
||||
colorScheme= 'whiteAlpha'
|
||||
colorScheme='whiteAlpha'
|
||||
backgroundColor='#ffffff05'
|
||||
variant= 'outline'
|
||||
variant='outline'
|
||||
onClick={props.clickHandler}
|
||||
width={90}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IconButton } from '@chakra-ui/button';
|
||||
import { FiRefreshCcw } from 'react-icons/fi';
|
||||
|
||||
export default function ReloadIconButton(props) {
|
||||
return (
|
||||
<IconButton
|
||||
icon={<FiRefreshCcw />}
|
||||
colorScheme='whiteAlpha'
|
||||
backgroundColor='#ffffff05'
|
||||
variant='outline'
|
||||
onClick={props.clickHandler}
|
||||
width={90}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,9 @@ export default function RollIconBtn(props) {
|
||||
colorScheme='blue'
|
||||
variant={props.active ? 'solid' : 'outline'}
|
||||
onClick={props.clickHandler}
|
||||
width={90}
|
||||
width={120}
|
||||
disabled
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default function SettingsIconBtn(props) {
|
||||
isRound
|
||||
variant='outline'
|
||||
onClick={props.clickHandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ export default function StartIconBtn(props) {
|
||||
colorScheme='green'
|
||||
variant={props.active ? 'solid' : 'outline'}
|
||||
onClick={props.clickHandler}
|
||||
width={90}
|
||||
width={120}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IconButton } from '@chakra-ui/button';
|
||||
import { FiCornerLeftUp } from 'react-icons/fi';
|
||||
|
||||
export default function UnloadIconBtn(props) {
|
||||
return (
|
||||
<IconButton
|
||||
icon={<FiCornerLeftUp />}
|
||||
colorScheme='whiteAlpha'
|
||||
backgroundColor='#ffffff05'
|
||||
variant='outline'
|
||||
onClick={props.clickHandler}
|
||||
width={90}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ export default function VisibleIconBtn(props) {
|
||||
colorScheme='blue'
|
||||
variant={props.active ? 'solid' : 'outline'}
|
||||
onClick={props.clickHandler}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import EditableTimer from '../../input/EditableTimer';
|
||||
import DelayValue from '../../input/DelayValue';
|
||||
import { showWarningToast } from '../../helpers/toastManager';
|
||||
|
||||
export default function EventTimes(props) {
|
||||
@@ -24,7 +23,6 @@ export default function EventTimes(props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<DelayValue delay={delay} />
|
||||
<EditableTimer
|
||||
name='timeStart'
|
||||
validate={handleValidate}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { millisToMinutes } from '../dateConfig';
|
||||
import style from './EditableTimer.module.css';
|
||||
import style from '../../features/editors/list/Block.module.css';
|
||||
|
||||
export default function DelayValue(props) {
|
||||
const { delay } = props;
|
||||
const delayed = delay > 0;
|
||||
return (
|
||||
<div className={delayed ? style.delayValue : undefined}>
|
||||
<div className={style.delayValue}>
|
||||
{delayed && <span>{`+ ${millisToMinutes(delay)}`}</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
||||
import { useState } from 'react';
|
||||
import style from './EditableText.module.css';
|
||||
|
||||
export default function EditableText(props) {
|
||||
const { label, defaultValue, placeholder, submitHandler } = props;
|
||||
const [text, setText] = useState(props.defaultValue || '');
|
||||
|
||||
const handleSubmit = (submitedVal) => {
|
||||
// No need to update if it hasnt changed
|
||||
@@ -11,19 +13,25 @@ export default function EditableText(props) {
|
||||
submitHandler(submitedVal);
|
||||
};
|
||||
|
||||
const handleChange = (val) => {
|
||||
if (val.length < 40) setText(val);
|
||||
console.log(val.length);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.block}>
|
||||
<span className={props.underlined ? style.titleUnderlined : style.title}>
|
||||
{label}
|
||||
</span>
|
||||
<Editable
|
||||
onChange={(v) => handleChange(v)}
|
||||
onSubmit={(v) => handleSubmit(v)}
|
||||
defaultValue={defaultValue}
|
||||
value={text}
|
||||
placeholder={placeholder}
|
||||
className={style.inline}
|
||||
>
|
||||
<EditablePreview />
|
||||
<EditableInput className={style.editable13} />
|
||||
<EditableInput />
|
||||
</Editable>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,13 +14,11 @@
|
||||
|
||||
.block {
|
||||
display: 'block';
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.editable13 {
|
||||
width: '13em';
|
||||
min-width: '13em';
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
.time {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
width: 5em;
|
||||
height: fit-content;
|
||||
@@ -10,17 +9,9 @@
|
||||
.delayedEditable {
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.delayedEditable {
|
||||
border: 1px solid #d69e2e55;
|
||||
}
|
||||
|
||||
.delayValue {
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 0.3em 0;
|
||||
text-align: center;
|
||||
align-self: flex-start;
|
||||
background-color: #d69e2e55;
|
||||
}
|
||||
|
||||
@@ -22,14 +22,16 @@ export default function TimeInput(props) {
|
||||
}, [props.value]);
|
||||
|
||||
const handleChange = (val) => {
|
||||
if (val <= 999) setValue(val);
|
||||
if (val <= 666) setValue(val);
|
||||
};
|
||||
|
||||
const handleSubmit = (val) => {
|
||||
if (val === props.value) return;
|
||||
if (val === '') setValue(0);
|
||||
if (val > 0 && val < 60) {
|
||||
if (val >= 0 && val <= 60) {
|
||||
props.submitHandler(val);
|
||||
} else {
|
||||
setValue(60);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import PauseIconBtn from '../../common/components/buttons/PauseIconBtn';
|
||||
import PrevIconBtn from '../../common/components/buttons/PrevIconBtn';
|
||||
import NextIconBtn from '../../common/components/buttons/NextIconBtn';
|
||||
import RollIconBtn from '../../common/components/buttons/RollIconBtn';
|
||||
import UnloadIconBtn from '../../common/components/buttons/UnloadIconBtn';
|
||||
import ReloadIconButton from '../../common/components/buttons/ReloadIconBtn';
|
||||
|
||||
export default function PlaybackControl() {
|
||||
const socket = useSocket();
|
||||
@@ -19,6 +21,14 @@ export default function PlaybackControl() {
|
||||
expectedFinish: null,
|
||||
});
|
||||
|
||||
const resetTimer = () => {
|
||||
setTimer({
|
||||
currentSeconds: null,
|
||||
startedAt: null,
|
||||
expectedFinish: null,
|
||||
});
|
||||
};
|
||||
|
||||
// handle incoming messages
|
||||
useEffect(() => {
|
||||
if (socket == null) return;
|
||||
@@ -56,9 +66,11 @@ export default function PlaybackControl() {
|
||||
break;
|
||||
case 'previous':
|
||||
socket.emit('set-playstate', 'previous');
|
||||
resetTimer();
|
||||
break;
|
||||
case 'next':
|
||||
socket.emit('set-playstate', 'next');
|
||||
resetTimer();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -93,13 +105,17 @@ export default function PlaybackControl() {
|
||||
active={playback === 'pause'}
|
||||
clickHandler={() => playbackControl('pause')}
|
||||
/>
|
||||
<PrevIconBtn clickHandler={() => playbackControl('previous')} />
|
||||
<NextIconBtn clickHandler={() => playbackControl('next')} />
|
||||
<RollIconBtn
|
||||
active={playback === 'roll'}
|
||||
clickHandler={() => playbackControl('roll')}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.playbackContainer}>
|
||||
<PrevIconBtn clickHandler={() => playbackControl('previous')} />
|
||||
<NextIconBtn clickHandler={() => playbackControl('next')} />
|
||||
<UnloadIconBtn clickHandler={() => playbackControl('unload')} />
|
||||
<ReloadIconButton clickHandler={() => playbackControl('reload')} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
padding: 2vh;
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr 1fr;
|
||||
grid-template-rows: 38vh 1fr 1fr;
|
||||
grid-template-columns: 1fr 25vw 25vw 5vw;
|
||||
grid-template-areas:
|
||||
'even mess play sett'
|
||||
@@ -67,7 +67,6 @@
|
||||
.mainContainer > div {
|
||||
border-radius: 0.5em;
|
||||
height: 100%;
|
||||
margin-top: 1.5vh;
|
||||
background-color: rgba(255, 255, 255, 0.13);
|
||||
padding: 0.8em 1.5em;
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu';
|
||||
import { IconButton } from '@chakra-ui/button';
|
||||
import { FiZap, FiPlus, FiMinus, FiMinusCircle, FiClock } from 'react-icons/fi';
|
||||
|
||||
export default function ActionButtons(props) {
|
||||
const { showDel, showAdd, showDelay, showBlock } = props;
|
||||
|
||||
const menuStyle = {
|
||||
color: '#000000',
|
||||
backgroundColor: 'rgba(255,255,255,0.67)',
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label='Options'
|
||||
size='xs'
|
||||
icon={<FiZap />}
|
||||
_hover={{ bg: 'pink.500' }}
|
||||
_expanded={{ bg: 'pink.300' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
colorScheme='pink'
|
||||
/>
|
||||
<MenuList style={menuStyle}>
|
||||
{showDel && (
|
||||
<MenuItem icon={<FiMinus />} onClick={props.deleteHandler}>
|
||||
Delete
|
||||
</MenuItem>
|
||||
)}
|
||||
{showAdd && (
|
||||
<MenuItem icon={<FiPlus />} onClick={props.addHandler}>
|
||||
Event next
|
||||
</MenuItem>
|
||||
)}
|
||||
{showDelay && (
|
||||
<MenuItem icon={<FiClock />} onClick={props.delayHandler}>
|
||||
Delay next
|
||||
</MenuItem>
|
||||
)}
|
||||
{showBlock && (
|
||||
<MenuItem icon={<FiMinusCircle />} onClick={props.blockHandler}>
|
||||
Block next
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -27,8 +27,7 @@
|
||||
gap: 0.5em;
|
||||
align-content: center;
|
||||
align-self: flex-start;
|
||||
|
||||
opacity: 0.4;
|
||||
opacity: 0.8;
|
||||
transition: linear 0.1s;
|
||||
}
|
||||
|
||||
@@ -52,7 +51,8 @@
|
||||
padding-left: 1em;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 3em auto auto 1fr auto;
|
||||
grid-template-columns: auto 2em auto auto 1fr auto;
|
||||
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: baseline;
|
||||
@@ -74,6 +74,45 @@
|
||||
);
|
||||
}
|
||||
|
||||
.drag {
|
||||
color: rgba(255, 255, 255, 0.37);
|
||||
align-self: center;
|
||||
/* cursor: grab; Later */
|
||||
}
|
||||
|
||||
.indicators {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: flex-start;
|
||||
font-size: 0.75em;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.next {
|
||||
color: #4bffabcc;
|
||||
width: max-content;
|
||||
padding: 0 0.2em;
|
||||
}
|
||||
|
||||
.nextDisabled {
|
||||
color: rgba(255, 255, 255, 0.17);
|
||||
}
|
||||
|
||||
.delayValue {
|
||||
color: #d69e2eaa;
|
||||
padding: 0 0.2em;
|
||||
text-align: center;
|
||||
align-self: flex-start;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.next:after,
|
||||
.delayValue:after {
|
||||
content: '\200b';
|
||||
}
|
||||
|
||||
.titleContainer,
|
||||
.detailedContainer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
@@ -124,4 +163,4 @@
|
||||
);
|
||||
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import AddIconBtn from '../../../common/components/buttons/AddIconBtn';
|
||||
import DelayIconBtn from '../../../common/components/buttons/DelayIconBtn';
|
||||
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
|
||||
import ActionButtons from './ActionButtons';
|
||||
import style from './Block.module.css';
|
||||
|
||||
export default function BlockBlock(props) {
|
||||
@@ -19,9 +17,15 @@ export default function BlockBlock(props) {
|
||||
return (
|
||||
<div className={style.block}>
|
||||
<div className={style.actionOverlay}>
|
||||
<DeleteIconBtn clickHandler={deleteHandler} />
|
||||
<AddIconBtn clickHandler={addHandler} />
|
||||
<DelayIconBtn clickHandler={delayHandler} />
|
||||
<ActionButtons
|
||||
showDel
|
||||
deleteHandler={deleteHandler}
|
||||
showAdd
|
||||
addHandler={addHandler}
|
||||
showDelay
|
||||
delayHandler={delayHandler}
|
||||
showBlock
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import AddIconBtn from '../../../common/components/buttons/AddIconBtn';
|
||||
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
|
||||
import { millisToMinutes } from '../../../common/dateConfig';
|
||||
import TimeInput from '../../../common/input/TimeInput';
|
||||
import style from './Block.module.css';
|
||||
import ActionButtons from './ActionButtons';
|
||||
|
||||
export default function DelayBlock(props) {
|
||||
const { data, eventsHandler, index } = props;
|
||||
@@ -13,6 +12,7 @@ export default function DelayBlock(props) {
|
||||
|
||||
const submitHandler = (value) => {
|
||||
// convert to ms and patch
|
||||
console.log('debug adding', { id: data.id, duration: value * 1000 * 60 });
|
||||
eventsHandler('patch', { id: data.id, duration: value * 1000 * 60 });
|
||||
};
|
||||
|
||||
@@ -27,8 +27,12 @@ export default function DelayBlock(props) {
|
||||
submitHandler={submitHandler}
|
||||
/>
|
||||
<div className={style.actionOverlay}>
|
||||
<DeleteIconBtn clickHandler={deleteHandler} />
|
||||
<AddIconBtn clickHandler={addHandler} />
|
||||
<ActionButtons
|
||||
showDel
|
||||
deleteHandler={deleteHandler}
|
||||
showAdd
|
||||
addHandler={addHandler}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { FiChevronDown, FiChevronUp } from 'react-icons/fi';
|
||||
import { FiChevronDown, FiChevronUp, FiMoreVertical } from 'react-icons/fi';
|
||||
import { useState } from 'react';
|
||||
import AddIconBtn from '../../../common/components/buttons/AddIconBtn';
|
||||
import BlockIconBtn from '../../../common/components/buttons/BlockIconBtn';
|
||||
import DelayIconBtn from '../../../common/components/buttons/DelayIconBtn';
|
||||
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
|
||||
import EventTimes from '../../../common/components/eventTimes/EventTimes';
|
||||
import { showErrorToast } from '../../../common/helpers/toastManager';
|
||||
import style from './Block.module.css';
|
||||
import EditableText from '../../../common/input/EditableText';
|
||||
import DelayValue from '../../../common/input/DelayValue';
|
||||
import ActionButtons from './ActionButtons';
|
||||
import VisibleIconBtn from '../../../common/components/buttons/VisibleIconBtn';
|
||||
|
||||
export default function EventBlock(props) {
|
||||
const { data, selected, delay, index, eventsHandler } = props;
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const [more, setMore] = useState(false);
|
||||
|
||||
@@ -52,8 +52,18 @@ export default function EventBlock(props) {
|
||||
updateValues('presenter', v);
|
||||
};
|
||||
|
||||
// TODO: implement functionality to select next
|
||||
let isNext = false;
|
||||
|
||||
return (
|
||||
<div className={selected ? style.eventRowActive : style.eventRow}>
|
||||
<span className={style.drag}>
|
||||
<FiMoreVertical />
|
||||
</span>
|
||||
<div className={style.indicators}>
|
||||
<div className={isNext ? style.next : style.nextDisabled}>Next</div>
|
||||
<DelayValue delay={delay} />
|
||||
</div>
|
||||
<EventTimes
|
||||
updateValues={updateValues}
|
||||
timeStart={data.timeStart}
|
||||
@@ -67,22 +77,22 @@ export default function EventBlock(props) {
|
||||
label='Title'
|
||||
defaultValue={data.title}
|
||||
placeholder='Add Title'
|
||||
underlined
|
||||
submitHandler={handleTitleSubmit}
|
||||
underlined
|
||||
/>
|
||||
<EditableText
|
||||
label='Subtitle'
|
||||
defaultValue={data.subtitle}
|
||||
placeholder='Add Subtitle'
|
||||
underlined
|
||||
submitHandler={handleSubtitleSubmit}
|
||||
underlined
|
||||
/>
|
||||
<EditableText
|
||||
label='Presenter'
|
||||
defaultValue={data.subtitle}
|
||||
defaultValue={data.presenter}
|
||||
placeholder='Add Presenter name'
|
||||
underlined
|
||||
submitHandler={handlePresenterSubmit}
|
||||
underlined
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -92,6 +102,7 @@ export default function EventBlock(props) {
|
||||
defaultValue={data.title}
|
||||
placeholder='Add Title'
|
||||
submitHandler={handleTitleSubmit}
|
||||
isTight
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -100,10 +111,21 @@ export default function EventBlock(props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.actionOverlay}>
|
||||
<DeleteIconBtn clickHandler={deleteHandler} />
|
||||
<AddIconBtn clickHandler={addHandler} />
|
||||
<DelayIconBtn clickHandler={delayHandler} />
|
||||
<BlockIconBtn clickHandler={blockHandler} />
|
||||
<VisibleIconBtn
|
||||
clickHandler={() => setVisible(!visible)}
|
||||
active={visible}
|
||||
/>
|
||||
<ActionButtons
|
||||
showDel
|
||||
deleteHandler={deleteHandler}
|
||||
showAdd
|
||||
addHandler={addHandler}
|
||||
showDelay
|
||||
delayHandler={delayHandler}
|
||||
showBlock
|
||||
blockHandler={blockHandler}
|
||||
showPublic
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -24,11 +24,14 @@ export default function EventListWrapper() {
|
||||
// we optimistically update here
|
||||
onMutate: async (newEvent) => {
|
||||
// cancel ongoing queries
|
||||
queryClient.cancelQueries(eventsNamespace);
|
||||
queryClient.cancelQueries(eventsNamespace, { exact: true });
|
||||
|
||||
// Snapshot the previous value
|
||||
const previousEvents = queryClient.getQueryData(eventsNamespace);
|
||||
|
||||
let previousEvents = queryClient.getQueryData(eventsNamespace);
|
||||
if (previousEvents == null) {
|
||||
await queryClient.refetchQueries();
|
||||
previousEvents = queryClient.getQueryData(eventsNamespace);
|
||||
}
|
||||
// optimistically update object, temp ID until refetch
|
||||
let optimistic = [...previousEvents];
|
||||
optimistic.splice(newEvent.order, 0, {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
overflow-y: scroll;
|
||||
height: 71vh;
|
||||
height: 73vh;
|
||||
}
|
||||
|
||||
.empty {
|
||||
|
||||
@@ -17,6 +17,9 @@ export default function EventListMenu(props) {
|
||||
variant: 'outline',
|
||||
colorScheme: 'whiteAlpha',
|
||||
backgroundColor: '#ffffff05',
|
||||
_hover: { bg: 'blue.800' },
|
||||
_expanded: { bg: 'blue.400' },
|
||||
_focus: { boxShadow: 'none' },
|
||||
};
|
||||
|
||||
const menuStyle = {
|
||||
|
||||
@@ -15,15 +15,15 @@ import {
|
||||
Textarea,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
fetchSettings,
|
||||
postSettings,
|
||||
settingsNamespace,
|
||||
} from '../../app/api/settingsApi';
|
||||
fetchEvent,
|
||||
postEvent,
|
||||
eventNamespace,
|
||||
} from '../../app/api/eventApi';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function SettingsModal(props) {
|
||||
const { data, status, isError } = useQuery(settingsNamespace, fetchSettings);
|
||||
const { data, status, isError } = useQuery(eventNamespace, fetchEvent);
|
||||
const [formData, setFormData] = useState({
|
||||
title: '',
|
||||
url: '',
|
||||
@@ -48,7 +48,7 @@ export default function SettingsModal(props) {
|
||||
event.preventDefault();
|
||||
setSubmitting(true);
|
||||
|
||||
await postSettings(formData).then(setSubmitting(false)).then(onClose);
|
||||
await postEvent(formData).then(setSubmitting(false)).then(onClose);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -77,7 +77,7 @@ export default function SettingsModal(props) {
|
||||
size='sm'
|
||||
name='title'
|
||||
placeholder='Event Title'
|
||||
autocomplete='off'
|
||||
autoComplete='off'
|
||||
value={formData.title}
|
||||
onChange={(event) =>
|
||||
setFormData({ ...formData, title: event.target.value })
|
||||
@@ -97,7 +97,7 @@ export default function SettingsModal(props) {
|
||||
size='sm'
|
||||
name='url'
|
||||
placeholder='www.onsite.no'
|
||||
autocomplete='off'
|
||||
autoComplete='off'
|
||||
value={formData.url}
|
||||
onChange={(event) =>
|
||||
setFormData({ ...formData, url: event.target.value })
|
||||
@@ -117,7 +117,7 @@ export default function SettingsModal(props) {
|
||||
size='sm'
|
||||
name='pubInfo'
|
||||
placeholder='Information to be shown on public screens'
|
||||
autocomplete='off'
|
||||
autoComplete='off'
|
||||
value={formData.publicInfo}
|
||||
onChange={(event) =>
|
||||
setFormData({
|
||||
@@ -140,7 +140,7 @@ export default function SettingsModal(props) {
|
||||
size='sm'
|
||||
name='backstageInfo'
|
||||
placeholder='Information to be shown on backstage screens'
|
||||
autocomplete='off'
|
||||
autoComplete='off'
|
||||
value={formData.backstageInfo}
|
||||
onChange={(event) =>
|
||||
setFormData({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { eventsNamespace, fetchAllEvents } from '../../app/api/eventsApi';
|
||||
import { fetchSettings, settingsNamespace } from '../../app/api/settingsApi';
|
||||
import { fetchEvent, eventNamespace } from '../../app/api/eventApi';
|
||||
import { useSocket } from '../../app/context/socketContext';
|
||||
import { stringFromMillis } from '../../common/dateConfig';
|
||||
|
||||
@@ -16,9 +16,10 @@ const withSocket = (Component) => {
|
||||
data: genData,
|
||||
status: genDataStatus,
|
||||
isError: genDataIsError,
|
||||
} = useQuery(settingsNamespace, fetchSettings);
|
||||
} = useQuery(eventNamespace, fetchEvent);
|
||||
|
||||
const [events, setEvents] = useState([]);
|
||||
const [publicEvents, setPublicEvents] = useState([]);
|
||||
const [backstageEvents, setBackstageEvents] = useState([]);
|
||||
|
||||
const socket = useSocket();
|
||||
const [pres, setPres] = useState({
|
||||
@@ -54,6 +55,7 @@ const withSocket = (Component) => {
|
||||
publicInfo: '',
|
||||
backstageInfo: '',
|
||||
});
|
||||
const [playback, setPlayback] = useState(null);
|
||||
|
||||
// Ask for update on load
|
||||
useEffect(() => {
|
||||
@@ -79,6 +81,11 @@ const withSocket = (Component) => {
|
||||
setTimer({ ...data });
|
||||
});
|
||||
|
||||
// Handle playstate
|
||||
socket.on('playstate', (data) => {
|
||||
setPlayback(data);
|
||||
});
|
||||
|
||||
// Handle titles
|
||||
socket.on('titles', (data) => {
|
||||
setTitles({ ...data });
|
||||
@@ -95,6 +102,12 @@ const withSocket = (Component) => {
|
||||
// Ask for up to data
|
||||
socket.emit('get-presenter');
|
||||
|
||||
// ask for timer
|
||||
socket.emit('get-timer');
|
||||
|
||||
// ask for playstate
|
||||
socket.emit('get-playstate');
|
||||
|
||||
// Ask for up titles
|
||||
socket.emit('get-titles');
|
||||
|
||||
@@ -107,6 +120,7 @@ const withSocket = (Component) => {
|
||||
socket.off('messages-presenter');
|
||||
socket.off('messages-lower');
|
||||
socket.off('timer');
|
||||
socket.off('playstate');
|
||||
socket.off('titles');
|
||||
socket.off('selected-id');
|
||||
};
|
||||
@@ -117,9 +131,11 @@ const withSocket = (Component) => {
|
||||
if (eventsData == null) return;
|
||||
|
||||
// filter just events with title
|
||||
const e = eventsData.filter((d) => d.type === 'event' && d.title !== '');
|
||||
const pe = eventsData.filter((d) => d.type === 'event' && d.title !== '');
|
||||
setPublicEvents(pe);
|
||||
|
||||
setEvents(e);
|
||||
// everything goes backstage
|
||||
setBackstageEvents(eventsData);
|
||||
}, [eventsData]);
|
||||
|
||||
// Set general data
|
||||
@@ -133,12 +149,17 @@ const withSocket = (Component) => {
|
||||
/*** WRAP INFORMATION RELATED TO TITLES ***/
|
||||
/*** ---------------------------------- ***/
|
||||
/********************************************/
|
||||
// is there a now field?
|
||||
let showNow = true;
|
||||
if (!titles.titleNow && !titles.subtitleNow && !titles.presenterNow)
|
||||
showNow = false;
|
||||
|
||||
// is there a next field?
|
||||
let showNext = true;
|
||||
if (!titles.titleNext && !titles.subtitleNext && !titles.presenterNext)
|
||||
showNext = false;
|
||||
|
||||
const titleManager = { ...titles, showNext: showNext };
|
||||
const titleManager = { ...titles, showNow: showNow, showNext: showNext };
|
||||
|
||||
/******************************************/
|
||||
/*** + timeManager ***/
|
||||
@@ -152,7 +173,12 @@ const withSocket = (Component) => {
|
||||
// get clock
|
||||
let clock = stringFromMillis(timer.clock);
|
||||
|
||||
const timeManager = { ...timer, finished: finished, clock: clock };
|
||||
const timeManager = {
|
||||
...timer,
|
||||
finished: finished,
|
||||
clock: clock,
|
||||
playstate: playback,
|
||||
};
|
||||
|
||||
return (
|
||||
<Component
|
||||
@@ -162,7 +188,8 @@ const withSocket = (Component) => {
|
||||
lower={lower}
|
||||
title={titleManager}
|
||||
time={timeManager}
|
||||
events={events}
|
||||
events={publicEvents}
|
||||
backstageEvents={backstageEvents}
|
||||
selectedId={selectedId}
|
||||
general={general}
|
||||
/>
|
||||
|
||||
@@ -5,7 +5,7 @@ import Paginator from './Paginator';
|
||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
||||
|
||||
export default function StageManager(props) {
|
||||
const { pres, publ, lower, title, time, events, selectedId, general } = props;
|
||||
const { publ, title, time, events, selectedId, general } = props;
|
||||
|
||||
// Format messages
|
||||
const showPubl = publ.text !== '' && publ.visible;
|
||||
@@ -19,18 +19,24 @@ export default function StageManager(props) {
|
||||
<NavLogo />
|
||||
|
||||
<div className={style.eventTitle}>{general.title}</div>
|
||||
<div className={style.nowContainer}>
|
||||
<div className={style.label}>Now</div>
|
||||
<div className={style.nowTitle}>{title.titleNow}</div>
|
||||
<div className={style.nowSubtitle}>{title.subtitleNow}</div>
|
||||
<div className={style.nowPresenter}>{title.presenterNow}</div>
|
||||
</div>
|
||||
<div className={style.nextContainer}>
|
||||
<div className={style.label}>Next</div>
|
||||
<div className={style.nextTitle}>{title.titleNext}</div>
|
||||
<div className={style.nextSubtitle}>{title.subtitleNext}</div>
|
||||
<div className={style.nextPresenter}>{title.presenterNext}</div>
|
||||
</div>
|
||||
{title.showNow && (
|
||||
<div className={style.nowContainer}>
|
||||
<div className={style.label}>Now</div>
|
||||
<div className={style.nowTitle}>{title.titleNow}</div>
|
||||
<div className={style.nowSubtitle}>{title.subtitleNow}</div>
|
||||
<div className={style.nowPresenter}>{title.presenterNow}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{title.showNext && (
|
||||
<div className={style.nextContainer}>
|
||||
<div className={style.label}>Next</div>
|
||||
<div className={style.nextTitle}>{title.titleNext}</div>
|
||||
<div className={style.nextSubtitle}>{title.subtitleNext}</div>
|
||||
<div className={style.nextPresenter}>{title.presenterNext}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={style.todayContainer}>
|
||||
<div className={style.label}>Today</div>
|
||||
<div className={style.entriesContainer}>
|
||||
|
||||
@@ -2,16 +2,18 @@ import { AnimatePresence, motion } from 'framer-motion';
|
||||
import style from './Lower.module.css';
|
||||
|
||||
export default function Lower(props) {
|
||||
const { pres, publ, lower, title, time, events, selectedId, general } = props;
|
||||
const { lower, title, time, general } = props;
|
||||
|
||||
// getting config from URL
|
||||
// like http://localhost:3000/lower?bg=ff2&text=f00&size=0.6&transition=5
|
||||
// getting config from URL: preset, size, transition, bg, text, key
|
||||
// eg. http://localhost:3000/lower?bg=ff2&text=f00&size=0.6&transition=5
|
||||
let params = new URLSearchParams(props.location.search);
|
||||
|
||||
// Preset: choose from an animation preset, not yet
|
||||
const preset = params.get('preset') ? params.get('preset') : 1;
|
||||
|
||||
const size = params.get('size') ? params.get('size') : 1;
|
||||
const transitionIn = params.get('transition') ? params.get('transition') : 3;
|
||||
const textColour = params.get('bg') ? `#${params.get('text')}` : '#fffffa';
|
||||
const textColour = params.get('text') ? `#${params.get('text')}` : '#fffffa';
|
||||
const bgColour = params.get('bg') ? `#${params.get('bg')}` : '#00000033';
|
||||
const key = params.get('key') ? `#${params.get('key')}` : 'null';
|
||||
|
||||
@@ -87,7 +89,7 @@ export default function Lower(props) {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className={style.lowerThird}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
.lowerContainer {
|
||||
position: absolute;
|
||||
top: 70vh;
|
||||
top: 75vh;
|
||||
background-color: #00000033;
|
||||
|
||||
padding: 1vw 1vw 1vw 0;
|
||||
|
||||
@@ -4,9 +4,10 @@ import NavLogo from '../../../common/components/nav/NavLogo';
|
||||
import style from './PresenterView.module.css';
|
||||
|
||||
export default function PresenterView(props) {
|
||||
const { pres, publ, lower, title, time } = props;
|
||||
const { pres, title, time } = props;
|
||||
|
||||
let showOverlay = pres.text !== '' && pres.visible;
|
||||
const showOverlay = pres.text !== '' && pres.visible;
|
||||
const isPlaying = time.playstate === 'start';
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -33,14 +34,18 @@ export default function PresenterView(props) {
|
||||
{time.finished ? (
|
||||
<div className={style.finished}>TIME UP</div>
|
||||
) : (
|
||||
<div className={style.countdown}>
|
||||
<div className={isPlaying ? style.countdown : style.countdownPaused}>
|
||||
<Countdown time={time.currentSeconds} hideZeroHours />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!time.finished && (
|
||||
<div className={style.progressContainer}>
|
||||
<div
|
||||
className={
|
||||
isPlaying ? style.progressContainer : style.progressContainerPaused
|
||||
}
|
||||
>
|
||||
<MyProgressBar
|
||||
now={time.currentSeconds}
|
||||
complete={time.durationSeconds}
|
||||
@@ -49,12 +54,14 @@ export default function PresenterView(props) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={style.nowContainer}>
|
||||
<div className={style.label}>Now</div>
|
||||
<div className={style.title}>{title.titleNow}</div>
|
||||
<div className={style.subtitle}>{title.subtitleNow}</div>
|
||||
<div className={style.presenter}>{title.presenterNow}</div>
|
||||
</div>
|
||||
{title.showNow && (
|
||||
<div className={style.nowContainer}>
|
||||
<div className={style.label}>Now</div>
|
||||
<div className={style.title}>{title.titleNow}</div>
|
||||
<div className={style.subtitle}>{title.subtitleNow}</div>
|
||||
<div className={style.presenter}>{title.presenterNow}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{title.showNext && (
|
||||
<div className={style.nextContainer}>
|
||||
|
||||
@@ -101,10 +101,21 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.progressContainer {
|
||||
.countdown {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.progressContainer,
|
||||
.progressContainerPaused {
|
||||
grid-area: prog;
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.countdownPaused,
|
||||
.progressContainerPaused {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.container__grayFinished {
|
||||
|
||||
Reference in New Issue
Block a user