import { useMemo } from 'react';
import {
TbCalendarClock,
TbCalendarPin,
TbCalendarStar,
TbFlagPin,
TbFlagStar,
TbFolderPin,
TbFolderStar,
} from 'react-icons/tb';
import { OffsetMode, OntimeEvent, OntimeGroup, TimerPhase, TimerType } from 'ontime-types';
import { dayInMs, isPlaybackActive, millisToString } from 'ontime-utils';
import Tooltip from '../../../common/components/tooltip/Tooltip';
import {
useClock,
useCurrentGroupId,
useFlagTimerOverView,
useGroupTimerOverView,
useNextFlag,
useOffsetOverview,
useProgressOverview,
useRundownOverview,
useTimer,
} from '../../../common/hooks/useSocket';
import { useEntry } from '../../../common/hooks-query/useRundown';
import { getOffsetState, getOffsetText } from '../../../common/utils/offset';
import { cx, enDash, timerPlaceholder } from '../../../common/utils/styleUtils';
import { formatTime } from '../../../common/utils/time';
import { calculateEndAndDaySpan, formatDueTime, formattedTime } from '../overview.utils';
import { OverUnder, TimeColumn } from './TimeLayout';
import style from './TimeElements.module.scss';
export function StartTimes() {
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRundownOverview();
const plannedStartText = plannedStart === null ? timerPlaceholder : formatTime(plannedStart);
const [maybePlannedEnd, maybePlannedDaySpan] = useMemo(() => calculateEndAndDaySpan(plannedEnd), [plannedEnd]);
const [maybeExpectedEnd, maybeExpectedDaySpan] = useMemo(() => calculateEndAndDaySpan(expectedEnd), [expectedEnd]);
const plannedEndText = maybePlannedEnd === null ? timerPlaceholder : formatTime(maybePlannedEnd);
return (
Start
} />
{plannedStartText}
} />
{formattedTime(actualStart)}
End
} />
{maybePlannedDaySpan > 0 ? (
}
>
{plannedEndText}
) : (
{plannedEndText}
)}
} />
{maybeExpectedEnd !== null && maybeExpectedDaySpan > 0 ? (
}
>
{formattedTime(maybeExpectedEnd)}
) : (
{formattedTime(maybeExpectedEnd)}
)}
);
}
export function MetadataTimes() {
return (
);
}
function GroupTimes() {
const { clock, groupExpectedEnd, actualGroupStart, mode, playback, currentDay } = useGroupTimerOverView();
const { currentGroupId } = useCurrentGroupId();
const group = useEntry(currentGroupId) as OntimeGroup | null;
const active = isPlaybackActive(playback);
// the group end time dose not encode any day offsets so it is calculated with group start time and duration
const plannedGroupEnd = (() => {
if (!active) return null;
if (!group || group.timeStart === null) return null;
const normalizedClock = clock + currentDay * dayInMs;
return mode === OffsetMode.Absolute
? group.timeStart + group.duration - normalizedClock
: actualGroupStart + group.duration - normalizedClock;
})();
const plannedTimeUntilGroupEnd = formatDueTime(plannedGroupEnd, 3, TimerType.CountDown);
const expectedGroupEnd = groupExpectedEnd !== null ? groupExpectedEnd - clock : null;
const expectedTimeUntilGroupEnd = formatDueTime(expectedGroupEnd, 3, TimerType.CountDown);
const groupTitle = group?.title ?? null;
return (
{`${groupTitle || 'Group'} `}
} />
{plannedTimeUntilGroupEnd}
} />
{expectedTimeUntilGroupEnd}
);
}
function FlagTimes() {
const { clock, mode, actualStart, plannedStart, playback, currentDay } = useFlagTimerOverView();
const { id, expectedStart } = useNextFlag();
const entry = useEntry(id) as OntimeEvent | null;
const active = isPlaybackActive(playback);
const plannedFlagStart = (() => {
if (!active) return null;
if (!entry) return null;
const normalizedTimeStart = entry.timeStart + entry.dayOffset * dayInMs;
const normalizedClock = clock + currentDay * dayInMs;
return mode === OffsetMode.Absolute
? normalizedTimeStart - normalizedClock
: normalizedTimeStart + actualStart - plannedStart - normalizedClock;
})();
const plannedTimeUntilDisplay = formatDueTime(plannedFlagStart, 3, TimerType.CountDown);
const expectedTimeUntil = expectedStart !== null ? expectedStart - clock : null;
const expectedTimeUntilDisplay = formatDueTime(expectedTimeUntil, 3, TimerType.CountDown);
const title = entry?.title ?? null;
return (
{`${title || 'Flag'} `}
} />
{plannedTimeUntilDisplay}
} />
{expectedTimeUntilDisplay}
);
}
export function ProgressOverview() {
const { numEvents, selectedEventIndex } = useProgressOverview();
const current = selectedEventIndex !== null ? selectedEventIndex + 1 : enDash;
const progressText = numEvents ? `${current} of ${numEvents || enDash}` : enDash;
return ;
}
export function OffsetOverview() {
const { offset, playback } = useOffsetOverview();
const isPlaying = isPlaybackActive(playback);
const offsetState = getOffsetState(isPlaying ? offset : null);
const offsetText = getOffsetText(isPlaying ? offset : null);
return ;
}
export function ClockOverview({ className }: { className?: string }) {
const { clock } = useClock();
const formattedClock = formatTime(clock);
return ;
}
export function TimerOverview({ className }: { className?: string }) {
const timer = useTimer();
const isWaiting = timer.phase === TimerPhase.Pending;
const title = isWaiting ? 'Count to start' : 'Running timer';
const display = millisToString(isWaiting ? timer.secondaryTimer : timer.current, { fallback: timerPlaceholder });
const timerState = (() => {
if (isWaiting) return 'waiting';
if (timer.current === null) return 'muted';
return 'active';
})();
return ;
}