mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 12:53:32 +00:00
32b5ab0574
* fix: fetch in offline environments (#295) * Add arm platforms to docker build (#297) * add arm platforms to docker build --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> * fix: docker build (#298) Co-authored-by: Fabian Posenau <fabian@fphome.de> * Timer: fix too many renders error when using ?progress (#305) * Timer: fix too many renders error when using ?progress * version bump (#314) * style: small tweaks * fix: prevent actions in roll mode * chore: update external assets * style: show shortcuts in quick blocks * refactor: revert optimisations on callbacks * fun: industry dictionary * style: indicators for event features --------- Co-authored-by: Fabian Posenau <fabian.p99@gmx.de> Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: Marks Polakovs <github@markspolakovs.me>
260 lines
8.5 KiB
TypeScript
260 lines
8.5 KiB
TypeScript
import { memo, useCallback, useEffect, useState } from 'react';
|
|
import { Editable, EditableInput, EditablePreview, Tooltip } from '@chakra-ui/react';
|
|
import { IoCaretDownCircle } from '@react-icons/all-files/io5/IoCaretDownCircle';
|
|
import { IoCaretUpCircle } from '@react-icons/all-files/io5/IoCaretUpCircle';
|
|
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
|
import { IoPeople } from '@react-icons/all-files/io5/IoPeople';
|
|
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
|
import { IoPlayCircle } from '@react-icons/all-files/io5/IoPlayCircle';
|
|
import { IoPlayForwardCircle } from '@react-icons/all-files/io5/IoPlayForwardCircle';
|
|
import { IoPlayOutline } from '@react-icons/all-files/io5/IoPlayOutline';
|
|
import { IoPlaySkipForwardCircle } from '@react-icons/all-files/io5/IoPlaySkipForwardCircle';
|
|
import { IoReload } from '@react-icons/all-files/io5/IoReload';
|
|
import { IoRemoveCircle } from '@react-icons/all-files/io5/IoRemoveCircle';
|
|
import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
|
|
import { IoStopCircle } from '@react-icons/all-files/io5/IoStopCircle';
|
|
import { IoTime } from '@react-icons/all-files/io5/IoTime';
|
|
import { EndAction, Playback, TimerType } from 'ontime-types';
|
|
|
|
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
|
import { useEventAction } from '../../../common/hooks/useEventAction';
|
|
import { setEventPlayback } from '../../../common/hooks/useSocket';
|
|
import { useEventEditorStore } from '../../../common/stores/eventEditor';
|
|
import { tooltipDelayMid } from '../../../ontimeConfig';
|
|
import { EventItemActions } from '../RundownEntry';
|
|
|
|
import BlockActionMenu from './composite/BlockActionMenu';
|
|
import EventBlockProgressBar from './composite/EventBlockProgressBar';
|
|
import EventBlockTimers from './composite/EventBlockTimers';
|
|
|
|
import style from './EventBlock.module.scss';
|
|
|
|
const blockBtnStyle = {
|
|
size: 'sm',
|
|
};
|
|
|
|
const tooltipProps = {
|
|
openDelay: tooltipDelayMid,
|
|
};
|
|
|
|
interface EventBlockInnerProps {
|
|
isOpen: boolean;
|
|
timeStart: number;
|
|
timeEnd: number;
|
|
duration: number;
|
|
eventId: string;
|
|
isPublic: boolean;
|
|
endAction: EndAction;
|
|
timerType: TimerType;
|
|
title: string;
|
|
note: string;
|
|
delay: number;
|
|
previousEnd: number;
|
|
next: boolean;
|
|
skip: boolean;
|
|
selected: boolean;
|
|
playback?: Playback;
|
|
actionHandler: (action: EventItemActions, payload?: any) => void;
|
|
}
|
|
|
|
const EventBlockInner = (props: EventBlockInnerProps) => {
|
|
const {
|
|
isOpen,
|
|
timeStart,
|
|
timeEnd,
|
|
duration,
|
|
eventId,
|
|
isPublic = true,
|
|
endAction,
|
|
timerType,
|
|
title,
|
|
note,
|
|
delay,
|
|
previousEnd,
|
|
next,
|
|
skip = false,
|
|
selected,
|
|
playback,
|
|
actionHandler,
|
|
} = props;
|
|
|
|
const { updateEvent } = useEventAction();
|
|
|
|
const [blockTitle, setBlockTitle] = useState<string>(title || '');
|
|
const [renderInner, setRenderInner] = useState(false);
|
|
const setOpenEvent = useEventEditorStore((state) => state.setOpenEvent);
|
|
const removeOpenEvent = useEventEditorStore((state) => state.removeOpenEvent);
|
|
|
|
// Todo: could I re-render the item without causing a state change here?
|
|
// ?? use refs instead?
|
|
|
|
useEffect(() => {
|
|
setRenderInner(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
setBlockTitle(title);
|
|
}, [title]);
|
|
|
|
const handleTitle = useCallback(
|
|
(text: string) => {
|
|
if (text === title) {
|
|
return;
|
|
}
|
|
|
|
const cleanVal = text.trim();
|
|
setBlockTitle(cleanVal);
|
|
|
|
updateEvent({ id: eventId, title: cleanVal });
|
|
},
|
|
[title, updateEvent, eventId],
|
|
);
|
|
|
|
const toggleOpenEvent = useCallback(() => {
|
|
if (isOpen) {
|
|
removeOpenEvent();
|
|
} else {
|
|
setOpenEvent(eventId);
|
|
}
|
|
}, [eventId, isOpen, removeOpenEvent, setOpenEvent]);
|
|
|
|
const eventIsPlaying = selected && playback === Playback.Play;
|
|
const playBtnStyles = { _hover: {} };
|
|
if (!skip && eventIsPlaying) {
|
|
playBtnStyles._hover = { bg: '#c05621' };
|
|
} else if (!skip && !eventIsPlaying) {
|
|
playBtnStyles._hover = {};
|
|
}
|
|
|
|
return !renderInner ? null : (
|
|
<>
|
|
<div className={style.playbackActions}>
|
|
<TooltipActionBtn
|
|
variant='ontime-subtle-white'
|
|
aria-label='Skip event'
|
|
tooltip='Skip event'
|
|
icon={skip ? <IoRemoveCircle /> : <IoRemoveCircleOutline />}
|
|
{...tooltipProps}
|
|
{...blockBtnStyle}
|
|
clickHandler={() => actionHandler('update', { field: 'skip', value: !skip })}
|
|
tabIndex={-1}
|
|
disabled={selected}
|
|
/>
|
|
<TooltipActionBtn
|
|
variant='ontime-subtle-white'
|
|
aria-label='Load event'
|
|
tooltip='Load event'
|
|
icon={<IoReload className={style.flip} />}
|
|
disabled={skip}
|
|
{...tooltipProps}
|
|
{...blockBtnStyle}
|
|
clickHandler={() => setEventPlayback.loadEvent(eventId)}
|
|
tabIndex={-1}
|
|
/>
|
|
<TooltipActionBtn
|
|
variant='ontime-subtle-white'
|
|
aria-label='Start event'
|
|
tooltip='Start event'
|
|
icon={eventIsPlaying ? <IoPlay /> : <IoPlayOutline />}
|
|
disabled={skip}
|
|
{...tooltipProps}
|
|
{...blockBtnStyle}
|
|
clickHandler={() => setEventPlayback.startEvent(eventId)}
|
|
backgroundColor={eventIsPlaying ? '#58A151' : undefined}
|
|
_hover={{ backgroundColor: eventIsPlaying ? '#58A151' : undefined }}
|
|
tabIndex={-1}
|
|
/>
|
|
</div>
|
|
<EventBlockTimers
|
|
timeStart={timeStart}
|
|
timeEnd={timeEnd}
|
|
duration={duration}
|
|
delay={delay}
|
|
actionHandler={actionHandler}
|
|
previousEnd={previousEnd}
|
|
/>
|
|
<Editable
|
|
variant='ontime'
|
|
value={blockTitle}
|
|
className={`${style.eventTitle} ${!title ? style.noTitle : ''}`}
|
|
placeholder='Event title'
|
|
onChange={(value) => setBlockTitle(value)}
|
|
onSubmit={(value) => handleTitle(value)}
|
|
>
|
|
<EditablePreview className={style.preview} />
|
|
<EditableInput />
|
|
</Editable>
|
|
<div className={style.statusElements}>
|
|
<span className={style.eventNote}>{note}</span>
|
|
<div className={selected ? style.progressBg : `${style.progressBg} ${style.hidden}`}>
|
|
{selected && <EventBlockProgressBar playback={playback} />}
|
|
</div>
|
|
<div className={style.eventStatus} tabIndex={-1}>
|
|
{next && (
|
|
<Tooltip label='Next event' {...tooltipProps}>
|
|
<span className={style.tag}>NEXT</span>
|
|
</Tooltip>
|
|
)}
|
|
<Tooltip label={`End action: ${endAction}`} {...tooltipProps}>
|
|
<span>
|
|
<EndActionIcon action={endAction} className={style.statusIcon} />
|
|
</span>
|
|
</Tooltip>
|
|
<Tooltip label={`Time type: ${timerType}`} {...tooltipProps}>
|
|
<span>
|
|
<TimerIcon type={timerType} className={style.statusIcon} />
|
|
</span>
|
|
</Tooltip>
|
|
<Tooltip label={`${isPublic ? 'Event is public' : 'Event is private'}`} {...tooltipProps}>
|
|
<span>
|
|
<IoPeople className={`${style.statusIcon} ${isPublic ? style.active : style.disabled}`} />
|
|
</span>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
<div className={style.eventActions}>
|
|
<TooltipActionBtn
|
|
{...blockBtnStyle}
|
|
variant='ontime-subtle-white'
|
|
size='sm'
|
|
icon={<IoOptions />}
|
|
clickHandler={toggleOpenEvent}
|
|
tooltip='Event options'
|
|
aria-label='Event options'
|
|
tabIndex={-1}
|
|
backgroundColor={isOpen ? '#2B5ABC' : undefined}
|
|
color={isOpen ? 'white' : '#f6f6f6'}
|
|
/>
|
|
<BlockActionMenu showAdd showDelay showBlock showClone enableDelete={!selected} actionHandler={actionHandler} />
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default memo(EventBlockInner);
|
|
|
|
function EndActionIcon(props: { action: EndAction; className: string }) {
|
|
const { action, className } = props;
|
|
if (action === EndAction.LoadNext) {
|
|
return <IoPlaySkipForwardCircle className={className} />;
|
|
}
|
|
if (action === EndAction.PlayNext) {
|
|
return <IoPlayForwardCircle className={className} />;
|
|
}
|
|
if (action === EndAction.Stop) {
|
|
return <IoStopCircle className={className} />;
|
|
}
|
|
return <IoPlayCircle className={className} />;
|
|
}
|
|
|
|
function TimerIcon(props: { type: TimerType; className: string }) {
|
|
const { type, className } = props;
|
|
if (type === TimerType.CountUp) {
|
|
return <IoCaretUpCircle className={className} />;
|
|
}
|
|
if (type === TimerType.Clock) {
|
|
return <IoTime className={className} />;
|
|
}
|
|
return <IoCaretDownCircle className={className} />;
|
|
}
|