diff --git a/apps/client/src/common/components/schedule-time/ScheduleTime.scss b/apps/client/src/common/components/schedule-time/ScheduleTime.scss new file mode 100644 index 000000000..dc207a96c --- /dev/null +++ b/apps/client/src/common/components/schedule-time/ScheduleTime.scss @@ -0,0 +1,26 @@ +@use '@/theme/viewerDefs' as *; + +.schedule__ { + padding-top: 0.5rem; + display: flex; + gap: 0.25em; + font-size: $timer-label-size; + color: var(--label-color-override, $timer-color); +} + +.schedule__delayed { + color: $ontime-delay-text; +} + +.schedule__strike { + text-decoration: line-through; + color: var(--label-color-override, $viewer-label-color); +} + +.schedule__over { + color: $playback-over; +} + +.schedule__under { + color: $playback-under; +} diff --git a/apps/client/src/common/components/schedule-time/ScheduleTime.tsx b/apps/client/src/common/components/schedule-time/ScheduleTime.tsx new file mode 100644 index 000000000..19d6d4f5c --- /dev/null +++ b/apps/client/src/common/components/schedule-time/ScheduleTime.tsx @@ -0,0 +1,88 @@ +import { OntimeEvent } from 'ontime-types'; +import { dayInMs, MILLIS_PER_MINUTE } from 'ontime-utils'; + +import ClockTime from '../../../views/common/clock-time/ClockTime'; +import { getOffsetState } from '../../utils/offset'; +import { ExtendedEntry } from '../../utils/rundownMetadata'; + +import './ScheduleTime.scss'; +import { cx } from '../../utils/styleUtils'; + +type ScheduleTimeProps = { + event: ExtendedEntry; + showExpected: boolean; + className?: string; + preferredFormat12?: string; + preferredFormat24?: string; + expectedStart: number; +}; + +//TODO: consider relative mode +export default function ScheduleTime(props: ScheduleTimeProps) { + const { + event, + showExpected, + className, + preferredFormat12 = 'h:mm a', + preferredFormat24 = 'HH:mm', + expectedStart, + } = props; + const { timeStart, duration, delay, countToEnd } = event; + + const plannedStart = timeStart + delay + event.dayOffset * dayInMs; + + // only show new expected value if outside range of the planned value + const isExpectedValueShow = showExpected && isOutsideRange(plannedStart, expectedStart); + + const plannedStateClass = isExpectedValueShow ? 'schedule__strike' : delay !== 0 ? 'schedule__delayed' : ''; + + const expectedOffsetState = getOffsetState(expectedStart - plannedStart); + const expectedStateClass = expectedOffsetState ? `schedule__${expectedOffsetState}` : ''; + const plannedEnd = plannedStart + duration; + const expectedEnd = countToEnd ? Math.max(expectedStart + duration, plannedEnd) : expectedStart + duration; + const expectedEndOffsetState = getOffsetState(expectedEnd - plannedEnd); + const expectedEndClass = expectedEndOffsetState ? `schedule__${expectedEndOffsetState}` : ''; + + return ( +
+ + {!isExpectedValueShow && ( + <> + → + + + )} + {isExpectedValueShow && ( + <> + + → + + + )} +
+ ); +} + +function isOutsideRange(a: number, b: number): boolean { + return Math.abs(a - b) > MILLIS_PER_MINUTE; +} diff --git a/apps/client/src/views/countdown/Countdown.scss b/apps/client/src/views/countdown/Countdown.scss index 6c32000d9..69c0ea3f2 100644 --- a/apps/client/src/views/countdown/Countdown.scss +++ b/apps/client/src/views/countdown/Countdown.scss @@ -1,4 +1,5 @@ @use '@/theme/viewerDefs' as *; +@use 'sass:color'; $item-height: 3.5rem; @@ -118,7 +119,8 @@ $item-height: 3.5rem; } .sub--live { - background-color: $active-green; + // we need to dim this if the schedule times are ahead of schedule they will be green and diaper in a full green background + background-color: color.adjust($active-green, $lightness: -10%); } .sub--armed { @@ -132,6 +134,10 @@ $item-height: 3.5rem; .sub__schedule { grid-area: schedule; + } + + .sub__schedule-inline { + grid-area: schedule; padding-top: 0.5rem; display: flex; @@ -140,22 +146,6 @@ $item-height: 3.5rem; color: var(--label-color-override, $viewer-label-color); } - .sub__schedule--delayed { - color: $ontime-delay-text; - } - - .sub__schedule--strike { - text-decoration: line-through; - } - - .sub__schedule--over { - color: $playback-over; - } - - .sub__schedule--under { - color: $playback-under; - } - .sub__title { grid-area: title; padding-bottom: 0.5rem; diff --git a/apps/client/src/views/countdown/CountdownSelect.tsx b/apps/client/src/views/countdown/CountdownSelect.tsx index c36ce0fd8..95e87cb58 100644 --- a/apps/client/src/views/countdown/CountdownSelect.tsx +++ b/apps/client/src/views/countdown/CountdownSelect.tsx @@ -71,7 +71,7 @@ export default function CountdownSelect({ events, subscriptions, disableEdit }: className={cx(['sub', isSelected && 'sub--selected'])} >
-
+
diff --git a/apps/client/src/views/countdown/CountdownSubscriptions.tsx b/apps/client/src/views/countdown/CountdownSubscriptions.tsx index 408dc3341..1ca71b6d2 100644 --- a/apps/client/src/views/countdown/CountdownSubscriptions.tsx +++ b/apps/client/src/views/countdown/CountdownSubscriptions.tsx @@ -1,33 +1,24 @@ import { MaybeNumber, OntimeEvent } from 'ontime-types'; -import { dayInMs } from 'ontime-utils'; +import { getExpectedStart } from 'ontime-utils'; import { useEffect, useRef, useState } from 'react'; import { IoPencil } from 'react-icons/io5'; import Button from '../../common/components/buttons/Button'; +import ScheduleTime from '../../common/components/schedule-time/ScheduleTime'; import useReport from '../../common/hooks-query/useReport'; import { useFadeOutOnInactivity } from '../../common/hooks/useFadeOutOnInactivity'; import useFollowComponent from '../../common/hooks/useFollowComponent'; import { useExpectedStartData, usePlayback, useSelectedEventId } from '../../common/hooks/useSocket'; -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 '../common/clock-time/ClockTime'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import { getPropertyValue } from '../common/viewUtils'; import { useCountdownOptions } from './countdown.options'; -import { - CountdownEvent, - extendEventData, - getIsLive, - isOutsideRange, - preferredFormat12, - preferredFormat24, - useSubscriptionDisplayData, -} from './countdown.utils'; import './Countdown.scss'; +import { getIsLive, useSubscriptionDisplayData } from './countdown.utils'; interface CountdownSubscriptionsProps { subscribedEvents: ExtendedEntry[]; @@ -35,6 +26,7 @@ interface CountdownSubscriptionsProps { } export default function CountdownSubscriptions({ subscribedEvents, goToEditMode }: CountdownSubscriptionsProps) { + 'use memo'; const { mainSource, secondarySource, showExpected } = useCountdownOptions(); const playback = usePlayback(); const selectedEventId = useSelectedEventId(); @@ -101,7 +93,17 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode 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 { totalGap, isLinkedToLoaded } = event; + const expectedStart = getExpectedStart(event, { + currentDay, + totalGap, + actualStart, + plannedStart, + isLinkedToLoaded, + offset, + mode, + }); + const { endedAt } = reportData[event.id] ?? { endedAt: null }; const displayTitle = getPropertyValue(event, mainSource ?? 'title'); return (
- - + +
{displayTitle}
{secondaryData &&
{secondaryData}
}
@@ -128,73 +135,15 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode ); } -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 }; + event: ExtendedEntry; + endedAt: MaybeNumber; + expectedStart: number; } -function SubscriptionStatus({ event }: SubscriptionStatusProps) { - const { status, statusDisplay, timeDisplay } = useSubscriptionDisplayData(event); +function SubscriptionStatus({ event, endedAt, expectedStart }: SubscriptionStatusProps) { + 'use memo'; + const { status, statusDisplay, timeDisplay } = useSubscriptionDisplayData(event, endedAt, expectedStart); return ( <> diff --git a/apps/client/src/views/countdown/SingleEventCountdown.scss b/apps/client/src/views/countdown/SingleEventCountdown.scss index 49b6b3dc0..7744b73b7 100644 --- a/apps/client/src/views/countdown/SingleEventCountdown.scss +++ b/apps/client/src/views/countdown/SingleEventCountdown.scss @@ -7,27 +7,6 @@ gap: 5rem; } -.event__title { - background-color: var(--card-background-color-override, $viewer-card-bg-color); - padding: $view-card-padding; - border-radius: $element-border-radius; - font-size: clamp(40px, 4.5vw, 80px); - line-height: 1.1; - text-align: center; - border-left: 0.25em solid; - border-color: var(--card-background-color-override, $viewer-card-bg-color); - - .secondary { - text-align: left; - font-size: $title-font-size; - } - - .sub__schedule { - color: inherit; - font-size: $base-font-size; - } -} - .event__status { color: var(--secondary-color-override, $viewer-secondary-color); font-size: $header-font-size; diff --git a/apps/client/src/views/countdown/SingleEventCountdown.tsx b/apps/client/src/views/countdown/SingleEventCountdown.tsx index d567d3263..243e188b8 100644 --- a/apps/client/src/views/countdown/SingleEventCountdown.tsx +++ b/apps/client/src/views/countdown/SingleEventCountdown.tsx @@ -3,6 +3,7 @@ import { getExpectedStart } from 'ontime-utils'; import { IoPencil } from 'react-icons/io5'; import Button from '../../common/components/buttons/Button'; +import TitleCard from '../../common/components/title-card/TitleCard'; import useReport from '../../common/hooks-query/useReport'; import { useFadeOutOnInactivity } from '../../common/hooks/useFadeOutOnInactivity'; import { useExpectedStartData } from '../../common/hooks/useSocket'; @@ -11,10 +12,9 @@ import { cx } from '../../common/utils/styleUtils'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import { getPropertyValue } from '../common/viewUtils'; import { useCountdownOptions } from './countdown.options'; -import { useSubscriptionDisplayData } from './countdown.utils'; -import { ScheduleTime } from './CountdownSubscriptions'; import './SingleEventCountdown.scss'; +import { useSubscriptionDisplayData } from './countdown.utils'; interface SingleEventCountdownProps { subscribedEvent: ExtendedEntry; @@ -39,19 +39,22 @@ export default function SingleEventCountdown({ subscribedEvent, goToEditMode }: }); const { endedAt } = reportData[subscribedEvent.id] ?? { endedAt: null }; - const countdownEvent = { ...subscribedEvent, expectedStart, endedAt }; - const titleTmp = getPropertyValue(subscribedEvent, mainSource ?? 'title'); - const title = titleTmp?.length ? titleTmp : ' '; // insert utf-8 empty space to avoid the line collapsing; + const title = getPropertyValue(subscribedEvent, mainSource ?? 'title'); const secondaryData = getPropertyValue(subscribedEvent, secondarySource); return (
- -
- - {title} - {secondaryData &&
{secondaryData}
} -
+ +