mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-14 20:03:52 +00:00
refactor: show running gap in UI
This commit is contained in:
committed by
Carlos Valente
parent
f5936e5254
commit
73db14c383
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user