refactor: show running gap in UI

This commit is contained in:
Carlos Valente
2024-08-27 09:39:32 +02:00
committed by Carlos Valente
parent f5936e5254
commit 73db14c383
10 changed files with 129 additions and 50 deletions
+20 -12
View File
@@ -2,7 +2,15 @@ import { Fragment, lazy, useCallback, useEffect, useRef, useState } from 'react'
import { closestCenter, DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'; import { closestCenter, DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { useHotkeys } from '@mantine/hooks'; import { useHotkeys } from '@mantine/hooks';
import { isOntimeBlock, isOntimeEvent, MaybeNumber, Playback, RundownCached, SupportedEvent } from 'ontime-types'; import {
isOntimeBlock,
isOntimeEvent,
isPlayableEvent,
PlayableEvent,
Playback,
RundownCached,
SupportedEvent,
} from 'ontime-types';
import { import {
getFirstNormal, getFirstNormal,
getLastNormal, getLastNormal,
@@ -10,6 +18,7 @@ import {
getNextNormal, getNextNormal,
getPreviousBlockNormal, getPreviousBlockNormal,
getPreviousNormal, getPreviousNormal,
isNewLatest,
} from 'ontime-utils'; } from 'ontime-utils';
import { useEventAction } from '../../common/hooks/useEventAction'; import { useEventAction } from '../../common/hooks/useEventAction';
@@ -247,11 +256,9 @@ export default function Rundown({ data }: RundownProps) {
return <RundownEmpty handleAddNew={() => insertAtId(SupportedEvent.Event, cursor)} />; return <RundownEmpty handleAddNew={() => insertAtId(SupportedEvent.Event, cursor)} />;
} }
let previousStart: MaybeNumber = null; let lastEntry: PlayableEvent | undefined; // used by indicators
let previousEnd: MaybeNumber = null; let thisEntry: PlayableEvent | undefined;
let previousEventId: string | undefined; let previousEventId: string | undefined;
let thisStart: MaybeNumber = null;
let thisEnd: MaybeNumber = null;
let thisId = previousEventId; let thisId = previousEventId;
let eventIndex = 0; let eventIndex = 0;
@@ -279,13 +286,14 @@ export default function Rundown({ data }: RundownProps) {
if (isOntimeEvent(event)) { if (isOntimeEvent(event)) {
// event indexes are 1 based in frontend // event indexes are 1 based in frontend
eventIndex++; eventIndex++;
previousStart = thisStart;
previousEnd = thisEnd;
previousEventId = thisId; previousEventId = thisId;
lastEntry = thisEntry;
if (!event.skip) { if (isPlayableEvent(event)) {
thisStart = event.timeStart; // populate previous entry
thisEnd = event.timeEnd; if (isNewLatest(event.timeStart, event.timeEnd, lastEntry?.timeStart, lastEntry?.timeEnd)) {
thisEntry = event;
}
thisId = eventId; thisId = eventId;
} }
} }
@@ -312,8 +320,8 @@ export default function Rundown({ data }: RundownProps) {
loaded={isLoaded} loaded={isLoaded}
hasCursor={hasCursor} hasCursor={hasCursor}
isNext={isNext} isNext={isNext}
previousStart={previousStart} previousStart={lastEntry?.timeStart}
previousEnd={previousEnd} previousEnd={lastEntry?.timeEnd}
previousEventId={previousEventId} previousEventId={previousEventId}
playback={isLoaded ? featureData.playback : undefined} playback={isLoaded ? featureData.playback : undefined}
isRolling={featureData.playback === Playback.Roll} isRolling={featureData.playback === Playback.Roll}
@@ -1,5 +1,5 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { MaybeNumber, OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent } from 'ontime-types'; import { OntimeEvent, OntimeRundownEntry, Playback, SupportedEvent } from 'ontime-types';
import { useEventAction } from '../../common/hooks/useEventAction'; import { useEventAction } from '../../common/hooks/useEventAction';
import useMemoisedFn from '../../common/hooks/useMemoisedFn'; import useMemoisedFn from '../../common/hooks/useMemoisedFn';
@@ -32,8 +32,8 @@ interface RundownEntryProps {
eventIndex: number; eventIndex: number;
hasCursor: boolean; hasCursor: boolean;
isNext: boolean; isNext: boolean;
previousStart: MaybeNumber; previousStart?: number;
previousEnd: MaybeNumber; previousEnd?: number;
previousEventId?: string; previousEventId?: string;
playback?: Playback; // we only care about this if this event is playing 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 isRolling: boolean; // we need to know even if not related to this event
@@ -8,7 +8,7 @@ import { IoPeopleOutline } from '@react-icons/all-files/io5/IoPeopleOutline';
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo'; import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical'; import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
import { EndAction, MaybeNumber, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types'; import { EndAction, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types';
import { useContextMenu } from '../../../common/hooks/useContextMenu'; import { useContextMenu } from '../../../common/hooks/useContextMenu';
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils'; import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
@@ -36,8 +36,8 @@ interface EventBlockProps {
title: string; title: string;
note: string; note: string;
delay: number; delay: number;
previousStart: MaybeNumber; previousStart?: number;
previousEnd: MaybeNumber; previousEnd?: number;
colour: string; colour: string;
isPast: boolean; isPast: boolean;
isNext: boolean; isNext: boolean;
@@ -1,4 +1,3 @@
import { MaybeNumber } from 'ontime-types';
import { import {
calculateDuration, calculateDuration,
checkIsNextDay, checkIsNextDay,
@@ -19,12 +18,8 @@ export function formatDelay(timeStart: number, delay: number): string | undefine
return `New start ${timeTag}`; return `New start ${timeTag}`;
} }
export function formatOverlap( export function formatOverlap(timeStart: number, previousStart?: number, previousEnd?: number): string | undefined {
previousStart: MaybeNumber, const noPreviousElement = previousEnd === undefined || previousStart === undefined;
previousEnd: MaybeNumber,
timeStart: number,
): string | undefined {
const noPreviousElement = previousEnd === null || previousStart === null;
if (noPreviousElement) return; if (noPreviousElement) return;
const normalisedDuration = calculateDuration(previousStart, previousEnd); const normalisedDuration = calculateDuration(previousStart, previousEnd);
@@ -1,20 +1,18 @@
import { MaybeNumber } from 'ontime-types';
import { formatDelay, formatOverlap } from './EventBlock.utils'; import { formatDelay, formatOverlap } from './EventBlock.utils';
import style from './RundownIndicators.module.scss'; import style from './RundownIndicators.module.scss';
interface RundownIndicatorProps { interface RundownIndicatorProps {
timeStart: number; timeStart: number;
previousStart: MaybeNumber; previousStart?: number;
previousEnd: MaybeNumber; previousEnd?: number;
delay: number; delay: number;
} }
export default function RundownIndicators(props: RundownIndicatorProps) { export default function RundownIndicators(props: RundownIndicatorProps) {
const { timeStart, previousStart, previousEnd, delay } = props; const { timeStart, previousStart, previousEnd, delay } = props;
const hasOverlap = formatOverlap(previousStart, previousEnd, timeStart); const hasOverlap = formatOverlap(timeStart, previousStart, previousEnd);
const hasDelay = formatDelay(timeStart, delay); const hasDelay = formatDelay(timeStart, delay);
return ( return (
@@ -16,7 +16,7 @@ describe('formatOverlap()', () => {
const previousStart = 0; const previousStart = 0;
const previousEnd = 60000; // 1 min const previousEnd = 60000; // 1 min
const timeStart = 30000; // 30 sec const timeStart = 30000; // 30 sec
const result = formatOverlap(previousStart, previousEnd, timeStart); const result = formatOverlap(timeStart, previousStart, previousEnd);
expect(result).toEqual('Overlap 30s'); expect(result).toEqual('Overlap 30s');
}); });
@@ -24,7 +24,7 @@ describe('formatOverlap()', () => {
const previousStart = 46800000; // 13:00:00 const previousStart = 46800000; // 13:00:00
const previousEnd = 48600000; // 13:30:00 const previousEnd = 48600000; // 13:30:00
const timeStart = 48300000; // 13:25:00 const timeStart = 48300000; // 13:25:00
const result = formatOverlap(previousStart, previousEnd, timeStart); const result = formatOverlap(timeStart, previousStart, previousEnd);
expect(result).toEqual('Overlap 5m'); expect(result).toEqual('Overlap 5m');
}); });
@@ -32,7 +32,7 @@ describe('formatOverlap()', () => {
const previousStart = 11 * MILLIS_PER_HOUR; const previousStart = 11 * MILLIS_PER_HOUR;
const previousEnd = 12 * MILLIS_PER_HOUR; const previousEnd = 12 * MILLIS_PER_HOUR;
const timeStart = 6 * MILLIS_PER_HOUR; const timeStart = 6 * MILLIS_PER_HOUR;
const result = formatOverlap(previousStart, previousEnd, timeStart); const result = formatOverlap(timeStart, previousStart, previousEnd);
expect(result).toBe('Gap 18h (next day)'); expect(result).toBe('Gap 18h (next day)');
}); });
@@ -40,7 +40,7 @@ describe('formatOverlap()', () => {
const previousStart = 17 * MILLIS_PER_HOUR; const previousStart = 17 * MILLIS_PER_HOUR;
const previousEnd = 23 * MILLIS_PER_HOUR; const previousEnd = 23 * MILLIS_PER_HOUR;
const timeStart = 9 * MILLIS_PER_HOUR; const timeStart = 9 * MILLIS_PER_HOUR;
const result = formatOverlap(previousStart, previousEnd, timeStart); const result = formatOverlap(timeStart, previousStart, previousEnd);
expect(result).toBe('Gap 10h (next day)'); expect(result).toBe('Gap 10h (next day)');
}); });
@@ -48,15 +48,31 @@ describe('formatOverlap()', () => {
const previousStart = 23 * MILLIS_PER_HOUR; // 23:00:00 const previousStart = 23 * MILLIS_PER_HOUR; // 23:00:00
const previousEnd = 0; // 00:00:00 const previousEnd = 0; // 00:00:00
const timeStart = 1 * MILLIS_PER_HOUR; // 01:00:00 const timeStart = 1 * MILLIS_PER_HOUR; // 01:00:00
const result = formatOverlap(previousStart, previousEnd, timeStart); const result = formatOverlap(timeStart, previousStart, previousEnd);
expect(result).toBe('Gap 1h (next day)'); expect(result).toBe('Gap 1h (next day)');
}); });
it('handles events the day after, with previous ending over midnight', () => { it('handles sequential events the day after, with previous ending over midnight', () => {
const previousStart = 23 * MILLIS_PER_HOUR;
const previousEnd = 1 * MILLIS_PER_HOUR;
const timeStart = 1 * MILLIS_PER_HOUR;
const result = formatOverlap(timeStart, previousStart, previousEnd);
expect(result).toBeUndefined();
});
it('handles events the day after, with previous ending over midnight with overlap', () => {
const previousStart = 23 * MILLIS_PER_HOUR;
const previousEnd = 2 * MILLIS_PER_HOUR;
const timeStart = 1 * MILLIS_PER_HOUR;
const result = formatOverlap(timeStart, previousStart, previousEnd);
expect(result).toBe('Overlap 1h');
});
it('handles events the day after, with previous ending over midnight with gap', () => {
const previousStart = 23 * MILLIS_PER_HOUR; const previousStart = 23 * MILLIS_PER_HOUR;
const previousEnd = 1 * MILLIS_PER_HOUR; const previousEnd = 1 * MILLIS_PER_HOUR;
const timeStart = 2 * MILLIS_PER_HOUR; const timeStart = 2 * MILLIS_PER_HOUR;
const result = formatOverlap(previousStart, previousEnd, timeStart); const result = formatOverlap(timeStart, previousStart, previousEnd);
expect(result).toBe('Gap 1h'); expect(result).toBe('Gap 1h');
}); });
}); });
@@ -11,14 +11,7 @@ import {
OntimeRundownEntry, OntimeRundownEntry,
PlayableEvent, PlayableEvent,
} from 'ontime-types'; } from 'ontime-types';
import { import { generateId, insertAtIndex, reorderArray, swapEventData, getTimeFromPrevious, isNewLatest } from 'ontime-utils';
generateId,
insertAtIndex,
reorderArray,
swapEventData,
getTimeFromPrevious,
checkIsNextDay,
} from 'ontime-utils';
import { getDataProvider } from '../../classes/data-provider/DataProvider.js'; import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
import { createPatch } from '../../utils/parser.js'; import { createPatch } from '../../utils/parser.js';
import { apply } from './delayUtils.js'; import { apply } from './delayUtils.js';
@@ -140,11 +133,7 @@ export function generate(
currentEntry.delay = totalDelay; currentEntry.delay = totalDelay;
// lastEntry is the event with the latest end time // lastEntry is the event with the latest end time
if ( if (isNewLatest(currentEntry.timeStart, currentEntry.timeEnd, lastEntry?.timeStart, lastEntry?.timeEnd)) {
lastEntry === null ||
currentEntry.timeEnd > lastEntry.timeEnd ||
checkIsNextDay(lastEntry.timeStart, currentEntry.timeStart, lastEntry.duration)
) {
lastEntry = currentEntry; lastEntry = currentEntry;
} }
} }
+1
View File
@@ -76,6 +76,7 @@ export { validateEndAction, validateTimerType } from './src/validate-events/vali
export { checkIsNow } from './src/date-utils/checkIsNow.js'; export { checkIsNow } from './src/date-utils/checkIsNow.js';
export { checkIsNextDay } from './src/date-utils/checkIsNextDay.js'; export { checkIsNextDay } from './src/date-utils/checkIsNextDay.js';
export { getTimeFromPrevious } from './src/date-utils/getTimeFromPrevious.js'; export { getTimeFromPrevious } from './src/date-utils/getTimeFromPrevious.js';
export { isNewLatest } from './src/date-utils/isNewLatest.js';
// feature business logic - spreadsheet import // feature business logic - spreadsheet import
export { export {
@@ -0,0 +1,48 @@
import { MILLIS_PER_HOUR } from './conversionUtils';
import { isNewLatest } from './isNewLatest';
describe('isNewLatest', () => {
it('should be true if there is no previous', () => {
expect(isNewLatest(0, 60000)).toBeTruthy();
});
it('should be true if it starts when the previous finishes', () => {
const nowStart = 10 * MILLIS_PER_HOUR;
const nowEnd = 11 * MILLIS_PER_HOUR;
const previousStart = 9 * MILLIS_PER_HOUR;
const previousEnd = 10 * MILLIS_PER_HOUR;
expect(isNewLatest(nowStart, nowEnd, previousStart, previousEnd)).toBeTruthy();
});
it('should be true if it starts the same day the previous finishes', () => {
const nowStart = 22 * MILLIS_PER_HOUR;
const nowEnd = 23 * MILLIS_PER_HOUR;
const previousStart = 9 * MILLIS_PER_HOUR;
const previousEnd = 20 * MILLIS_PER_HOUR;
expect(isNewLatest(nowStart, nowEnd, previousStart, previousEnd)).toBeTruthy();
});
it('should be true if it finishes after the previous, accounting for passing midnight', () => {
const nowStart = 1 * MILLIS_PER_HOUR;
const nowEnd = 3 * MILLIS_PER_HOUR;
const previousStart = 23 * MILLIS_PER_HOUR;
const previousEnd = 2 * MILLIS_PER_HOUR;
expect(isNewLatest(nowStart, nowEnd, previousStart, previousEnd)).toBeTruthy();
});
it('should be true if it the next day', () => {
const nowStart = 8 * MILLIS_PER_HOUR;
const nowEnd = 10 * MILLIS_PER_HOUR;
const previousStart = 9 * MILLIS_PER_HOUR;
const previousEnd = 11 * MILLIS_PER_HOUR;
expect(isNewLatest(nowStart, nowEnd, previousStart, previousEnd)).toBeTruthy();
});
it('should be true if it the next day (2)', () => {
const nowStart = 9 * MILLIS_PER_HOUR;
const nowEnd = 11 * MILLIS_PER_HOUR;
const previousStart = 9 * MILLIS_PER_HOUR;
const previousEnd = 11 * MILLIS_PER_HOUR;
expect(isNewLatest(nowStart, nowEnd, previousStart, previousEnd)).toBeTruthy();
});
});
@@ -0,0 +1,24 @@
import { checkIsNextDay } from './checkIsNextDay.js';
/**
* Checks whether a new element is the latest in the list
*/
export function isNewLatest(timeStart: number, timeEnd: number, previousStart?: number, previousEnd?: number): boolean {
// true if there is no previous
if (previousStart === undefined || previousEnd === undefined) {
return true;
}
// true if it starts after the previous is finished
if (timeStart >= previousEnd) {
return true;
}
// true if it finishes later than previous
if (timeEnd > previousEnd) {
return true;
}
// true if it is the day after
return checkIsNextDay(previousStart, timeStart, previousEnd - previousStart);
}