mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-13 19:33:46 +00:00
0fea4064c3
* chore: upgrade relevant libraries * feat(skip): add skip styling to paginated items * chore: upgrade relevant libraries * Fix: issue with text colour (#214) * fix: adjust text colour from context * fix: issue with wrong proptype * fix issue with vite migration (#217) * hotfix: 1.8.2 issues vite migration * fix: file import options * feat(55): styling * refactor: remove onhover option for entry block * refactor: relocate logging provider * refactor: convert to typescript * feat(55): add event editor * refactor: extract event actions * fix: bad import * feat(55): redesign components * fix: issues with not awaiting async * refactor: replace socket with subscription * refactor: remove unused * style: small tweaks * refactor: extract event actions * refactor: cleanup debug * refactor: chakra imports * fix: optimistic mutations * refactor: handle promise rejections * refactor: validation * fix: rq optimistic mutations * feat: add playback feedback to block * refactor: no optimistic adding of events * refactor: simplify cursor state * styles: cleanup editor style * refactor: cleanup duration update * fix: revert package upgrade (issues with vitest) * fix: prevent cyclic imports * refactor: extract data fetcher * refactor: typescript migration * chore: update tests
207 lines
6.9 KiB
React
207 lines
6.9 KiB
React
import { useCallback, useContext, useEffect, useState } from 'react';
|
|
import { Button } from '@chakra-ui/react';
|
|
import { FiUsers } from '@react-icons/all-files/fi/FiUsers';
|
|
import { IoBan } from '@react-icons/all-files/io5/IoBan';
|
|
import { editorEventId } from 'common/atoms/LocalEventSettings';
|
|
import ColourInput from 'common/components/input/ColourInput';
|
|
import TextInput from 'common/components/input/TextInput';
|
|
import TimeInput from 'common/components/input/TimeInput';
|
|
import { LoggingContext } from 'common/context/LoggingContext';
|
|
import { useEventAction } from 'common/hooks/useEventAction';
|
|
import { millisToMinutes } from 'common/utils/dateConfig';
|
|
import getDelayTo from 'common/utils/getDelayTo';
|
|
import { stringFromMillis } from 'common/utils/time';
|
|
import { calculateDuration, validateEntry } from 'common/utils/timesManager';
|
|
import { useAtom } from 'jotai';
|
|
|
|
import useEventsList from '../../common/hooks-query/useEventsList';
|
|
|
|
import style from './EventEditor.module.scss';
|
|
|
|
export default function EventEditor() {
|
|
const [openId] = useAtom(editorEventId);
|
|
const { data } = useEventsList();
|
|
const { emitWarning, emitError } = useContext(LoggingContext);
|
|
const { updateEvent } = useEventAction();
|
|
const [event, setEvent] = useState(null);
|
|
const [delay, setDelay] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (!data || !openId) {
|
|
return;
|
|
}
|
|
|
|
const eventIndex = data.findIndex((event) => event.id === openId);
|
|
if (eventIndex > -1) {
|
|
setDelay(getDelayTo(data, eventIndex));
|
|
setEvent(data[eventIndex]);
|
|
}
|
|
}, [data, event, openId]);
|
|
|
|
const handleSubmit = useCallback(
|
|
(field, value) => {
|
|
const newEventData = { id: event.id };
|
|
switch (field) {
|
|
case 'durationOverride': {
|
|
// duration defines timeEnd
|
|
newEventData.timeEnd = event.timeStart += value;
|
|
break;
|
|
}
|
|
case 'timeStart': {
|
|
newEventData.duration = calculateDuration(value, event.timeEnd);
|
|
newEventData.timeStart = value;
|
|
break;
|
|
}
|
|
case 'timeEnd': {
|
|
newEventData.duration = calculateDuration(event.timeStart, value);
|
|
newEventData.timeEnd = value;
|
|
break;
|
|
}
|
|
default: {
|
|
if (field in event) {
|
|
// create object with new field
|
|
newEventData[field] = value;
|
|
break;
|
|
} else {
|
|
emitError(`Unknown field: ${field}`);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
updateEvent(newEventData);
|
|
},
|
|
[emitError, event, updateEvent],
|
|
);
|
|
|
|
const timerValidationHandler = useCallback(
|
|
(entry, val) => {
|
|
const valid = validateEntry(entry, val, event.timeStart, event.timeEnd);
|
|
if (!valid.value) {
|
|
emitWarning(`Time Input Warning: ${valid.catch}`);
|
|
}
|
|
return valid.value;
|
|
},
|
|
[emitWarning, event?.timeStart, event?.timeEnd],
|
|
);
|
|
|
|
const togglePublic = useCallback(
|
|
(currentValue) => {
|
|
updateEvent({ id: event.id, isPublic: !currentValue });
|
|
},
|
|
[event?.id, updateEvent],
|
|
);
|
|
|
|
if (!event) {
|
|
return <span>Loading</span>;
|
|
}
|
|
|
|
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;
|
|
|
|
return (
|
|
<div className={style.eventEditor}>
|
|
<div className={style.timers}>
|
|
<label className={style.inputLabel}>
|
|
Start time {delayed && <span className={style.delayLabel}>{addedTime}</span>}
|
|
{delayed && <div className={style.delayLabel}>{newStart}</div>}
|
|
</label>
|
|
<TimeInput
|
|
name='timeStart'
|
|
submitHandler={handleSubmit}
|
|
validationHandler={timerValidationHandler}
|
|
time={event.timeStart}
|
|
delay={0}
|
|
placeholder='Start'
|
|
/>
|
|
<label className={style.inputLabel}>
|
|
End time {delayed && <span className={style.delayLabel}>{addedTime}</span>}
|
|
{delayed && <div className={style.delayLabel}>{newEnd}</div>}
|
|
</label>
|
|
<TimeInput
|
|
name='timeEnd'
|
|
submitHandler={handleSubmit}
|
|
validationHandler={timerValidationHandler}
|
|
time={event.timeEnd}
|
|
delay={0}
|
|
placeholder='End'
|
|
/>
|
|
<label className={style.inputLabel}>Duration</label>
|
|
<TimeInput
|
|
name='duration'
|
|
submitHandler={handleSubmit}
|
|
validationHandler={timerValidationHandler}
|
|
time={event.duration}
|
|
delay={0}
|
|
placeholder='Duration'
|
|
/>
|
|
</div>
|
|
<div className={style.titles}>
|
|
<div className={style.left}>
|
|
<div className={style.column}>
|
|
<label className={style.inputLabel}>Title</label>
|
|
<TextInput field='title' initialText={event.title} submitHandler={handleSubmit} />
|
|
</div>
|
|
<div className={style.column}>
|
|
<label className={style.inputLabel}>Subtitle</label>
|
|
<TextInput field='subtitle' initialText={event.subtitle} submitHandler={handleSubmit} />
|
|
</div>
|
|
<div className={style.column}>
|
|
<label className={style.inputLabel}>Presenter</label>
|
|
<TextInput
|
|
field='presenter'
|
|
initialText={event.presenter}
|
|
submitHandler={handleSubmit}
|
|
/>
|
|
</div>
|
|
<div className={style.padTop}>
|
|
<Button
|
|
leftIcon={<FiUsers />}
|
|
size='sm'
|
|
colorScheme='blue'
|
|
variant={event.isPublic ? 'solid' : 'outline'}
|
|
onClick={() => togglePublic(event.isPublic)}
|
|
>
|
|
{event.isPublic ? 'Event is Public' : 'Make event public'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className={style.right}>
|
|
<div className={style.column}>
|
|
<label className={style.inputLabel}>Colour</label>
|
|
<div className={style.inline}>
|
|
<ColourInput
|
|
value={event?.colour}
|
|
handleChange={(value) => handleSubmit('colour', value)}
|
|
/>
|
|
<Button
|
|
leftIcon={<IoBan />}
|
|
onClick={() => handleSubmit('colour', '')}
|
|
variant='outline'
|
|
colorScheme='blue'
|
|
borderRadius='3px'
|
|
size='sm'
|
|
>
|
|
Clear colour
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className={style.column}>
|
|
<label className={style.inputLabel}>Notes</label>
|
|
<TextInput
|
|
field='note'
|
|
initialText={event.note}
|
|
submitHandler={handleSubmit}
|
|
isTextArea
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className={style.osc}>{`OSC Trigger /ontime/gotoid/${event.id}`}</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|