mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 06:59:09 +00:00
feat: propper timeuntil calc in countdown view (#1765)
* fix error with is linked to loaded metadata * pass down metadata * create expected schedule * expected start in sub info * for singel subscribtions * refactor * add color to single events * refactor: clean up subscribtion data * show schedule and secondray data for single events * improve subscribtion data display
This commit is contained in:
@@ -1,32 +1,37 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { IoPencil } from 'react-icons/io5';
|
||||
import { EntryId, OntimeEvent } from 'ontime-types';
|
||||
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 {
|
||||
useCountdownSocket,
|
||||
useCurrentDay,
|
||||
usePlayback,
|
||||
useRuntimeOffset,
|
||||
useSelectedEventId,
|
||||
} from '../../common/hooks/useSocket';
|
||||
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 { useTranslation } from '../../translation/TranslationProvider';
|
||||
|
||||
import { useCountdownOptions } from './countdown.options';
|
||||
import { getIsLive, getSubscriptionDisplayData, sanitiseTitle, timerProgress } from './countdown.utils';
|
||||
import {
|
||||
CountdownEvent,
|
||||
extendEventData,
|
||||
getIsLive,
|
||||
isOutsideRange,
|
||||
preferredFormat12,
|
||||
preferredFormat24,
|
||||
useSubscriptionDisplayData,
|
||||
} from './countdown.utils';
|
||||
|
||||
import './Countdown.scss';
|
||||
|
||||
interface CountdownSubscriptionsProps {
|
||||
subscribedEvents: OntimeEvent[];
|
||||
subscribedEvents: ExtendedEntry<OntimeEvent>[];
|
||||
goToEditMode: () => void;
|
||||
}
|
||||
|
||||
@@ -36,6 +41,9 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode
|
||||
const { selectedEventId } = useSelectedEventId();
|
||||
const showFab = useFadeOutOnInactivity(true);
|
||||
|
||||
const { data: reportData } = useReport();
|
||||
const { offset, currentDay, actualStart, plannedStart, mode } = useExpectedStartData();
|
||||
|
||||
const timeoutId = useRef<NodeJS.Timeout | null>(null);
|
||||
const [lockAutoScroll, setLockAutoScroll] = useState(false);
|
||||
const selectedRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -92,23 +100,19 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode
|
||||
{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 (
|
||||
<div key={event.id} ref={isLive ? selectedRef : undefined} className={cx(['sub', isLive && 'sub--live'])}>
|
||||
<div
|
||||
key={event.id}
|
||||
ref={isLive ? selectedRef : undefined}
|
||||
className={cx(['sub', isLive && 'sub--live', isArmed && 'sub--armed'])}
|
||||
>
|
||||
<div className='sub__binder' style={{ '--user-color': event.colour }} />
|
||||
<div className={cx(['sub__schedule', event.delay > 0 && 'sub__schedule--delayed'])}>
|
||||
{showExpected ? (
|
||||
<ExpectedSchedule timeStart={event.timeStart} timeEnd={event.timeEnd} delay={event.delay} />
|
||||
) : (
|
||||
<>
|
||||
<ClockTime value={event.timeStart + event.delay} preferredFormat12='h:mm' preferredFormat24='HH:mm' />
|
||||
→
|
||||
<ClockTime value={event.timeEnd + event.delay} preferredFormat12='h:mm' preferredFormat24='HH:mm' />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<SubscriptionStatus key={event.id} event={event} selectedEventId={selectedEventId} />
|
||||
<div className={cx(['sub__title', !event.title && 'subdued'])}>{sanitiseTitle(event.title)}</div>
|
||||
<ScheduleTime event={countdownEvent} showExpected={showExpected} />
|
||||
<SubscriptionStatus event={countdownEvent} />
|
||||
<div className={cx(['sub__title', !event.title && 'subdued'])}>{title}</div>
|
||||
{secondaryData && <div className='sub__secondary'>{secondaryData}</div>}
|
||||
</div>
|
||||
);
|
||||
@@ -123,63 +127,82 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode
|
||||
);
|
||||
}
|
||||
|
||||
interface ExpectedScheduleProps {
|
||||
timeStart: number;
|
||||
timeEnd: number;
|
||||
delay: number;
|
||||
}
|
||||
function ExpectedSchedule(props: ExpectedScheduleProps) {
|
||||
const { timeStart, timeEnd, delay } = props;
|
||||
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 { offset } = useRuntimeOffset();
|
||||
const plannedStart = timeStart + delay + event.dayOffset * dayInMs;
|
||||
|
||||
// offset is negative if we are ahead
|
||||
const expectedOffset = offset - delay;
|
||||
const expectedState = getOffsetState(expectedOffset);
|
||||
// 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 (
|
||||
<>
|
||||
<div className='sub__schedule'>
|
||||
<ClockTime
|
||||
value={timeStart - expectedOffset}
|
||||
className={`sub__schedule--${expectedState}`}
|
||||
preferredFormat12='h:mm'
|
||||
preferredFormat24='HH:mm'
|
||||
value={plannedStart}
|
||||
preferredFormat12={preferredFormat12}
|
||||
preferredFormat24={preferredFormat24}
|
||||
className={plannedStateClass}
|
||||
/>
|
||||
→
|
||||
<ClockTime value={timeEnd - expectedOffset} preferredFormat12='h:mm' preferredFormat24='HH:mm' />
|
||||
</>
|
||||
{!isExpectedValueShow && (
|
||||
<>
|
||||
→
|
||||
<ClockTime
|
||||
value={plannedEnd}
|
||||
preferredFormat12={preferredFormat12}
|
||||
preferredFormat24={preferredFormat24}
|
||||
className={plannedStateClass}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{isExpectedValueShow && (
|
||||
<>
|
||||
<ClockTime
|
||||
value={expectedStart}
|
||||
className={expectedStateClass}
|
||||
preferredFormat12={preferredFormat12}
|
||||
preferredFormat24={preferredFormat24}
|
||||
/>
|
||||
→
|
||||
<ClockTime
|
||||
value={expectedEnd}
|
||||
className={expectedEndClass}
|
||||
preferredFormat12={preferredFormat12}
|
||||
preferredFormat24={preferredFormat24}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SubscriptionStatusProps {
|
||||
event: OntimeEvent;
|
||||
selectedEventId: EntryId | null;
|
||||
event: ExtendedEntry<OntimeEvent> & { endedAt: MaybeNumber; expectedStart: number };
|
||||
}
|
||||
|
||||
function SubscriptionStatus({ event, selectedEventId }: SubscriptionStatusProps) {
|
||||
const { getLocalizedString } = useTranslation();
|
||||
const { currentDay } = useCurrentDay();
|
||||
const { offset } = useRuntimeOffset();
|
||||
const { showExpected } = useCountdownOptions();
|
||||
const { playback, current, clock } = useCountdownSocket();
|
||||
|
||||
// TODO: use reporter values as in the event block chip
|
||||
const { status, timer } = getSubscriptionDisplayData(
|
||||
current,
|
||||
playback,
|
||||
clock,
|
||||
event,
|
||||
selectedEventId,
|
||||
offset,
|
||||
currentDay,
|
||||
getLocalizedString('common.minutes'),
|
||||
showExpected,
|
||||
);
|
||||
function SubscriptionStatus({ event }: SubscriptionStatusProps) {
|
||||
const { status, statusDisplay, timeDisplay } = useSubscriptionDisplayData(event);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='sub__status'>{getLocalizedString(timerProgress[status])}</div>
|
||||
<div className='sub__timer'>{timer}</div>
|
||||
<div className='sub__status'>{statusDisplay}</div>
|
||||
{status === 'done' ? (
|
||||
<SuperscriptTime className='sub__timer' time={timeDisplay} />
|
||||
) : (
|
||||
<div className='sub__timer'>{timeDisplay}</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user