* style: labels on added time

* style: remove mentions of PiP

* refactor: unify usage of ms for timers

* refactor: create events with 0 duration

* style: several small tweaks

* refactor: keep block when applying delays

* feat: blocks have titles

* style: improvements in time entry warnings

* style: override progress bar styles

* style: prevent overflow

* feat: show character count in editor

* refactor: provide initial payload

* refactor: lower test boundary

* refactor: get colour from swatches

* style: rename title block

* chore: version bump
This commit is contained in:
Carlos Valente
2023-04-05 22:08:13 +02:00
committed by GitHub
parent 0a68377b82
commit 5e481d49da
57 changed files with 556 additions and 276 deletions
@@ -6,6 +6,7 @@
box-sizing: content-box;
display: grid;
grid-template-columns: 32px 1fr auto;
gap: 8px;
align-items: center;
height: $secondary-block-height;
@@ -5,6 +5,7 @@ import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
import { OntimeBlock, OntimeEvent } from 'ontime-types';
import { cx } from '../../../common/utils/styleUtils';
import EditableBlockTitle from '../common/EditableBlockTitle';
import BlockActionMenu from '../event-block/composite/BlockActionMenu';
import { EventItemActions } from '../RundownEntry';
@@ -57,6 +58,7 @@ export default function BlockBlock(props: BlockBlockProps) {
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
<IoReorderTwo />
</span>
<EditableBlockTitle title={data.title} eventId={data.id} placeholder='Block title' />
<BlockActionMenu className={style.actionOverlay} showAdd showDelay enableDelete actionHandler={actionHandler} />
</div>
);
@@ -0,0 +1,53 @@
import { useCallback, useEffect, useState } from 'react';
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/react';
import { useEventAction } from '../../../common/hooks/useEventAction';
import { cx } from '../../../common/utils/styleUtils';
import style from './TitleEditor.module.scss';
interface TitleEditorProps {
title: string;
eventId: string;
placeholder: string;
className?: string;
}
export default function EditableBlockTitle(props: TitleEditorProps) {
const { title, eventId, placeholder, className } = props;
const [blockTitle, setBlockTitle] = useState<string>(title || '');
const { updateEvent } = useEventAction();
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 classes = cx([className, style.eventTitle, !blockTitle ? style.noTitle : null]);
return (
<Editable
variant='ontime'
value={blockTitle}
className={classes}
placeholder={placeholder}
onChange={(value) => setBlockTitle(value)}
onSubmit={(value) => handleTitle(value)}
>
<EditablePreview className={style.preview} />
<EditableInput />
</Editable>
);
}
@@ -0,0 +1,16 @@
@use '../../../theme/v2Styles' as *;
.titleEditor {
display: block;
font-size: 18px;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.noTitle {
.preview {
opacity: $opacity-disabled;
}
}
@@ -87,18 +87,6 @@
.eventTitle {
grid-area: title;
display: block;
font-size: 18px;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.noTitle {
.preview {
opacity: $opacity-disabled;
}
}
}
.eventActions {
@@ -158,6 +146,7 @@
gap: 8px;
.tag {
padding-top: 1px;
font-size: 0.55em;
color: $active-indicator;
}
@@ -1,5 +1,5 @@
import { memo, useCallback, useEffect, useState } from 'react';
import { Editable, EditableInput, EditablePreview, Tooltip } from '@chakra-ui/react';
import { 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';
@@ -17,10 +17,10 @@ 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 EditableBlockTitle from '../common/EditableBlockTitle';
import { EventItemActions } from '../RundownEntry';
import BlockActionMenu from './composite/BlockActionMenu';
@@ -78,38 +78,14 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
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();
@@ -173,17 +149,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
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>
<EditableBlockTitle title={title} eventId={eventId} placeholder='Event title' className={style.eventTitle} />
<div className={style.statusElements}>
<span className={style.eventNote}>{note}</span>
<div className={selected ? style.progressBg : `${style.progressBg} ${style.hidden}`}>
@@ -1,18 +1,25 @@
import { useCallback } from 'react';
import { useCallback, useState } from 'react';
import { millisToString } from 'ontime-utils';
import PropTypes from 'prop-types';
import { useEmitLog } from '@/common/stores/logger';
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
import { millisToMinutes } from '../../../../common/utils/dateConfig';
import { validateEntry } from '../../../../common/utils/timesManager';
import { TimeEntryField, validateEntry } from '../../../../common/utils/timesManager';
import { EventItemActions } from '../../RundownEntry';
import style from '../EventBlock.module.scss';
export default function EventBlockTimers(props) {
interface EventBlockTimerProps {
timeStart: number;
timeEnd: number;
duration: number;
delay: number;
actionHandler: (action: EventItemActions, payload?: any) => void;
previousEnd: number;
}
export default function EventBlockTimers(props: EventBlockTimerProps) {
const { timeStart, timeEnd, duration, delay, actionHandler, previousEnd } = props;
const { emitWarning } = useEmitLog();
const [warning, setWarnings] = useState({ start: '', end: '', duration: '' });
const delayTime = `${delay >= 0 ? '+' : '-'} ${millisToMinutes(Math.abs(delay))}`;
const newTime = millisToString(timeStart + delay);
@@ -24,21 +31,19 @@ export default function EventBlockTimers(props) {
* @return {boolean}
*/
const handleValidation = useCallback(
(field, value) => {
(field: TimeEntryField, value: number) => {
const valid = validateEntry(field, value, timeStart, timeEnd);
if (valid.catch) {
emitWarning(`Time Input Warning: ${valid.catch}`);
}
setWarnings((prev) => ({ ...prev, ...valid.warnings }));
return valid.value;
},
[emitWarning, timeEnd, timeStart]
[timeEnd, timeStart],
);
const handleSubmit = useCallback(
(field, value) => {
(field: TimeEntryField, value: number) => {
actionHandler('update', { field, value });
},
[actionHandler]
[actionHandler],
);
return (
@@ -51,6 +56,7 @@ export default function EventBlockTimers(props) {
delay={delay}
placeholder='Start'
previousEnd={previousEnd}
warning={warning.start}
/>
<TimeInput
name='timeEnd'
@@ -60,6 +66,7 @@ export default function EventBlockTimers(props) {
delay={delay}
placeholder='End'
previousEnd={previousEnd}
warning={warning.end}
/>
<TimeInput
name='durationOverride'
@@ -68,6 +75,7 @@ export default function EventBlockTimers(props) {
time={duration}
placeholder='Duration'
previousEnd={previousEnd}
warning={warning.duration}
/>
{delay !== 0 && delay !== null && (
<div className={style.delayNote}>
@@ -79,12 +87,3 @@ export default function EventBlockTimers(props) {
</div>
);
}
EventBlockTimers.propTypes = {
timeStart: PropTypes.number,
timeEnd: PropTypes.number,
duration: PropTypes.number,
delay: PropTypes.number,
actionHandler: PropTypes.func,
previousEnd: PropTypes.number,
};
@@ -15,6 +15,7 @@
gap: 10%;
.quickBtn {
font-weight: 400;
width: auto;
padding: 0 32px;
}