diff --git a/apps/client/src/common/hooks-query/useRundown.ts b/apps/client/src/common/hooks-query/useRundown.ts index 05f8ef6d0..d3ae648a7 100644 --- a/apps/client/src/common/hooks-query/useRundown.ts +++ b/apps/client/src/common/hooks-query/useRundown.ts @@ -1,6 +1,6 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; -import { OntimeEntry, Rundown } from 'ontime-types'; +import { EntryId, OntimeEntry, Rundown } from 'ontime-types'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; import { RUNDOWN } from '../api/constants'; @@ -78,3 +78,18 @@ export function usePartialRundown(cb: (event: OntimeEntry) => boolean) { return { data: filteredData, status }; } + +/** + * Hook to get a specific entry by ID from the rundown + */ +export function useEntry(entryId: EntryId | null): OntimeEntry | null { + const { data: rundown } = useRundown(); + + // track the specific entry we care about + const entry = useMemo(() => { + if (entryId === null) return null; + return rundown.entries[entryId]; + }, [entryId, rundown.entries]); + + return entry; +} diff --git a/apps/client/src/features/overview/CuesheetOverview.tsx b/apps/client/src/features/overview/CuesheetOverview.tsx index cf22eb156..d39db69bd 100644 --- a/apps/client/src/features/overview/CuesheetOverview.tsx +++ b/apps/client/src/features/overview/CuesheetOverview.tsx @@ -3,7 +3,13 @@ import { memo, PropsWithChildren, useMemo } from 'react'; import { useIsMobileScreen } from '../../common/hooks/useIsMobileScreen'; import { useRuntimeOverview } from '../../common/hooks/useSocket'; -import { CurrentBlockOverview, OverviewWrapper, RuntimeOverview, TimerOverview } from './composite/OverviewWrapper'; +import { + ClockOverview, + CurrentBlockOverview, + OverviewWrapper, + RuntimeOverview, + TimerOverview, +} from './composite/OverviewWrapper'; import { TimeRow } from './composite/TimeLayout'; import { calculateEndAndDaySpan, formatedTime } from './overviewUtils'; @@ -51,8 +57,9 @@ function CuesheetDesktop({ children }: PropsWithChildren) { /> - + +
- + +
; + /** + * The time to the end of the block + * as scheduled + * TODO(v4): this needs to be calculated according to offset mode + */ + const blockEnd = (() => { + if (!entry) return timerPlaceholderMin; + if (!isOntimeBlock(entry)) return timerPlaceholderMin; + if (entry.timeEnd === null) return timerPlaceholderMin; + return formatedTime(entry.timeEnd - clock, 2); + })(); + + /** + * The time to the end of the block + * as projected accounting for delays and offset + * TODO(v4): this needs to be calculated according to offset mode + */ + const projectedBlockEnd = (() => { + if (blockStartAt === null || !entry) return timerPlaceholderMin; + if (!isOntimeBlock(entry)) return timerPlaceholderMin; + return formatedTime(blockStartAt + entry.duration - clock, 2); + })(); + + return ( + <> + +
+ + +
+ + ); } export function TimerOverview() { @@ -62,23 +108,23 @@ export function ProgressOverview() { const { numEvents, selectedEventIndex } = useRuntimePlaybackOverview(); const current = selectedEventIndex !== null ? selectedEventIndex + 1 : enDash; - const ofTotal = numEvents || enDash; - const progressText = numEvents ? `${current} of ${ofTotal}` : '-'; + const progressText = numEvents ? `${current} of ${numEvents || enDash}` : enDash; return ; } export function RuntimeOverview() { - const { clock, offset, playback } = useRuntimePlaybackOverview(); + const { offset, playback } = useRuntimePlaybackOverview(); const isPlaying = isPlaybackActive(playback); const offsetText = getOffsetText(isPlaying ? offset : null); const offsetClasses = cx([style.offset, isPlaying && (offset < 0 ? style.behind : style.ahead)]); - return ( - <> - - - - ); + return ; +} + +export function ClockOverview() { + const { clock } = useClock(); + + return ; }