Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calculate time util all events start #1360

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/client/src/common/hooks/useSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,16 @@ export const useTimelineStatus = () => {
return useRuntimeStore(featureSelector);
};

export const useTimeUntilData = () => {
const featureSelector = (state: RuntimeStore) => ({
clock: state.clock,
offset: state.runtime.offset,
selectedEventIndex: state.runtime.selectedEventIndex,
});

return useRuntimeStore(featureSelector);
};

export const usePing = () => {
const featureSelector = (state: RuntimeStore) => ({
ping: state.ping,
Expand Down
12 changes: 12 additions & 0 deletions apps/client/src/common/hooks/useTimeUntil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { calculateExpectedStart } from 'ontime-utils';

import useRundown from '../hooks-query/useRundown';

import { useTimeUntilData } from './useSocket';

export default function useTimeUntil() {
const { data: rundownCached } = useRundown();
const { offset, clock, selectedEventIndex } = useTimeUntilData();

return calculateExpectedStart(rundownCached, offset, clock, selectedEventIndex);
}
1 change: 1 addition & 0 deletions apps/client/src/views/timeline/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ function Timeline(props: TimelineProps) {
status={eventStatus}
start={normalisedStart} // dataset solves issues related to crossing midnight
title={event.title}
id={event.id}
width={elementWidth}
/>
);
Expand Down
17 changes: 8 additions & 9 deletions apps/client/src/views/timeline/TimelineEntry.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useTimelineStatus, useTimer } from '../../common/hooks/useSocket';
import { useTimer } from '../../common/hooks/useSocket';
import { getProgress } from '../../common/utils/getProgress';
import { alpha, cx } from '../../common/utils/styleUtils';
import { formatDuration, formatTime } from '../../common/utils/time';
import { useTranslation } from '../../translation/TranslationProvider';

import { getStatusLabel, getTimeToStart } from './timeline.utils';
import { getStatusLabel, StableTimeUntil } from './timeline.utils';

import style from './Timeline.module.scss';

Expand All @@ -19,6 +19,7 @@ interface TimelineEntryProps {
start: number;
title: string;
width: number;
id: string;
}

const formatOptions = {
Expand All @@ -27,7 +28,7 @@ const formatOptions = {
};

export function TimelineEntry(props: TimelineEntryProps) {
const { colour, delay, duration, left, status, start, title, width } = props;
const { colour, delay, duration, left, status, start, title, width, id } = props;

const formattedStartTime = formatTime(start, formatOptions);
const formattedDuration = formatDuration(duration);
Expand Down Expand Up @@ -65,7 +66,7 @@ export function TimelineEntry(props: TimelineEntryProps) {
{status !== 'done' && (
<>
<div className={style.duration}>{formattedDuration}</div>
<TimelineEntryStatus delay={delay} start={start} status={status} />
<TimelineEntryStatus id={id} status={status} />
</>
)}
</div>
Expand All @@ -74,19 +75,17 @@ export function TimelineEntry(props: TimelineEntryProps) {
}

interface TimelineEntryStatusProps {
delay: number;
start: number;
status: ProgressStatus;
id: string;
}

// extract component to isolate re-renders provoked by the clock changes
function TimelineEntryStatus(props: TimelineEntryStatusProps) {
const { delay, start, status } = props;
const { clock, offset } = useTimelineStatus();
const { status, id } = props;
const { getLocalizedString } = useTranslation();

// start times need to be normalised in a rundown that crosses midnight
let statusText = getStatusLabel(getTimeToStart(clock, start, delay, offset), status);
let statusText = getStatusLabel(StableTimeUntil(id) ?? '', status);
if (statusText === 'live') {
statusText = getLocalizedString('timeline.live');
} else if (statusText === 'pending') {
Expand Down
27 changes: 5 additions & 22 deletions apps/client/src/views/timeline/TimelinePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import ViewLogo from '../../common/components/view-logo/ViewLogo';
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
import { ViewExtendedTimer } from '../../common/models/TimeManager.type';
import { formatDuration, formatTime, getDefaultFormat } from '../../common/utils/time';
import { formatTime, getDefaultFormat } from '../../common/utils/time';
import SuperscriptTime from '../../features/viewers/common/superscript-time/SuperscriptTime';
import { useTranslation } from '../../translation/TranslationProvider';

import Section from './timeline-section/TimelineSection';
import Timeline from './Timeline';
import { getTimelineOptions } from './timeline.options';
import { getTimeToStart, getUpcomingEvents, useScopedRundown } from './timeline.utils';
import { getUpcomingEvents, StableTimeUntil, useScopedRundown } from './timeline.utils';

import './TimelinePage.scss';

Expand All @@ -31,7 +31,7 @@ interface TimelinePageProps {
* There is little point splitting or memoising top level elements
*/
export default function TimelinePage(props: TimelinePageProps) {
const { backstageEvents, general, runtime, selectedId, settings, time } = props;
const { backstageEvents, general, selectedId, settings, time } = props;
// holds copy of the rundown with only relevant events
const { scopedRundown, firstStart, totalDuration } = useScopedRundown(backstageEvents, selectedId);
const { getLocalizedString } = useTranslation();
Expand All @@ -48,29 +48,12 @@ export default function TimelinePage(props: TimelinePageProps) {
const progressOptions = getTimelineOptions(defaultFormat);

const titleNow = now?.title ?? '-';
const dueText = getLocalizedString('timeline.due').toUpperCase();
const nextText = next !== null ? next.title : '-';
const followedByText = followedBy !== null ? followedBy.title : '-';
let nextStatus: string | undefined;
let followedByStatus: string | undefined;

if (next !== null) {
const timeToStart = getTimeToStart(time.clock, next.timeStart, next?.delay ?? 0, runtime.offset);
if (timeToStart < 0) {
nextStatus = dueText;
} else {
nextStatus = `T - ${formatDuration(timeToStart)}`;
}
}
const nextStatus = StableTimeUntil(next?.id);
const followedByStatus = StableTimeUntil(followedBy?.id);

if (followedBy !== null) {
const timeToStart = getTimeToStart(time.clock, followedBy.timeStart, followedBy?.delay ?? 0, runtime.offset);
if (timeToStart < 0) {
followedByStatus = dueText;
} else {
followedByStatus = `T - ${formatDuration(timeToStart)}`;
}
}
return (
<div className='timeline' data-testid='timeline-view'>
<ViewParamsEditor viewOptions={progressOptions} />
Expand Down
39 changes: 28 additions & 11 deletions apps/client/src/views/timeline/timeline.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import {
getTimeFromPrevious,
isNewLatest,
MILLIS_PER_HOUR,
MILLIS_PER_SECOND,
} from 'ontime-utils';

import useTimeUntil from '../../common/hooks/useTimeUntil';
import { clamp } from '../../common/utils/math';
import { formatDuration } from '../../common/utils/time';
import { isStringBoolean } from '../../features/viewers/common/viewUtils';
import { useTranslation } from '../../translation/TranslationProvider';

import type { ProgressStatus } from './TimelineEntry';

Expand Down Expand Up @@ -77,16 +80,12 @@ export function makeTimelineSections(firstHour: number, lastHour: number) {
/**
* Returns a formatted label for a progress status
*/
export function getStatusLabel(timeToStart: number, status: ProgressStatus): string {
export function getStatusLabel(timeToStart: string, status: ProgressStatus): string {
if (status === 'done' || status === 'live') {
return status;
}

if (timeToStart < 0) {
return 'pending';
}

return formatDuration(timeToStart);
return timeToStart;
}

interface ScopedRundownData {
Expand Down Expand Up @@ -198,9 +197,27 @@ export function getUpcomingEvents(events: OntimeRundown, selectedId: MaybeString
};
}

/**
* Utility function calculates time to start
*/
export function getTimeToStart(now: number, start: number, delay: number, offset: number): number {
return start + delay - now - offset;
export function StableTimeUntil(id: string | undefined) {
const expectedRundown = useTimeUntil();
const { getLocalizedString } = useTranslation();

const lazyTimeToStart = useMemo(() => {
if (id === undefined) {
return;
}
const thisEvent = expectedRundown[id];

if (thisEvent === undefined) {
return;
}

const { timeUntil } = thisEvent;
// if the event is due, we dont need need the accurate value
if (timeUntil <= MILLIS_PER_SECOND) {
return getLocalizedString('timeline.due').toUpperCase();
}
return `T - ${formatDuration(timeUntil)}`;
}, [expectedRundown, getLocalizedString, id]);

return lazyTimeToStart;
}
3 changes: 3 additions & 0 deletions packages/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// time utils
export { calculateExpectedStart } from './src/time-utils/timeUtils.js';

// runtime utils
export { validatePlayback } from './src/validate-action/validatePlayback.js';
export { isKnownTimerType, validateLinkStart, validateTimeStrategy } from './src/validate-events/validateEvent.js';
Expand Down
Loading
Loading