refactor: gather group metadata

This commit is contained in:
Carlos Valente
2025-03-28 19:16:38 +01:00
committed by arc-alex
parent 84fac739f0
commit fc4c1bf22e
48 changed files with 1044 additions and 751 deletions
@@ -97,9 +97,7 @@ export const useEventAction = () => {
linkPrevious: options?.linkPrevious ?? linkPrevious,
};
if (applicationOptions.linkPrevious && applicationOptions?.lastEventId) {
newEvent.linkStart = applicationOptions.lastEventId;
} else if (applicationOptions?.lastEventId) {
if (applicationOptions?.lastEventId) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know this is a value
const rundownData = queryClient.getQueryData<Rundown>(RUNDOWN)!;
const previousEvent = rundownData.entries[applicationOptions.lastEventId];
@@ -109,9 +107,8 @@ export const useEventAction = () => {
}
// Override event with options from editor settings
if (applicationOptions.defaultPublic) {
newEvent.isPublic = true;
}
newEvent.linkStart = applicationOptions.linkPrevious;
newEvent.isPublic = applicationOptions.defaultPublic;
if (newEvent.duration === undefined && newEvent.timeEnd === undefined) {
newEvent.duration = parseUserTime(defaultDuration);
@@ -263,7 +260,7 @@ export const useEventAction = () => {
newEvent.duration = value === '' ? undefined : calculateNewValue();
} else if (field === 'timeStart') {
// an empty values means we should link to the previous
newEvent.linkStart = value === '' ? 'true' : null;
newEvent.linkStart = value === '';
newEvent.timeStart = value === '' ? undefined : calculateNewValue();
}
} else {
@@ -15,7 +15,8 @@ describe('cloneEvent()', () => {
timeEnd: 10,
timerType: TimerType.CountDown,
timeStrategy: TimeStrategy.LockEnd,
linkStart: null,
currentBlock: 'test',
linkStart: false,
countToEnd: false,
endAction: EndAction.None,
isPublic: false,
@@ -45,6 +46,7 @@ describe('cloneEvent()', () => {
timeEnd: original.timeEnd,
timerType: original.timerType,
timeStrategy: original.timeStrategy,
currentBlock: 'test',
countToEnd: original.countToEnd,
linkStart: original.linkStart,
endAction: original.endAction,
@@ -0,0 +1,20 @@
import { IoAdd } from 'react-icons/io5';
import { Button } from '@chakra-ui/react';
import style from './Empty.module.scss';
interface BlockEmptyProps {
handleAddNew: () => void;
}
export default function BlockEmpty(props: BlockEmptyProps) {
const { handleAddNew } = props;
return (
<div className={style.empty}>
<Button size='sm' onClick={handleAddNew} variant='ontime-filled' leftIcon={<IoAdd />}>
Create Event
</Button>
</div>
);
}
@@ -0,0 +1,4 @@
.empty {
padding-block: 1.5rem;
text-align: center;
}
@@ -23,15 +23,6 @@
align-self: center;
}
.alignCenter {
text-align: center;
flex-direction: column;
.spaceTop {
margin-top: 1.5rem;
}
}
.spacer {
min-height: 50vh;
}
+88 -66
View File
@@ -5,23 +5,19 @@ import { useHotkeys } from '@mantine/hooks';
import {
type EntryId,
type MaybeString,
type PlayableEvent,
type Rundown,
isOntimeBlock,
isOntimeEvent,
isPlayableEvent,
Playback,
SupportedEvent,
} from 'ontime-types';
import {
checkIsNextDay,
getFirstNormal,
getLastNormal,
getNextBlockNormal,
getNextNormal,
getPreviousBlockNormal,
getPreviousNormal,
isNewLatest,
reorderArray,
} from 'ontime-utils';
@@ -32,7 +28,10 @@ import { AppMode, useAppMode } from '../../common/stores/appModeStore';
import { useEntryCopy } from '../../common/stores/entryCopyStore';
import { cloneEvent } from '../../common/utils/eventsManager';
import BlockBlock from './block-block/BlockBlock';
import QuickAddBlock from './quick-add-block/QuickAddBlock';
import BlockEmpty from './BlockEmpty';
import { makeRundownMetadata } from './rundown.utils';
import RundownEmpty from './RundownEmpty';
import { useEventSelection } from './useEventSelection';
@@ -264,20 +263,11 @@ export default function Rundown({ data }: RundownProps) {
return <RundownEmpty handleAddNew={() => insertAtId(SupportedEvent.Event, cursor)} />;
}
// last event is used to calculate relative timings
let lastEvent: PlayableEvent | null = null; // used by indicators
let thisEvent: PlayableEvent | null = null;
// previous entry is used to infer position in the rundown for new events
let previousEntryId: MaybeString = null;
let thisId: MaybeString = null;
let eventIndex = 0;
// all events before the current selected are in the past
let isPast = Boolean(featureData?.selectedEventId);
let isNextDay = false;
let totalGap = 0;
// 1. gather presentation options
const isEditMode = appMode === AppMode.Edit;
let isLinkedToLoaded = true; //check if the event can link all the way back to the currently playing event
// 2. initialise rundown metadata
const process = makeRundownMetadata(featureData?.selectedEventId);
return (
<div className={style.rundownContainer} ref={scrollRef} data-testid='rundown'>
@@ -292,64 +282,96 @@ export default function Rundown({ data }: RundownProps) {
if (!entry) {
return null;
}
if (index === 0) {
eventIndex = 0;
}
isNextDay = false;
previousEntryId = thisId;
thisId = entryId;
if (isOntimeEvent(entry)) {
// event indexes are 1 based in frontend
eventIndex++;
lastEvent = thisEvent;
if (isPlayableEvent(entry)) {
isNextDay = checkIsNextDay(entry, lastEvent);
if (!isPast) {
totalGap += entry.gap;
// We also include countToEnd in this test as the behavior of a linked event coming after a countToEnd is simelar to an unlinked event
isLinkedToLoaded = isLinkedToLoaded && entry.linkStart !== null && !lastEvent?.countToEnd;
}
if (isNewLatest(entry, lastEvent)) {
// populate previous entry
thisEvent = entry;
}
}
}
const rundownMeta = process(entry);
const isFirst = index === 0;
const isLast = index === order.length - 1;
const isLoaded = featureData?.selectedEventId === entry.id;
const isNext = featureData?.nextEventId === entry.id;
const hasCursor = entry.id === cursor;
if (isLoaded) {
isPast = false;
}
return (
<Fragment key={entry.id}>
{isEditMode && (hasCursor || isFirst) && <QuickAddBlock previousEventId={previousEntryId} />}
<div className={style.entryWrapper} data-testid={`entry-${eventIndex}`}>
{isOntimeEvent(entry) && <div className={style.entryIndex}>{eventIndex}</div>}
<div className={style.entry} key={entry.id} ref={hasCursor ? cursorRef : undefined}>
<RundownEntry
type={entry.type}
isPast={isPast}
eventIndex={eventIndex}
data={entry}
loaded={isLoaded}
hasCursor={hasCursor}
isNext={isNext}
previousEntryId={previousEntryId}
previousEventId={lastEvent?.id}
playback={isLoaded ? featureData.playback : undefined}
isRolling={featureData.playback === Playback.Roll}
isNextDay={isNextDay}
totalGap={totalGap}
isLinkedToLoaded={isLinkedToLoaded}
/>
{isEditMode && (hasCursor || isFirst) && (
<QuickAddBlock showBlocks previousEventId={rundownMeta.previousEntryId} />
)}
{isOntimeBlock(entry) ? (
<BlockBlock data={entry} hasCursor={hasCursor}>
{entry.events.length === 0 && (
<BlockEmpty handleAddNew={() => insertAtId(SupportedEvent.Event, cursor)} />
)}
{entry.events.map((eventId, nestedIndex) => {
const nestedEntry = entries[eventId];
const nestedRundownMeta = process(nestedEntry);
const isFirstInGroup = nestedIndex === 0;
const isLastInGroup = nestedIndex === entry.events.length - 1;
const hasNestedCursor = nestedEntry.id === cursor;
if (!isOntimeEvent(nestedEntry)) {
return null;
}
return (
<Fragment key={nestedEntry.id}>
{isEditMode && (hasNestedCursor || isFirstInGroup) && (
<QuickAddBlock previousEventId={rundownMeta.previousEntryId} />
)}
<div
key={nestedEntry.id}
className={style.entryWrapper}
data-testid={`entry-${nestedRundownMeta.eventIndex}`}
>
<div className={style.entryIndex}>{nestedRundownMeta.eventIndex}</div>
<div className={style.entry} ref={hasNestedCursor ? cursorRef : undefined}>
<RundownEntry
key={nestedEntry.id}
type={nestedEntry.type}
isPast={nestedRundownMeta.isPast}
eventIndex={nestedRundownMeta.eventIndex}
data={nestedEntry}
loaded={nestedRundownMeta.isLoaded}
hasCursor={hasNestedCursor}
isNext={isNext}
previousEntryId={nestedRundownMeta.previousEntryId}
previousEventId={nestedRundownMeta.previousEvent?.id}
playback={nestedRundownMeta.isLoaded ? featureData.playback : undefined}
isRolling={featureData.playback === Playback.Roll}
isNextDay={nestedRundownMeta.isNextDay}
totalGap={nestedRundownMeta.totalGap}
isLinkedToLoaded={nestedRundownMeta.isLinkedToLoaded}
/>
</div>
</div>
{isEditMode && (hasNestedCursor || isLastInGroup) && (
<QuickAddBlock previousEventId={entry.id} />
)}
</Fragment>
);
})}
</BlockBlock>
) : (
<div className={style.entryWrapper} data-testid={`entry-${rundownMeta.eventIndex}`}>
{isOntimeEvent(entry) && <div className={style.entryIndex}>{rundownMeta.eventIndex}</div>}
<div className={style.entry} key={entry.id} ref={hasCursor ? cursorRef : undefined}>
<RundownEntry
type={entry.type}
isPast={rundownMeta.isPast}
eventIndex={rundownMeta.eventIndex}
data={entry}
loaded={rundownMeta.isLoaded}
hasCursor={hasCursor}
isNext={isNext}
previousEntryId={rundownMeta.previousEntryId}
previousEventId={rundownMeta.previousEvent?.id}
playback={rundownMeta.isLoaded ? featureData.playback : undefined}
isRolling={featureData.playback === Playback.Roll}
isNextDay={rundownMeta.isNextDay}
totalGap={rundownMeta.totalGap}
isLinkedToLoaded={rundownMeta.isLinkedToLoaded}
/>
</div>
</div>
</div>
{isEditMode && (hasCursor || isLast) && <QuickAddBlock previousEventId={entry.id} />}
)}
{isEditMode && (hasCursor || isLast) && <QuickAddBlock showBlocks previousEventId={entry.id} />}
</Fragment>
);
})}
@@ -3,7 +3,7 @@ import { Button } from '@chakra-ui/react';
import Empty from '../../common/components/state/Empty';
import style from './Rundown.module.scss';
import style from './Empty.module.scss';
interface RundownEmptyProps {
handleAddNew: () => void;
@@ -13,9 +13,9 @@ export default function RundownEmpty(props: RundownEmptyProps) {
const { handleAddNew } = props;
return (
<div className={style.alignCenter}>
<Empty style={{ marginTop: '7vh' }} />
<Button onClick={handleAddNew} variant='ontime-filled' className={style.spaceTop} leftIcon={<IoAdd />}>
<div className={style.empty}>
<Empty style={{ marginTop: '7vh', marginBottom: '1.5rem' }} />
<Button onClick={handleAddNew} variant='ontime-filled' leftIcon={<IoAdd />}>
Create Event
</Button>
</div>
@@ -1,6 +1,5 @@
import { useCallback } from 'react';
import {
isOntimeBlock,
isOntimeDelay,
isOntimeEvent,
MaybeString,
@@ -15,7 +14,6 @@ import useMemoisedFn from '../../common/hooks/useMemoisedFn';
import { useEmitLog } from '../../common/stores/logger';
import { cloneEvent } from '../../common/utils/eventsManager';
import BlockBlock from './block-block/BlockBlock';
import DelayBlock from './delay-block/DelayBlock';
import EventBlock from './event-block/EventBlock';
import { useEventSelection } from './useEventSelection';
@@ -193,14 +191,6 @@ export default function RundownEntry(props: RundownEntryProps) {
actionHandler={actionHandler}
/>
);
} else if (isOntimeBlock(data)) {
return (
<BlockBlock data={data} hasCursor={hasCursor}>
{data.events.map((eventId) => {
return <div key={eventId}>{eventId}</div>;
})}
</BlockBlock>
);
} else if (isOntimeDelay(data)) {
return <DelayBlock data={data} hasCursor={hasCursor} />;
}
@@ -2,19 +2,75 @@
.block {
@include block-styling;
background-color: $block-bg2;
overflow: hidden;
min-width: 34rem;
display: grid;
grid-template-columns: 2rem 1fr auto;
grid-template-areas:
'binder header'
'content content'
'footer footer';
align-items: center;
height: $secondary-block-height;
gap: 0.5rem;
&.hasCursor {
outline: 1px solid $block-cursor-color;
}
.binder {
grid-area: binder;
height: 100%;
background-color: $gray-1050; // to override inline
color: $section-white;
font-size: 1rem;
display: grid;
justify-content: center;
padding-top: 0.25rem;
}
.header {
grid-area: header;
padding-inline: 0.5rem;
background-color: $block-bg2;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.titleRow {
display: flex;
align-items: center;
gap: 0.5rem;
}
.metaRow {
display: flex;
gap: 3rem;
margin-bottom: 0.25rem;
}
.metaEntry {
font-size: calc(1rem - 3px);
width: 4.5em;
:first-child {
color: $label-gray;
}
}
.group {
background-color: color-mix(in srgb, var(--user-bg, $gray-1050) 10%, transparent 90%);
grid-area: content;
padding-right: 2px;
box-sizing: content-box;
}
.footer {
grid-area: footer;
background-color: var(--user-bg, $gray-1050) ;
height: 0.25rem;
}
}
.drag {
@@ -1,10 +1,12 @@
import { PropsWithChildren, useRef } from 'react';
import { IoReorderTwo } from 'react-icons/io5';
import { IoChevronDown, IoChevronUp, IoReorderTwo } from 'react-icons/io5';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { useSessionStorage } from '@mantine/hooks';
import { OntimeBlock } from 'ontime-types';
import { cx } from '../../../common/utils/styleUtils';
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
import { formatDuration, formatTime } from '../../../common/utils/time';
import EditableBlockTitle from '../common/EditableBlockTitle';
import style from './BlockBlock.module.scss';
@@ -16,7 +18,7 @@ interface BlockBlockProps {
export default function BlockBlock(props: PropsWithChildren<BlockBlockProps>) {
const { data, hasCursor, children } = props;
const [collapsed, setCollapsed] = useSessionStorage<boolean>({ key: `block-${data.id}`, defaultValue: false });
const handleRef = useRef<null | HTMLSpanElement>(null);
const {
@@ -35,16 +37,54 @@ export default function BlockBlock(props: PropsWithChildren<BlockBlockProps>) {
transition,
};
const blockClasses = cx([style.block, hasCursor ? style.hasCursor : null]);
const binderColours = data.colour && getAccessibleColour(data.colour);
return (
<div className={blockClasses} ref={setNodeRef} style={dragStyle}>
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
<IoReorderTwo />
</span>
<EditableBlockTitle title={data.title} eventId={data.id} placeholder='Block title' />
<button>+++</button>
<div>{children}</div>
<div
className={cx([style.block, hasCursor && style.hasCursor])}
ref={setNodeRef}
style={{
...(binderColours ? { '--user-bg': binderColours.backgroundColor } : {}),
...dragStyle,
}}
>
<div className={style.binder} style={{ ...binderColours }} tabIndex={-1}>
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
<IoReorderTwo />
</span>
</div>
<div className={style.header}>
<div className={style.titleRow}>
<EditableBlockTitle title={data.title} eventId={data.id} placeholder='Block title' />
<button onClick={() => setCollapsed((prev) => !prev)}>
{collapsed ? <IoChevronUp /> : <IoChevronDown />}
</button>
</div>
<div className={style.metaRow}>
<div className={style.metaEntry}>
<div>Start</div>
<div>{formatTime(data.startTime)}</div>
</div>
<div className={style.metaEntry}>
<div>End</div>
<div>{formatTime(data.endTime)}</div>
</div>
<div className={style.metaEntry}>
<div>Duration</div>
<div>{formatDuration(data.duration)}</div>
</div>
<div className={style.metaEntry}>
<div>Events</div>
<div>{data.numEvents}</div>
</div>
</div>
</div>
{!collapsed && (
<div className={style.group} style={binderColours ? { '--user-bg': binderColours.backgroundColor } : {}}>
{children}
</div>
)}
<div className={style.footer} style={binderColours ? { '--user-bg': binderColours.backgroundColor } : {}} />
</div>
);
}
@@ -12,7 +12,7 @@ import {
} from 'react-icons/io5';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { EndAction, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types';
import { EndAction, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types';
import { useContextMenu } from '../../../common/hooks/useContextMenu';
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
@@ -32,7 +32,7 @@ interface EventBlockProps {
timeEnd: number;
duration: number;
timeStrategy: TimeStrategy;
linkStart: MaybeString;
linkStart: boolean;
countToEnd: boolean;
eventIndex: number;
isPublic: boolean;
@@ -151,7 +151,7 @@ export default function EventBlock(props: EventBlockProps) {
onClick: () =>
actionHandler('update', {
field: 'linkStart',
value: linkStart ? null : 'true',
value: linkStart,
}),
},
{
@@ -11,7 +11,7 @@ import {
IoTime,
} from 'react-icons/io5';
import { Tooltip } from '@chakra-ui/react';
import { EndAction, MaybeString, Playback, TimerType, TimeStrategy } from 'ontime-types';
import { EndAction, Playback, TimerType, TimeStrategy } from 'ontime-types';
import { cx } from '../../../common/utils/styleUtils';
import { tooltipDelayMid } from '../../../ontimeConfig';
@@ -30,7 +30,7 @@ interface EventBlockInnerProps {
timeEnd: number;
duration: number;
timeStrategy: TimeStrategy;
linkStart: MaybeString;
linkStart: boolean;
countToEnd: boolean;
eventIndex: number;
isPublic: boolean;
@@ -22,7 +22,7 @@ export default function RundownEventEditor() {
return;
}
const selectedEventId = data.order.find((entryId) => selectedEvents.has(entryId));
const selectedEventId = Array.from(selectedEvents).at(0);
if (!selectedEventId) {
setEvent(null);
return;
@@ -1,7 +1,7 @@
import { memo } from 'react';
import { IoInformationCircle } from 'react-icons/io5';
import { Select, Switch, Tooltip } from '@chakra-ui/react';
import { EndAction, MaybeString, TimerType, TimeStrategy } from 'ontime-types';
import { EndAction, TimerType, TimeStrategy } from 'ontime-types';
import { millisToString, parseUserTime } from 'ontime-utils';
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
@@ -18,7 +18,7 @@ interface EventEditorTimesProps {
timeEnd: number;
duration: number;
timeStrategy: TimeStrategy;
linkStart: MaybeString;
linkStart: boolean;
countToEnd: boolean;
delay: number;
isPublic: boolean;
@@ -10,12 +10,13 @@ import style from './QuickAddBlock.module.scss';
interface QuickAddBlockProps {
previousEventId: MaybeString;
showBlocks?: boolean;
}
export default memo(QuickAddBlock);
function QuickAddBlock(props: QuickAddBlockProps) {
const { previousEventId } = props;
const { previousEventId, showBlocks } = props;
const { addEvent } = useEventAction();
const { emitError } = useEmitLog();
@@ -86,16 +87,18 @@ function QuickAddBlock(props: QuickAddBlockProps) {
>
Delay
</Button>
<Button
onClick={() => handleCreateEvent(SupportedEvent.Block)}
size='xs'
variant='ontime-subtle-white'
className={style.quickBtn}
leftIcon={<IoAdd />}
color='#b1b1b1' // $gray-400
>
Block
</Button>
{showBlocks && (
<Button
onClick={() => handleCreateEvent(SupportedEvent.Block)}
size='xs'
variant='ontime-subtle-white'
className={style.quickBtn}
leftIcon={<IoAdd />}
color='#b1b1b1' // $gray-400
>
Block
</Button>
)}
</div>
);
}
@@ -0,0 +1,91 @@
import { isOntimeEvent, isPlayableEvent, MaybeString, OntimeEntry, PlayableEvent } from 'ontime-types';
import { checkIsNextDay, isNewLatest } from 'ontime-utils';
type RundownMetadata = {
previousEvent: PlayableEvent | null; // The playableEvent from the previous iteration, used by indicators
latestEvent: PlayableEvent | null; // The playableEvent most forwards in time processed so far
previousEntryId: MaybeString; // previous entry is used to infer position in the rundown for new events
thisId: MaybeString;
eventIndex: number;
isPast: boolean;
isNext: boolean;
isNextDay: boolean;
totalGap: number;
isLinkedToLoaded: boolean; // check if the event can link all the way back to the currently playing event
isLoaded: boolean;
};
/**
* Creates a process function which aggregates the rundown metadata and event metadata
*/
export function makeRundownMetadata(selectedEventId: MaybeString) {
let rundownMeta: RundownMetadata = {
previousEvent: null,
latestEvent: null,
previousEntryId: null,
thisId: null,
eventIndex: 0,
isPast: Boolean(selectedEventId), // all events before the current selected are in the past
isNext: false,
isNextDay: false,
totalGap: 0,
isLinkedToLoaded: true,
isLoaded: false,
};
function process(entry: OntimeEntry): Readonly<RundownMetadata> {
const processedRundownMetadata = processEntry(rundownMeta, selectedEventId, entry);
rundownMeta = processedRundownMetadata;
return rundownMeta;
}
return process;
}
/**
* Receives a rundown entry and processes its place in the rundown
*
*/
function processEntry(
rundownMetadata: RundownMetadata,
selectedEventId: MaybeString,
entry: Readonly<OntimeEntry>,
): Readonly<RundownMetadata> {
const processedData = { ...rundownMetadata };
processedData.isNextDay = false;
processedData.isLoaded = false;
processedData.previousEntryId = processedData.thisId;
processedData.thisId = entry.id;
if (entry.id === selectedEventId) {
processedData.isLoaded = true;
processedData.isPast = false;
}
if (isOntimeEvent(entry)) {
// event indexes are 1 based in UI
processedData.eventIndex += 1;
processedData.previousEvent = processedData.latestEvent;
if (isPlayableEvent(entry)) {
processedData.isNextDay = checkIsNextDay(entry, processedData.previousEvent);
if (!processedData.isPast) {
processedData.totalGap += entry.gap;
/**
* isLinkToLoaded is a chain value that we maintain until we find an unlinked event
* or we find a countToEnd event
*/
processedData.isLinkedToLoaded =
processedData.isLinkedToLoaded && entry.linkStart && !processedData.previousEvent?.countToEnd;
}
if (isNewLatest(entry, processedData.previousEvent)) {
// this event is the forward most event in rundown, for next iteration
processedData.latestEvent = entry;
}
}
}
return processedData;
}
@@ -1,7 +1,7 @@
import { memo } from 'react';
import { IoAlertCircleOutline, IoLink, IoLockClosed, IoLockOpenOutline, IoUnlink } from 'react-icons/io5';
import { InputRightElement, Tooltip } from '@chakra-ui/react';
import { MaybeString, TimeField, TimeStrategy } from 'ontime-types';
import { TimeField, TimeStrategy } from 'ontime-types';
import { dayInMs } from 'ontime-utils';
import TimeInputWithButton from '../../../common/components/input/time-input/TimeInputWithButton';
@@ -19,7 +19,7 @@ interface EventBlockTimerProps {
timeEnd: number;
duration: number;
timeStrategy: TimeStrategy;
linkStart: MaybeString;
linkStart: boolean;
delay: number;
showLabels?: boolean;
}
@@ -38,7 +38,7 @@ function TimeInputFlow(props: EventBlockTimerProps) {
};
const handleLink = (doLink: boolean) => {
updateEvent({ id: eventId, linkStart: doLink ? 'true' : null });
updateEvent({ id: eventId, linkStart: doLink });
};
const warnings = [];
@@ -55,9 +55,9 @@ function TimeInputFlow(props: EventBlockTimerProps) {
const isLockedEnd = timeStrategy === TimeStrategy.LockEnd;
const isLockedDuration = timeStrategy === TimeStrategy.LockDuration;
const activeStart = cx([style.timeAction, linkStart ? style.active : null]);
const activeEnd = cx([style.timeAction, isLockedEnd ? style.active : null]);
const activeDuration = cx([style.timeAction, isLockedDuration ? style.active : null]);
const activeStart = cx([style.timeAction, linkStart && style.active]);
const activeEnd = cx([style.timeAction, isLockedEnd && style.active]);
const activeDuration = cx([style.timeAction, isLockedDuration && style.active]);
return (
<>
@@ -69,7 +69,7 @@ function TimeInputFlow(props: EventBlockTimerProps) {
time={timeStart}
hasDelay={hasDelay}
placeholder='Start'
disabled={Boolean(linkStart)}
disabled={linkStart}
>
<Tooltip label='Link start to previous end' openDelay={tooltipDelayMid}>
<InputRightElement className={activeStart} onClick={() => handleLink(!linkStart)}>
@@ -22,7 +22,7 @@ function MakeStart({ getValue, row, table }: CellContext<OntimeEntry, unknown>)
const update = (newValue: string) => handleUpdateTimer(row.original.id, 'timeStart', newValue);
const startTime = getValue() as number;
const isStartLocked = (row.original as OntimeEvent).linkStart === null;
const isStartLocked = !(row.original as OntimeEvent).linkStart;
const delayValue = (row.original as OntimeEvent)?.delay ?? 0;
const displayTime = showDelayedTimes ? startTime + delayValue : startTime;
@@ -6,7 +6,7 @@ import {
getEventWithId,
getFirstEvent,
getNextEvent,
getTimeFromPrevious,
getTimeFrom,
isNewLatest,
MILLIS_PER_HOUR,
} from 'ontime-utils';
@@ -134,7 +134,7 @@ export function useScopedRundown(rundown: OntimeEntry[], selectedEventId: MaybeS
firstStart = currentEntry.timeStart;
}
const timeFromPrevious: number = getTimeFromPrevious(currentEntry, lastEntry);
const timeFromPrevious: number = getTimeFrom(currentEntry, lastEntry);
if (timeFromPrevious === 0) {
totalDuration += currentEntry.duration;