mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 04:43:35 +00:00
Feat/55 v2 (#225)
* 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
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user