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 { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
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 {
getFirstNormal,
getLastNormal,
@@ -10,6 +18,7 @@ import {
getNextNormal,
getPreviousBlockNormal,
getPreviousNormal,
isNewLatest,
} from 'ontime-utils';
import { useEventAction } from '../../common/hooks/useEventAction';
@@ -247,11 +256,9 @@ export default function Rundown({ data }: RundownProps) {
return <RundownEmpty handleAddNew={() => insertAtId(SupportedEvent.Event, cursor)} />;
}
let previousStart: MaybeNumber = null;
let previousEnd: MaybeNumber = null;
let lastEntry: PlayableEvent | undefined; // used by indicators
let thisEntry: PlayableEvent | undefined;
let previousEventId: string | undefined;
let thisStart: MaybeNumber = null;
let thisEnd: MaybeNumber = null;
let thisId = previousEventId;
let eventIndex = 0;
@@ -279,13 +286,14 @@ export default function Rundown({ data }: RundownProps) {
if (isOntimeEvent(event)) {
// event indexes are 1 based in frontend
eventIndex++;
previousStart = thisStart;
previousEnd = thisEnd;
previousEventId = thisId;
lastEntry = thisEntry;
if (!event.skip) {
thisStart = event.timeStart;
thisEnd = event.timeEnd;
if (isPlayableEvent(event)) {
// populate previous entry
if (isNewLatest(event.timeStart, event.timeEnd, lastEntry?.timeStart, lastEntry?.timeEnd)) {
thisEntry = event;
}
thisId = eventId;
}
}
@@ -312,8 +320,8 @@ export default function Rundown({ data }: RundownProps) {
loaded={isLoaded}
hasCursor={hasCursor}
isNext={isNext}
previousStart={previousStart}
previousEnd={previousEnd}
previousStart={lastEntry?.timeStart}
previousEnd={lastEntry?.timeEnd}
previousEventId={previousEventId}
playback={isLoaded ? featureData.playback : undefined}
isRolling={featureData.playback === Playback.Roll}
@@ -1,5 +1,5 @@
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 useMemoisedFn from '../../common/hooks/useMemoisedFn';
@@ -32,8 +32,8 @@ interface RundownEntryProps {
eventIndex: number;
hasCursor: boolean;
isNext: boolean;
previousStart: MaybeNumber;
previousEnd: MaybeNumber;
previousStart?: number;
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
@@ -8,7 +8,7 @@ import { IoPeopleOutline } from '@react-icons/all-files/io5/IoPeopleOutline';
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
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 { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
@@ -36,8 +36,8 @@ interface EventBlockProps {
title: string;
note: string;
delay: number;
previousStart: MaybeNumber;
previousEnd: MaybeNumber;
previousStart?: number;
previousEnd?: number;
colour: string;
isPast: boolean;
isNext: boolean;
@@ -1,4 +1,3 @@
import { MaybeNumber } from 'ontime-types';
import {
calculateDuration,
checkIsNextDay,
@@ -19,12 +18,8 @@ export function formatDelay(timeStart: number, delay: number): string | undefine
return `New start ${timeTag}`;
}
export function formatOverlap(
previousStart: MaybeNumber,
previousEnd: MaybeNumber,
timeStart: number,
): string | undefined {
const noPreviousElement = previousEnd === null || previousStart === null;
export function formatOverlap(timeStart: number, previousStart?: number, previousEnd?: number): string | undefined {
const noPreviousElement = previousEnd === undefined || previousStart === undefined;
if (noPreviousElement) return;
const normalisedDuration = calculateDuration(previousStart, previousEnd);
@@ -1,20 +1,18 @@
import { MaybeNumber } from 'ontime-types';
import { formatDelay, formatOverlap } from './EventBlock.utils';
import style from './RundownIndicators.module.scss';
interface RundownIndicatorProps {
timeStart: number;
previousStart: MaybeNumber;
previousEnd: MaybeNumber;
previousStart?: number;
previousEnd?: number;
delay: number;
}
export default function RundownIndicators(props: RundownIndicatorProps) {
const { timeStart, previousStart, previousEnd, delay } = props;
const hasOverlap = formatOverlap(previousStart, previousEnd, timeStart);
const hasOverlap = formatOverlap(timeStart, previousStart, previousEnd);
const hasDelay = formatDelay(timeStart, delay);
return (
@@ -16,7 +16,7 @@ describe('formatOverlap()', () => {
const previousStart = 0;
const previousEnd = 60000; // 1 min
const timeStart = 30000; // 30 sec
const result = formatOverlap(previousStart, previousEnd, timeStart);
const result = formatOverlap(timeStart, previousStart, previousEnd);
expect(result).toEqual('Overlap 30s');
});
@@ -24,7 +24,7 @@ describe('formatOverlap()', () => {
const previousStart = 46800000; // 13:00:00
const previousEnd = 48600000; // 13:30:00
const timeStart = 48300000; // 13:25:00
const result = formatOverlap(previousStart, previousEnd, timeStart);
const result = formatOverlap(timeStart, previousStart, previousEnd);
expect(result).toEqual('Overlap 5m');
});
@@ -32,7 +32,7 @@ describe('formatOverlap()', () => {
const previousStart = 11 * MILLIS_PER_HOUR;
const previousEnd = 12 * 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)');
});
@@ -40,7 +40,7 @@ describe('formatOverlap()', () => {
const previousStart = 17 * MILLIS_PER_HOUR;
const previousEnd = 23 * 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)');
});
@@ -48,15 +48,31 @@ describe('formatOverlap()', () => {
const previousStart = 23 * MILLIS_PER_HOUR; // 23:00:00
const previousEnd = 0; // 00: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)');
});
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 previousEnd = 1 * 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');
});
});
@@ -11,14 +11,7 @@ import {
OntimeRundownEntry,
PlayableEvent,
} from 'ontime-types';
import {
generateId,
insertAtIndex,
reorderArray,
swapEventData,
getTimeFromPrevious,
checkIsNextDay,
} from 'ontime-utils';
import { generateId, insertAtIndex, reorderArray, swapEventData, getTimeFromPrevious, isNewLatest } from 'ontime-utils';
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
import { createPatch } from '../../utils/parser.js';
import { apply } from './delayUtils.js';
@@ -140,11 +133,7 @@ export function generate(
currentEntry.delay = totalDelay;
// lastEntry is the event with the latest end time
if (
lastEntry === null ||
currentEntry.timeEnd > lastEntry.timeEnd ||
checkIsNextDay(lastEntry.timeStart, currentEntry.timeStart, lastEntry.duration)
) {
if (isNewLatest(currentEntry.timeStart, currentEntry.timeEnd, lastEntry?.timeStart, lastEntry?.timeEnd)) {
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 { checkIsNextDay } from './src/date-utils/checkIsNextDay.js';
export { getTimeFromPrevious } from './src/date-utils/getTimeFromPrevious.js';
export { isNewLatest } from './src/date-utils/isNewLatest.js';
// feature business logic - spreadsheet import
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);
}