* chore: upgrade local build documentation

* style: keep scrolling event in screen

* feat: delay is time entry

* refactor: remove unused

* refactor: remove unused

* refactor: batch store updates

* refactor: virtually remove cap on events

* refactor: style and behaviour tweaks to event block

* style: tweaks on schedules

* chore: remove sentry from server

* style: reorder menu

* chore: update docs
This commit is contained in:
Carlos Valente
2023-04-14 10:13:46 +02:00
committed by GitHub
parent 94a1369d64
commit 770d12888d
48 changed files with 531 additions and 376 deletions
@@ -144,8 +144,8 @@ $playback-width: 450px;
.eventEditor {
border-radius: 8px 8px 0 0;
background-color: $bg-container-l2;
box-shadow: rgba(0, 0, 0, 0.6) 0 3px 6px 6px;
border-top: 1px solid $white-10;
box-shadow: rgba(0, 0, 0, 0.35) 0 3px 6px 6px;
border-top: 1px solid $white-20;
position: absolute;
bottom: 0;
width: 100vw;
@@ -79,7 +79,7 @@
display: block;
@include input-label;
.delayLabel {
&.delayLabel {
color: $ontime-delay-text;
}
@@ -5,7 +5,8 @@ import { millisToString } from 'ontime-utils';
import TimeInput from '../../../common/components/input/time-input/TimeInput';
import { useEventAction } from '../../../common/hooks/useEventAction';
import { millisToMinutes } from '../../../common/utils/dateConfig';
import { millisToDelayString } from '../../../common/utils/dateConfig';
import { cx } from '../../../common/utils/styleUtils';
import { calculateDuration, TimeEntryField, validateEntry } from '../../../common/utils/timesManager';
import style from '../EventEditor.module.scss';
@@ -71,18 +72,15 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
updateEvent(newEventData);
};
const delayed = delay !== 0;
const addedTime = delayed ? `${delay >= 0 ? '+' : '-'} ${millisToMinutes(Math.abs(delay))} minutes` : null;
const newStart = delayed ? `New start ${millisToString(timeStart + delay)}` : null;
const newEnd = delayed ? `New end ${millisToString(timeEnd + delay)}` : null;
const delayTime = delay !== 0 ? millisToDelayString(delay) : null;
const startLabel = delayTime ? `New start ${millisToString(timeStart + delay)}` : 'Start time';
const endLabel = delayTime ? `New end ${millisToString(timeEnd + delay)}` : 'End time';
const inputTimeLabels = cx([style.inputLabel, delayTime ? style.delayLabel : null]);
return (
<div className={style.timeOptions}>
<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>
<label className={inputTimeLabels}>{startLabel}</label>
<TimeInput
name='timeStart'
submitHandler={handleSubmit}
@@ -92,10 +90,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
placeholder='Start'
warning={warning.start}
/>
<label className={style.inputLabel}>
End time {delayed && <span className={style.delayLabel}>{addedTime}</span>}
{delayed && <div className={style.delayLabel}>{newEnd}</div>}
</label>
<label className={inputTimeLabels}>{endLabel}</label>
<TimeInput
name='timeEnd'
submitHandler={handleSubmit}
+26 -26
View File
@@ -8,8 +8,8 @@ import { IoExtensionPuzzle } from '@react-icons/all-files/io5/IoExtensionPuzzle'
import { IoExtensionPuzzleOutline } from '@react-icons/all-files/io5/IoExtensionPuzzleOutline';
import { IoScan } from '@react-icons/all-files/io5/IoScan';
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
import { downloadRundown } from '../../common/api/ontimeApi';
import { downloadRundown } from '../../common/api/ontimeApi';
import QuitIconBtn from '../../common/components/buttons/QuitIconBtn';
import TooltipActionBtn from '../../common/components/buttons/TooltipActionBtn';
import useElectronEvent from '../../common/hooks/useElectronEvent';
@@ -130,31 +130,6 @@ export default function MenuBar(props: MenuBarProps) {
isDisabled={!isElectron}
/>
<div className={style.gap} />
<TooltipActionBtn
{...buttonStyle}
icon={<FiHelpCircle />}
clickHandler={() => actionHandler('help')}
tooltip='Help'
aria-label='Help'
/>
<TooltipActionBtn
{...buttonStyle}
icon={<IoSettingsOutline />}
className={isSettingsOpen ? style.open : ''}
clickHandler={onSettingsOpen}
tooltip='Settings'
aria-label='Settings'
/>
<div className={style.gap} />
<TooltipActionBtn
{...buttonStyle}
icon={isIntegrationOpen ? <IoExtensionPuzzle /> : <IoExtensionPuzzleOutline />}
className={isIntegrationOpen ? style.open : ''}
clickHandler={onIntegrationOpen}
tooltip='Integrations'
aria-label='Integrations'
/>
<div className={style.gap} />
<TooltipActionBtn
{...buttonStyle}
icon={<FiUpload />}
@@ -170,6 +145,31 @@ export default function MenuBar(props: MenuBarProps) {
tooltip='Export showfile'
aria-label='Export showfile'
/>
<div className={style.gap} />
<TooltipActionBtn
{...buttonStyle}
icon={isIntegrationOpen ? <IoExtensionPuzzle /> : <IoExtensionPuzzleOutline />}
className={isIntegrationOpen ? style.open : ''}
clickHandler={onIntegrationOpen}
tooltip='Integrations'
aria-label='Integrations'
/>
<TooltipActionBtn
{...buttonStyle}
icon={<IoSettingsOutline />}
className={isSettingsOpen ? style.open : ''}
clickHandler={onSettingsOpen}
tooltip='Settings'
aria-label='Settings'
/>
<div className={style.gap} />
<TooltipActionBtn
{...buttonStyle}
icon={<FiHelpCircle />}
clickHandler={() => actionHandler('help')}
tooltip='Help'
aria-label='Help'
/>
</VStack>
);
}
+2 -1
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { closestCenter, DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { OntimeRundown, SupportedEvent } from 'ontime-types';
import { OntimeRundown, Playback, SupportedEvent } from 'ontime-types';
import { useEventAction } from '../../common/hooks/useEventAction';
import { useRundownEditor } from '../../common/hooks/useSocket';
@@ -242,6 +242,7 @@ export default function Rundown(props: RundownProps) {
previousEnd={previousEnd}
previousEventId={previousEventId}
playback={isSelected ? featureData.playback : undefined}
isRolling={featureData.playback === Playback.Roll}
/>
{((showQuickEntry && index === cursor) || isLast) && (
<QuickAddBlock
@@ -26,10 +26,23 @@ interface RundownEntryProps {
previousEnd: number;
previousEventId?: string;
playback?: Playback; // we only care about this if this event is playing
isRolling: boolean; // we need to know even if not related to this event
}
export default function RundownEntry(props: RundownEntryProps) {
const { index, eventIndex, data, selected, hasCursor, next, delay, previousEnd, previousEventId, playback } = props;
const {
index,
eventIndex,
data,
selected,
hasCursor,
next,
delay,
previousEnd,
previousEventId,
playback,
isRolling,
} = props;
const { emitError } = useEmitLog();
const { addEvent, updateEvent, deleteEvent } = useEventAction();
@@ -149,6 +162,7 @@ export default function RundownEntry(props: RundownEntryProps) {
selected={selected}
hasCursor={hasCursor}
playback={playback}
isRolling={isRolling}
actionHandler={actionHandler}
/>
);
@@ -23,7 +23,7 @@ $block-cursor-color: $blue-400;
}
@mixin block-spacing() {
padding: 4px 8px 4px 2px;
padding-right: 8px;
gap: 2px;
}
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef } from 'react';
import { useEffect, useRef } from 'react';
import { Button, HStack } from '@chakra-ui/react';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
@@ -9,7 +9,6 @@ import { OntimeDelay, OntimeEvent } from 'ontime-types';
import DelayInput from '../../../common/components/input/delay-input/DelayInput';
import { useEventAction } from '../../../common/hooks/useEventAction';
import { millisToMinutes } from '../../../common/utils/dateConfig';
import { cx } from '../../../common/utils/styleUtils';
import BlockActionMenu from '../event-block/composite/BlockActionMenu';
import { EventItemActions } from '../RundownEntry';
@@ -32,7 +31,7 @@ interface DelayBlockProps {
export default function DelayBlock(props: DelayBlockProps) {
const { data, hasCursor, actionHandler } = props;
const { applyDelay, updateEvent, deleteEvent } = useEventAction();
const { applyDelay, deleteEvent } = useEventAction();
const handleRef = useRef<null | HTMLSpanElement>(null);
const {
@@ -65,28 +64,14 @@ export default function DelayBlock(props: DelayBlockProps) {
deleteEvent(data.id);
};
const delaySubmitHandler = useCallback(
(value: number) => {
const newEvent = {
id: data.id,
duration: value * 60000,
};
updateEvent(newEvent);
},
[data.id, updateEvent],
);
const blockClasses = cx([style.delay, hasCursor ? style.hasCursor : null]);
const delayValue = data.duration != null ? millisToMinutes(data.duration) : undefined;
return (
<div className={blockClasses} ref={setNodeRef} style={dragStyle}>
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
<IoReorderTwo />
</span>
<DelayInput value={delayValue} submitHandler={delaySubmitHandler} />
<DelayInput eventId={data.id} duration={data.duration} />
<HStack spacing='8px' className={style.actionOverlay}>
<Button onClick={applyDelayHandler} size='sm' leftIcon={<IoCheckmark />} variant='ontime-subtle-white'>
Apply
@@ -22,10 +22,30 @@ $skip-opacity: 0.1;
padding-right: $block-clearance;
gap: 2px;
@mixin declare-overrides(){
--status-color-override: #{$gray-200};
--status-color-active-override: #{$green-400};
}
&.selected {
background-color: $gray-1350;
}
&.play {
background-color: $green-700;
@include declare-overrides;
}
&.roll {
background-color: $blue-700;
@include declare-overrides;
}
&.pause {
background-color: $orange-700;
@include declare-overrides;
}
&.hasCursor {
outline: 1px solid $block-cursor-color;
}
@@ -146,6 +166,7 @@ $skip-opacity: 0.1;
justify-content: flex-end;
align-items: center;
gap: 8px;
color: var(--status-color-override, $gray-500);
.tag {
padding-top: 1px;
@@ -156,12 +177,12 @@ $skip-opacity: 0.1;
.statusIcon {
width: 16px;
height: 16px;
color: $gray-500;
}
.statusIcon.active {
color: $active-indicator;
color: var(--status-color-active-override, $active-indicator);
}
.statusIcon.disabled {
color: $gray-1000;
}
@@ -33,6 +33,7 @@ interface EventBlockProps {
selected: boolean;
hasCursor: boolean;
playback?: Playback;
isRolling: boolean;
actionHandler: (
action: EventItemActions,
payload?:
@@ -65,6 +66,7 @@ export default function EventBlock(props: EventBlockProps) {
selected,
hasCursor,
playback,
isRolling,
actionHandler,
} = props;
@@ -128,6 +130,7 @@ export default function EventBlock(props: EventBlockProps) {
style.eventBlock,
skip ? style.skip : null,
selected ? style.selected : null,
playback ? style[playback] : null,
hasCursor ? style.hasCursor : null,
]);
@@ -157,6 +160,7 @@ export default function EventBlock(props: EventBlockProps) {
skip={skip}
selected={selected}
playback={playback}
isRolling={isRolling}
actionHandler={actionHandler}
/>
)}
@@ -1,7 +1,7 @@
import { memo, useCallback, useEffect, useState } from 'react';
import { Tooltip } from '@chakra-ui/react';
import { IoCaretDown } from '@react-icons/all-files/io5/IoCaretDown';
import { IoCaretUp } from '@react-icons/all-files/io5/IoCaretUp';
import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown';
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
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';
@@ -49,6 +49,7 @@ interface EventBlockInnerProps {
skip: boolean;
selected: boolean;
playback?: Playback;
isRolling: boolean;
actionHandler: (action: EventItemActions, payload?: any) => void;
}
@@ -70,6 +71,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
skip = false,
selected,
playback,
isRolling,
actionHandler,
} = props;
@@ -89,17 +91,18 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
}
}, [eventId, isOpen, removeOpenEvent, setOpenEvent]);
const eventIsPlaying = selected && playback === Playback.Play;
const eventIsPlaying = playback === Playback.Play;
const eventIsPaused = playback === Playback.Pause;
const playBtnStyles = { _hover: {} };
if (!skip && eventIsPlaying) {
playBtnStyles._hover = { bg: '#c05621' };
playBtnStyles._hover = { bg: '#c05621' }; // $ontime-paused
} else if (!skip && !eventIsPlaying) {
playBtnStyles._hover = {};
}
return !renderInner ? null : (
<>
<EventBlockPlayback eventId={eventId} skip={skip} isPlaying={eventIsPlaying} selected={selected} />
<EventBlockTimers
eventId={eventId}
timeStart={timeStart}
@@ -109,6 +112,14 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
previousEnd={previousEnd}
/>
<EditableBlockTitle title={title} eventId={eventId} placeholder='Event title' className={style.eventTitle} />
<EventBlockPlayback
eventId={eventId}
skip={skip}
isPlaying={eventIsPlaying}
isPaused={eventIsPaused}
selected={selected}
disablePlayback={skip || isRolling}
/>
<div className={style.statusElements}>
<span className={style.eventNote}>{note}</span>
<div className={selected ? style.progressBg : `${style.progressBg} ${style.hidden}`}>
@@ -175,10 +186,10 @@ function EndActionIcon(props: { action: EndAction; className: string }) {
function TimerIcon(props: { type: TimerType; className: string }) {
const { type, className } = props;
if (type === TimerType.CountUp) {
return <IoCaretUp className={className} />;
return <IoArrowUp className={className} />;
}
if (type === TimerType.Clock) {
return <IoTime className={className} />;
}
return <IoCaretDown className={className} />;
return <IoArrowDown className={className} />;
}
@@ -1,6 +1,6 @@
import { memo } from 'react';
import { IoPause } from '@react-icons/all-files/io5/IoPause';
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
import { IoPlayOutline } from '@react-icons/all-files/io5/IoPlayOutline';
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';
@@ -16,6 +16,13 @@ const blockBtnStyle = {
size: 'sm',
};
type StyleVariant = {
'aria-label': string;
tooltip: string;
backgroundColor: string;
_hover: { backgroundColor?: string };
};
const tooltipProps = {
openDelay: tooltipDelayMid,
};
@@ -24,17 +31,55 @@ interface EventBlockPlaybackProps {
eventId: string;
skip: boolean;
isPlaying: boolean;
isPaused: boolean;
selected: boolean;
disablePlayback: boolean;
}
const EventBlockPlayback = (props: EventBlockPlaybackProps) => {
const { eventId, skip, isPlaying, selected } = props;
const { eventId, skip, isPlaying, isPaused, selected, disablePlayback } = props;
const { updateEvent } = useEventAction();
const toggleSkip = () => {
updateEvent({ id: eventId, skip: !skip });
};
const actionHandler = () => {
// is playing -> pause
// is paused -> continue
// otherwise -> start
if (isPlaying) {
setEventPlayback.pause();
} else if (isPaused) {
setEventPlayback.start();
} else {
setEventPlayback.startEvent(eventId);
}
};
const buttonVariant: Partial<StyleVariant> = {};
if (isPaused) {
// continue
buttonVariant['aria-label'] = 'Continue event';
buttonVariant.tooltip = 'Continue event';
buttonVariant.backgroundColor = '#339E4E';
buttonVariant._hover = { backgroundColor: '#339E4Eee' };
} else if (isPlaying) {
// pause
buttonVariant['aria-label'] = 'Pause event';
buttonVariant.tooltip = 'Pause event';
buttonVariant.backgroundColor = '#c05621';
buttonVariant._hover = { backgroundColor: '#c05621ee' };
} else {
// start
buttonVariant['aria-label'] = 'Start event';
buttonVariant.tooltip = 'Start event';
if (!disablePlayback) {
buttonVariant._hover = { backgroundColor: '#339E4E' };
}
}
return (
<div className={style.playbackActions}>
<TooltipActionBtn
@@ -55,7 +100,7 @@ const EventBlockPlayback = (props: EventBlockPlaybackProps) => {
aria-label='Load event'
tooltip='Load event'
icon={<IoReload className={style.flip} />}
isDisabled={skip}
isDisabled={disablePlayback}
{...tooltipProps}
{...blockBtnStyle}
clickHandler={() => setEventPlayback.loadEvent(eventId)}
@@ -65,13 +110,12 @@ const EventBlockPlayback = (props: EventBlockPlaybackProps) => {
variant='ontime-subtle-white'
aria-label='Start event'
tooltip='Start event'
icon={isPlaying ? <IoPlay /> : <IoPlayOutline />}
isDisabled={skip}
icon={!isPlaying ? <IoPlay /> : <IoPause />}
isDisabled={disablePlayback}
{...tooltipProps}
{...blockBtnStyle}
clickHandler={() => setEventPlayback.startEvent(eventId)}
backgroundColor={isPlaying ? '#58A151' : undefined}
_hover={{ backgroundColor: isPlaying ? '#58A151' : undefined }}
{...buttonVariant}
clickHandler={actionHandler}
tabIndex={-1}
/>
</div>
@@ -1,12 +1,10 @@
@use '../../../../theme/v2Styles' as *;
.progressBar {
// layout
height: 100%;
width: 0;
border-radius: 1px 0 0 1px;
// animations
transition: 1s linear;
transition-property: width;
@@ -14,6 +12,10 @@
background-color: $playback-start;
}
&.overtime {
background-color: $playback-negative;
}
&.pause {
background-color: $ontime-paused;
}
@@ -21,8 +23,4 @@
&.roll {
background-color: $ontime-roll;
}
&.overtime {
background-color: $playback-negative;
}
}
@@ -4,7 +4,7 @@ import { millisToString } from 'ontime-utils';
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
import { useEventAction } from '../../../../common/hooks/useEventAction';
import { millisToMinutes } from '../../../../common/utils/dateConfig';
import { millisToDelayString } from '../../../../common/utils/dateConfig';
import { calculateDuration, TimeEntryField, validateEntry } from '../../../../common/utils/timesManager';
import style from '../EventBlock.module.scss';
@@ -64,8 +64,9 @@ const EventBlockTimers = (props: EventBlockTimerProps) => {
[timeEnd, timeStart],
);
const delayTime = `${delay >= 0 ? '+' : '-'} ${millisToMinutes(Math.abs(delay))}`;
const newTime = millisToString(timeStart + delay);
const delayedStart = Math.max(0, timeStart + delay);
const newTime = millisToString(delayedStart);
const delayTime = delay !== 0 ? millisToDelayString(delay) : null;
return (
<div className={style.eventTimers}>
@@ -94,13 +95,14 @@ const EventBlockTimers = (props: EventBlockTimerProps) => {
submitHandler={handleSubmit}
validationHandler={handleValidation}
time={duration}
delay={0}
placeholder='Duration'
previousEnd={previousEnd}
warning={warning.duration}
/>
{delay !== 0 && delay !== null && (
{delayTime && (
<div className={style.delayNote}>
{`${delayTime} minutes`}
{delayTime}
<br />
{`New start: ${newTime}`}
</div>
@@ -10,11 +10,7 @@ import {
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
horizontalListSortingStrategy,
SortableContext,
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable';
import { horizontalListSortingStrategy, SortableContext, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
import PropTypes from 'prop-types';
import { TableSettingsContext } from '../../common/context/TableSettingsContext';
@@ -148,7 +144,7 @@ export default function OntimeTable({ tableData, userFields, selectedId, handleU
if (el) {
el.scrollIntoView({
behavior: 'smooth',
block: 'start',
block: 'center',
inline: 'nearest',
});
}
@@ -119,7 +119,7 @@
grid-area: schedule;
overflow: hidden;
height: 100%;
margin-left: 16px;
margin-left: clamp(16px, 5vw, 64px);;
}
.schedule-nav-container {
@@ -131,6 +131,8 @@
grid-area: info;
display: flex;
gap: max(1vw, 16px);
align-self: flex-end;
overflow: hidden;
&__message {
font-size: clamp(16px, 1.5vw, 24px);
@@ -141,7 +143,7 @@
}
.qr {
margin-left: auto;
margin-left: clamp(16px, 5vw, 64px);;
padding: 4px;
background-color: white;
}
@@ -99,7 +99,8 @@
grid-area: info;
display: flex;
gap: max(1vw, 16px);
min-height: max(calc(100vh / 15), 128px);
align-self: flex-end;
overflow: hidden;
&__message {
font-size: clamp(16px, 1.5vw, 24px);
@@ -110,7 +111,7 @@
}
.qr {
margin-left: auto;
margin-left: clamp(16px, 5vw, 64px);;
padding: 4px;
background-color: white;
}