import { useEffect, useRef, useState } from 'react'; import { IoPencil } from 'react-icons/io5'; import { MaybeNumber, OntimeEvent } from 'ontime-types'; import { dayInMs } from 'ontime-utils'; import Button from '../../common/components/buttons/Button'; import { useFadeOutOnInactivity } from '../../common/hooks/useFadeOutOnInactivity'; import useFollowComponent from '../../common/hooks/useFollowComponent'; import { useExpectedStartData, usePlayback, useSelectedEventId } from '../../common/hooks/useSocket'; import useReport from '../../common/hooks-query/useReport'; import { getOffsetState } from '../../common/utils/offset'; import { ExtendedEntry } from '../../common/utils/rundownMetadata'; import { cx } from '../../common/utils/styleUtils'; import { throttle } from '../../common/utils/throttle'; import FollowButton from '../../features/operator/follow-button/FollowButton'; import ClockTime from '../../features/viewers/common/clock-time/ClockTime'; import SuperscriptTime from '../../features/viewers/common/superscript-time/SuperscriptTime'; import { getPropertyValue } from '../../features/viewers/common/viewUtils'; import { useCountdownOptions } from './countdown.options'; import { CountdownEvent, extendEventData, getIsLive, isOutsideRange, preferredFormat12, preferredFormat24, useSubscriptionDisplayData, } from './countdown.utils'; import './Countdown.scss'; interface CountdownSubscriptionsProps { subscribedEvents: ExtendedEntry[]; goToEditMode: () => void; } export default function CountdownSubscriptions({ subscribedEvents, goToEditMode }: CountdownSubscriptionsProps) { const { secondarySource, showExpected } = useCountdownOptions(); const { playback } = usePlayback(); const { selectedEventId } = useSelectedEventId(); const showFab = useFadeOutOnInactivity(true); const { data: reportData } = useReport(); const { offset, currentDay, actualStart, plannedStart, mode } = useExpectedStartData(); const timeoutId = useRef(null); const [lockAutoScroll, setLockAutoScroll] = useState(false); const selectedRef = useRef(null); const scrollRef = useRef(null); const scrollToComponent = useFollowComponent({ followRef: selectedRef, scrollRef, doFollow: !lockAutoScroll, topOffset: 0, followTrigger: selectedEventId, }); // reset scroll if nothing is selected useEffect(() => { if (!selectedEventId) { if (!lockAutoScroll) { scrollRef.current?.scrollTo(0, 0); } } }, [selectedEventId, lockAutoScroll, scrollRef]); // scroll to component if user clicks the Follow button const handleOffset = () => { if (selectedEventId) { scrollToComponent(); } setLockAutoScroll(false); }; // prevent considering automated scrolls as user scrolls const handleUserScroll = () => { if (selectedRef?.current && scrollRef?.current) { const selectedRect = selectedRef.current.getBoundingClientRect(); const scrollerRect = scrollRef.current.getBoundingClientRect(); if (selectedRect && scrollerRect) { const distanceFromTop = selectedRect.top - scrollerRect.top; const hasScrolledOutOfThreshold = distanceFromTop < -8 || distanceFromTop > 50; setLockAutoScroll(hasScrolledOutOfThreshold); } } }; const throttledHandleScroll = throttle(handleUserScroll, 1000); // when the user scrolls we check if we need to show the button const handleScroll = () => { if (timeoutId.current) { clearTimeout(timeoutId.current); } throttledHandleScroll(); }; return (
{subscribedEvents.map((event) => { const secondaryData = getPropertyValue(event, secondarySource); const isLive = getIsLive(event.id, selectedEventId, playback); const isArmed = !isLive && event.id === selectedEventId; const countdownEvent = extendEventData(event, currentDay, actualStart, plannedStart, offset, mode, reportData); const title = event.title.length ? event.title : ' '; // insert utf-8 empty space to avoid the line collapsing return (
{title}
{secondaryData &&
{secondaryData}
}
); })}
); } type ScheduleTimeProps = { event: CountdownEvent; showExpected: boolean; }; //TODO: consider relative mode export function ScheduleTime(props: ScheduleTimeProps) { const { event, showExpected } = props; const { timeStart, duration, delay, expectedStart, countToEnd } = event; const plannedStart = timeStart + delay + event.dayOffset * dayInMs; // only show new exacted value if outside range of the planned value const isExpectedValueShow = showExpected && isOutsideRange(plannedStart, expectedStart); const plannedStateClass = isExpectedValueShow ? 'sub__schedule--strike' : delay !== 0 ? 'sub__schedule--delayed' : ''; const expectedStateClass = `sub__schedule--${getOffsetState(expectedStart - plannedStart)}`; const plannedEnd = plannedStart + duration + delay; const expectedEnd = countToEnd ? Math.max(expectedStart + duration, plannedEnd) : expectedStart + duration; const expectedEndClass = `sub__schedule--${getOffsetState(expectedEnd - plannedEnd)}`; return (
{!isExpectedValueShow && ( <> → )} {isExpectedValueShow && ( <> )}
); } interface SubscriptionStatusProps { event: ExtendedEntry & { endedAt: MaybeNumber; expectedStart: number }; } function SubscriptionStatus({ event }: SubscriptionStatusProps) { const { status, statusDisplay, timeDisplay } = useSubscriptionDisplayData(event); return ( <>
{statusDisplay}
{status === 'done' ? ( ) : (
{timeDisplay}
)} ); }