diff --git a/packages/webui/src/client/collections/lib.ts b/packages/webui/src/client/collections/lib.ts index 0b0aa7d8ef6..49d1053f6df 100644 --- a/packages/webui/src/client/collections/lib.ts +++ b/packages/webui/src/client/collections/lib.ts @@ -10,7 +10,7 @@ import { PeripheralDevicePubSubCollections, PeripheralDevicePubSubCollectionsNames, } from '@sofie-automation/shared-lib/dist/pubsub/peripheralDevice' -import { +import type { MongoCollection, MongoReadOnlyCollection, MongoCursor, @@ -22,7 +22,20 @@ import { import { CustomCollectionName as CustomCorelibCollectionName } from '@sofie-automation/corelib/dist/dataModel/Collections' import { CorelibPubSubCustomCollections } from '@sofie-automation/corelib/dist/pubsub' -export * from '@sofie-automation/meteor-lib/dist/collections/lib' +export type { + FieldNames, + FindOneOptions, + FindOptions, + IndexSpecifier, + MongoCollection, + MongoCursor, + MongoLiveQueryHandle, + MongoReadOnlyCollection, + ObserveCallbacks, + ObserveChangesCallbacks, + UpdateOptions, + UpsertOptions, +} from '@sofie-automation/meteor-lib/dist/collections/lib' export const ClientCollections = new Map | WrappedMongoReadOnlyCollection>() function registerClientCollection( diff --git a/packages/webui/src/client/lib/Components/OverUnderChip.scss b/packages/webui/src/client/lib/Components/OverUnderChip.scss new file mode 100644 index 00000000000..33302864636 --- /dev/null +++ b/packages/webui/src/client/lib/Components/OverUnderChip.scss @@ -0,0 +1,49 @@ +@import '../../styles/colorScheme'; + +.over-under-chip { + font-family: Roboto Flex; + display: inline-block; + border-radius: 999px; + white-space: nowrap; + letter-spacing: -0.02em; + font-variant-numeric: tabular-nums; + color: #000; + + padding: var(--overUnderChipPaddingY, 0.05em) var(--overUnderChipPaddingX, 0.25em); + margin-left: var(--overUnderChipMarginLeft, 0em); + margin-top: var(--overUnderChipMarginTop, 0em); + line-height: var(--overUnderChipLineHeight, 1); + + font-variation-settings: + 'wdth' var(--overUnderChipWdth, 25), + 'wght' var(--overUnderChipWght, 600), + 'slnt' var(--overUnderChipSlnt, 0), + 'GRAD' var(--overUnderChipGrad, 0), + 'opsz' var(--overUnderChipOpsz, 14), + 'XOPQ' var(--overUnderChipXopq, 96), + 'XTRA' var(--overUnderChipXtra, 468), + 'YOPQ' var(--overUnderChipYopq, 79), + 'YTAS' var(--overUnderChipYtas, 750), + 'YTFI' var(--overUnderChipYtfi, 738), + 'YTLC' var(--overUnderChipYtlc, 548), + 'YTDE' var(--overUnderChipYtde, -203), + 'YTUC' var(--overUnderChipYtuc, 712); + + &.over-under-chip--over { + --overUnderChipWght: 700; + background-color: $general-late-color; + } + + &.over-under-chip--under { + --overUnderChipWght: 500; + background-color: #ff0; // Should probably be changed to $general-fast-color; + } +} + +// Optional preset for when the chip is used as a large screen overlay. +.over-under-chip--overlay { + --overUnderChipWght: 700; + --overUnderChipOpsz: 20; + --overUnderChipYopq: 92; + --overUnderChipYtlc: 514; +} diff --git a/packages/webui/src/client/lib/Components/OverUnderChip.tsx b/packages/webui/src/client/lib/Components/OverUnderChip.tsx new file mode 100644 index 00000000000..5f57cdc34d6 --- /dev/null +++ b/packages/webui/src/client/lib/Components/OverUnderChip.tsx @@ -0,0 +1,72 @@ +import { type CSSProperties } from 'react' +import classNames from 'classnames' +import { RundownUtils } from '../rundown.js' +import './OverUnderChip.scss' +import { useTiming } from '../../ui/RundownView/RundownTiming/withTiming.js' +import { getPlaylistTimingDiff } from '../rundownTiming.js' +import { DBRundownPlaylist } from '@sofie-automation/corelib/src/dataModel/RundownPlaylist/RundownPlaylist.js' + +export type OverUnderChipFormat = 'playlistDiff' | 'timerPostfix' + +type OverUnderChipBaseProps = { + className?: string + style?: CSSProperties + format?: OverUnderChipFormat +} + +type OverUnderChipValueProps = + | { + valueMs: number | undefined + rundownPlaylist?: never + } + | { + valueMs?: never + rundownPlaylist: DBRundownPlaylist + } + +type OverUnderChipInnerProps = OverUnderChipBaseProps & { valueMs: number | undefined } + +/** + * Over/under "chip" display. + * Can either take a direct `valueMs` or a `rundownPlaylist` (requires RundownTiming context). + */ +export function OverUnderChip(props: Readonly): JSX.Element | null { + if ('valueMs' in props) { + return + } else { + return + } +} + +function OverUnderChipFromPlaylist( + props: Readonly +): JSX.Element | null { + const timingDurations = useTiming() + const valueMs = getPlaylistTimingDiff(props.rundownPlaylist, timingDurations) ?? 0 + return +} + +function OverUnderChipInner({ valueMs, format = 'playlistDiff', className, style }: Readonly) { + if (valueMs === undefined) return null + + const isUnder = valueMs <= 0 + const timeStr = (() => { + switch (format) { + case 'timerPostfix': + return RundownUtils.formatDiffToTimecode(Math.abs(valueMs), false, false, true, false, true) + case 'playlistDiff': + default: + return RundownUtils.formatDiffToTimecode(Math.abs(valueMs), false, false, true, true, true) + } + })() + + return ( + + {isUnder ? '−' : '+'} + {timeStr} + + ) +} diff --git a/packages/webui/src/client/styles/_colorScheme.scss b/packages/webui/src/client/styles/_colorScheme.scss index eea945a755c..603a0dd3bc3 100644 --- a/packages/webui/src/client/styles/_colorScheme.scss +++ b/packages/webui/src/client/styles/_colorScheme.scss @@ -19,6 +19,7 @@ $general-live-remote-color: var(--general-live-remote-color); $general-live-guest-color: var(--general-live-guest-color); $general-late-color: var(--general-late-color); +$over-under-over-color: var(--over-under-over-color); $general-fast-color: var(--general-fast-color); $general-fast-color--shadow: var(--general-fast-color--shadow); $general-countdown-to-next-color: var(--general-countdown-to-next-color); diff --git a/packages/webui/src/client/styles/countdown/director.scss b/packages/webui/src/client/styles/countdown/director.scss index 0c034624ee1..8de4093591a 100644 --- a/packages/webui/src/client/styles/countdown/director.scss +++ b/packages/webui/src/client/styles/countdown/director.scss @@ -17,6 +17,13 @@ $hold-status-color: $liveline-timecode-color; font-family: Roboto Flex; font-style: normal; + // Over/under timer overlay (reuses `screen-timing-clock` from prompter view) + // Ensure it layers above the fixed top bar. + .screen-timing-clock { + z-index: 2000; + font-size: 9vmin; + } + .director-screen__top { position: fixed; top: 0; @@ -35,24 +42,19 @@ $hold-status-color: $liveline-timecode-color; padding: 0 0.2em; text-transform: uppercase; - .director-screen__top__planned-end { - text-align: left; + .director-screen__top__spacer { + flex-grow: 4; } - .director-screen__top__time-to { - text-align: center; + .director-screen__top__planned-end { + flex-grow: 2; + text-align: left; } + .director-screen__top__center, .director-screen__top__planned-to { text-align: center; } - .director-screen__top__planned-since { - margin-left: -50px; - } - - .director-screen__top__over-under { - margin-left: 5vw; - } } .director-screen__body { @@ -208,14 +210,14 @@ $hold-status-color: $liveline-timecode-color; text-align: center; width: 100vw; margin-left: -13vw; - + .director-screen__body__part__timeto-name { color: #888; font-size: 9.63em; text-transform: uppercase; margin-top: -2vh; } - + .director-screen__body__part__timeto-countdown { margin-top: 4vh; grid-row: inherit; @@ -480,75 +482,13 @@ $hold-status-color: $liveline-timecode-color; .clocks-counter-heavy { font-weight: 600; } - - .director-screen__body__t-timer { - position: absolute; - bottom: 0; - right: 0; - text-align: right; - font-size: 5vh; - z-index: 10; - line-height: 1; - - .t-timer-display { - display: flex; - align-items: stretch; - justify-content: flex-end; - font-weight: 500; - background: #333; - border-radius: 0; - overflow: hidden; - - &__label { - display: flex; - align-items: center; - color: #fff; - padding-left: 0.4em; - padding-right: 0.2em; - font-size: 1em; - text-transform: uppercase; - letter-spacing: 0.05em; - font-stretch: condensed; - } - - &__value { - display: flex; - align-items: center; - color: #fff; - font-variant-numeric: tabular-nums; - padding: 0 0.2em; - font-size: 1em; - - .t-timer-display__part { - &--dimmed { - color: #aaa; - } - } - } - - &__over-under { - display: flex; - align-items: center; - justify-content: center; - margin: 0 0 0 0.2em; - font-size: 1em; - font-variant-numeric: tabular-nums; - padding: 0 0.4em; - line-height: 1.1; - min-width: 3.5em; - border-radius: 1em; - - &--over { - background-color: $general-late-color; - color: #fff; - } - - &--under { - background-color: #ffe900; - color: #000; - } - } - } - } + } + .director-screen__bottom-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 20; + font-size: 4.58vh; } } diff --git a/packages/webui/src/client/styles/countdown/presenter.scss b/packages/webui/src/client/styles/countdown/presenter.scss index df9a20d66a0..82ee8a483c7 100644 --- a/packages/webui/src/client/styles/countdown/presenter.scss +++ b/packages/webui/src/client/styles/countdown/presenter.scss @@ -15,10 +15,7 @@ $hold-status-color: $liveline-timecode-color; .presenter-screen { position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; font-size: 1vh; @@ -28,6 +25,18 @@ $hold-status-color: $liveline-timecode-color; overflow: hidden; white-space: nowrap; + // Over/under timer (reuses `screen-timing-clock` class from prompter view), + // but presenter view has a tiny base font-size (1vh), so we need explicit sizing + layering. + .screen-timing-clock { + position: fixed; + left: auto; + font-size: 9vmin; + + z-index: 2000; + + box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5); + } + .presenter-screen__part { display: grid; grid-template: @@ -47,9 +56,17 @@ $hold-status-color: $liveline-timecode-color; font-weight: bold; &.live { - background: $general-live-color; + // Layer the director-style gradient over the live color + background: + linear-gradient( + 90deg, + rgba(223, 0, 0, 0) 0%, + rgba(223, 0, 0, 0) 50%, + rgba(116, 0, 0, 0.808) 69%, + rgba(0, 0, 0, 0.8) 74% + ), + $general-live-color; color: #fff; - border-top: 0.1em solid #fff; -webkit-text-stroke: black; -webkit-text-stroke-width: 0.025em; text-shadow: 0px 0px 20px #00000044; @@ -58,7 +75,6 @@ $hold-status-color: $liveline-timecode-color; &.next { background: $general-next-color; color: #000; - border-top: 0.1em solid #fff; } } @@ -161,102 +177,6 @@ $hold-status-color: $liveline-timecode-color; } } - .presenter-screen__rundown-status-bar { - display: grid; - grid-template-columns: auto fit-content(20em) fit-content(5em); - grid-template-rows: fit-content(1em); - font-size: 6em; - color: #888; - padding: 0 0.2em; - - .presenter-screen__rundown-status-bar__rundown-name { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - line-height: 1.44em; - } - - .presenter-screen__rundown-status-bar__t-timer { - margin-right: 1em; - font-size: 0.8em; - align-self: center; - justify-self: end; - - .t-timer-display { - display: flex; - align-items: stretch; - justify-content: flex-end; - font-weight: 500; - background: #333; - border-radius: 0; - overflow: hidden; - - &__label { - display: flex; - align-items: center; - color: #fff; - padding-left: 0.4em; - padding-right: 0.2em; - font-size: 1em; - text-transform: uppercase; - letter-spacing: 0.05em; - font-stretch: condensed; - } - - &__value { - display: flex; - align-items: center; - color: #fff; - font-variant-numeric: tabular-nums; - padding: 0 0.2em; - font-size: 1em; - - .t-timer-display__part { - &--dimmed { - color: #aaa; - } - } - } - - &__over-under { - display: flex; - align-items: center; - justify-content: center; - margin: 0 0 0 0.2em; - font-size: 1em; - font-variant-numeric: tabular-nums; - padding: 0 0.4em; - line-height: 1.1; - min-width: 3.5em; - border-radius: 1em; - - &--over { - background-color: $general-late-color; - color: #fff; - } - - &--under { - background-color: #ffe900; - color: #000; - } - } - } - } - - .presenter-screen__rundown-status-bar__countdown { - white-space: nowrap; - - color: $general-countdown-to-next-color; - - font-weight: 600; - font-size: 1.2em; - - &.over { - color: $general-late-color; - } - } - } - .presenter-screen__part + .presenter-screen__part { border-top: solid 0.8em #454545; } @@ -411,4 +331,7 @@ $hold-status-color: $liveline-timecode-color; } } } + .rundown-status-bar { + font-size: 6.95vmin; + } } diff --git a/packages/webui/src/client/styles/counterComponents.scss b/packages/webui/src/client/styles/counterComponents.scss index 36b206f604b..585b19054c8 100644 --- a/packages/webui/src/client/styles/counterComponents.scss +++ b/packages/webui/src/client/styles/counterComponents.scss @@ -62,7 +62,7 @@ display: inline-block; } .over { - background-color: #ff5218; + background-color: $over-under-over-color; border-radius: 139px; align-self: stretch; gap: 10px; @@ -124,7 +124,7 @@ } .counter-component__time-since-planned-end { - color: #ff5218; + color: $over-under-over-color; text-align: right; margin-left: 1.2vw; diff --git a/packages/webui/src/client/styles/defaultColors.scss b/packages/webui/src/client/styles/defaultColors.scss index 41be88ec005..86612b5e352 100644 --- a/packages/webui/src/client/styles/defaultColors.scss +++ b/packages/webui/src/client/styles/defaultColors.scss @@ -22,6 +22,7 @@ --general-live-guest-color: #008a92; --general-late-color: #ff0000; + --over-under-over-color: #ff5218; --general-fast-color: #ff0000; --general-countdown-to-next-color: #ffff00; --general-freeze-color: #00bfff; diff --git a/packages/webui/src/client/styles/main.scss b/packages/webui/src/client/styles/main.scss index 254f12efbec..05a421722f9 100644 --- a/packages/webui/src/client/styles/main.scss +++ b/packages/webui/src/client/styles/main.scss @@ -38,6 +38,8 @@ input { @import 'overflowingContainer'; @import 'pieceStatusIcon'; @import 'prompter'; +@import 'tTimerDisplay'; +@import 'rundownStatusBar'; @import 'rundownList'; @import 'rundownSystemStatus'; @import 'settings'; diff --git a/packages/webui/src/client/styles/prompter.scss b/packages/webui/src/client/styles/prompter.scss index c3ca335957f..c9a281e6383 100644 --- a/packages/webui/src/client/styles/prompter.scss +++ b/packages/webui/src/client/styles/prompter.scss @@ -90,7 +90,8 @@ body.prompter-scrollbar { .prompter-part { text-align: center; letter-spacing: 0.1em; - font-size: 50%; + font-size: 63%; + height: 12.2vh; } .overlay-fix { pointer-events: none; @@ -129,7 +130,16 @@ body.prompter-scrollbar { &.live { //border-bottom: 0.2em solid #f55; - background: $general-live-color; + // Layer the director-style gradient over the live color + background: + linear-gradient( + 90deg, + rgba(223, 0, 0, 0) 0%, + rgba(223, 0, 0, 0) 50%, + rgba(116, 0, 0, 0.808) 68%, + rgba(0, 0, 0, 0.8) 73% + ), + $general-live-color; color: #fff; -webkit-text-stroke: black; -webkit-text-stroke-width: 0.025em; @@ -277,36 +287,24 @@ body.prompter-scrollbar { } } -.prompter-timing-clock { +.screen-timing-clock { position: fixed; display: block; top: 0; right: 0; - left: auto; - font-size: 75%; - padding: 0.05em 0.3em; - border-radius: 1em; - box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5); - - &.heavy-light { - font-weight: 600; + font-size: 9vmin; +} - &.light { - // color: $general-late-color; - background-color: #ffe900; - color: #000; - } +.prompter-rundown-status-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 1100; +} - &.heavy { - background-color: $general-fast-color; - color: #fff; - text-shadow: - 1px 1px 0px #000, - 1px -1px 0px #000, - -1px -1px 0px #000, - -1px 1px 0px #000; - } - } +.prompter-rundown-status-bar.rundown-status-bar { + font-size: 6.95vmin; } .script-text-formatted { diff --git a/packages/webui/src/client/styles/rundownStatusBar.scss b/packages/webui/src/client/styles/rundownStatusBar.scss new file mode 100644 index 00000000000..d3445d4dd95 --- /dev/null +++ b/packages/webui/src/client/styles/rundownStatusBar.scss @@ -0,0 +1,42 @@ +/** + * Styling for `ui/ClockView/RundownStatusBar.tsx`. + * + * View-level styles (Presenter/Prompter) should only handle placement and scaling. + */ +@import 'colorScheme'; + +.rundown-status-bar { + display: grid; + grid-template-columns: auto fit-content(1em); + grid-template-rows: fit-content(1em); + font-size: 6em; + color: #888; + padding: 0 0 0 0.2em; + background: rgba(0, 0, 0, 0.75); + + .rundown-status-bar__rundown-name { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + line-height: 1.44em; + } + + .rundown-status-bar__right { + display: flex; + align-items: center; + justify-content: flex-end; + justify-self: end; + // Match the exact background of `.t-timer-display` + background: #333; + } + + .rundown-status-bar__t-timer { + display: flex; + align-items: center; + white-space: nowrap; + } + + &.rundown-status-bar--no-title { + background: transparent; + } +} diff --git a/packages/webui/src/client/styles/tTimerDisplay.scss b/packages/webui/src/client/styles/tTimerDisplay.scss new file mode 100644 index 00000000000..1e59a61f4eb --- /dev/null +++ b/packages/webui/src/client/styles/tTimerDisplay.scss @@ -0,0 +1,79 @@ +@import 'colorScheme'; + +/** + * Default styling for `ui/ClockView/TTimerDisplay.tsx`. + * + * Layout/positioning/font-size should be applied by the embedding view + * (eg. status bar, director screen), but the core look of the timer itself + * is defined here. + */ +.t-timer-display { + display: flex; + align-items: stretch; + justify-content: flex-end; + + background: #333; + border-radius: 0; + overflow: hidden; + + font-family: Roboto Flex; + font-size: 1em; + line-height: 1; + + height: 1.46em; + + &__countdown { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 0; + + .countdown__label { + font-variation-settings: + 'wdth' 25, + 'wght' 400, + 'slnt' 0, + 'GRAD' 0, + 'opsz' 30, + 'XOPQ' 96, + 'XTRA' 468, + 'YOPQ' 79, + 'YTAS' 750, + 'YTFI' 738, + 'YTLC' 548, + 'YTDE' -203, + 'YTUC' 712; + letter-spacing: 0.0001em; + font-size: 1.3em; + padding-left: 0.125em; + color: #fff; + } + + .countdown__counter { + display: flex; + align-items: center; + font-variation-settings: + 'wdth' 25, + 'wght' 500, + 'slnt' 0, + 'GRAD' 0, + 'opsz' 20, + 'XOPQ' 96, + 'XTRA' 468, + 'YOPQ' 79, + 'YTAS' 750, + 'YTFI' 738, + 'YTLC' 548, + 'YTDE' -203, + 'YTUC' 712; + padding: 0 0.1em; + .over-under-chip { + position: relative; + left: 0.1em; + --overUnderChipOpsz: 20; + --overUnderChipYtlc: 514; + --overUnderChipMarginLeft: 0.15em; + } + } + } +} diff --git a/packages/webui/src/client/ui/ClockView/DirectorScreen/DirectorScreen.tsx b/packages/webui/src/client/ui/ClockView/DirectorScreen/DirectorScreen.tsx index 0887c899a50..d715a3a6782 100644 --- a/packages/webui/src/client/ui/ClockView/DirectorScreen/DirectorScreen.tsx +++ b/packages/webui/src/client/ui/ClockView/DirectorScreen/DirectorScreen.tsx @@ -44,13 +44,12 @@ import { AdjustLabelFit } from '../../util/AdjustLabelFit.js' import { AutoNextStatus } from '../../RundownView/RundownTiming/AutoNextStatus.js' import { useTranslation } from 'react-i18next' import { DBShowStyleBase, UIShowStyleBase } from '@sofie-automation/corelib/dist/dataModel/ShowStyleBase' -import { TTimerDisplay } from '../TTimerDisplay.js' -import { getDefaultTTimer } from '../../../lib/tTimerUtils.js' import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance.js' import { DirectorScreenTop } from './DirectorScreenTop.js' import { useTiming } from '../../RundownView/RundownTiming/withTiming.js' import { UIStudio } from '@sofie-automation/corelib/src/dataModel/Studio.js' import { PartInstance } from '@sofie-automation/corelib/src/dataModel/PartInstance.js' +import { RundownStatusBar } from '../RundownStatusBar.js' interface SegmentUi extends DBSegment { items: Array @@ -574,8 +573,6 @@ function DirectorScreenRender({ } } - const activeTTimer = getDefaultTTimer(playlist.tTimers) - return (
@@ -761,12 +758,13 @@ function DirectorScreenRender({ ) : null}
- {!!activeTTimer && ( -
- -
- )} + ) } diff --git a/packages/webui/src/client/ui/ClockView/DirectorScreen/DirectorScreenTop.tsx b/packages/webui/src/client/ui/ClockView/DirectorScreen/DirectorScreenTop.tsx index b1748a01285..1aead9fd627 100644 --- a/packages/webui/src/client/ui/ClockView/DirectorScreen/DirectorScreenTop.tsx +++ b/packages/webui/src/client/ui/ClockView/DirectorScreen/DirectorScreenTop.tsx @@ -1,16 +1,16 @@ import { - OverUnderClockComponent, PlannedEndComponent, TimeSincePlannedEndComponent, TimeToPlannedEndComponent, } from '../../../lib/Components/CounterComponents' -import { useTiming } from '../../RundownView/RundownTiming/withTiming' -import { getPlaylistTimingDiff } from '../../../lib/rundownTiming' +import { useTiming } from '../../RundownView/RundownTiming/withTiming.js' +import { getPlaylistTimingDiff } from '../../../lib/rundownTiming.js' import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/RundownPlaylist' -import { getCurrentTime } from '../../../lib/systemTime' +import { getCurrentTime } from '../../../lib/systemTime.js' import { useTranslation } from 'react-i18next' import { PlaylistTiming } from '@sofie-automation/corelib/dist/playout/rundownTiming' import { PartInstance } from '@sofie-automation/corelib/src/dataModel/PartInstance' +import { OverUnderChip } from '../../../lib/Components/OverUnderChip.js' export interface DirectorScreenTopProps { playlist: DBRundownPlaylist @@ -41,46 +41,44 @@ export function DirectorScreenTop({ const { t } = useTranslation() return ( -
- {expectedEnd ? ( -
-
- + <> +
+ {expectedEnd ? ( +
+
+ +
+ {t('Planned End')}
- {t('Planned End')} -
- ) : null} - {expectedEnd && expectedEnd > now ? ( -
-
- + ) : null} + {expectedEnd && expectedEnd > now ? ( +
+
+ +
+ + {rehearsalInProgress ? t('Time to rehearsal end') : t('Time to planned end')} +
- - {rehearsalInProgress ? t('Time to rehearsal end') : t('Time to planned end')} - -
- ) : ( -
-
- - + ) : ( +
+
+ +
+ {rehearsalInProgress ? t('Time since rehearsal end') : t('Time since planned end')}
-
- )} -
-
- -
- {t('Over/Under')} + )} +
-
+ + ) } diff --git a/packages/webui/src/client/ui/ClockView/PresenterScreen.tsx b/packages/webui/src/client/ui/ClockView/PresenterScreen.tsx index d8932727be9..6784c923967 100644 --- a/packages/webui/src/client/ui/ClockView/PresenterScreen.tsx +++ b/packages/webui/src/client/ui/ClockView/PresenterScreen.tsx @@ -35,7 +35,7 @@ import { RundownLayoutsAPI } from '../../lib/rundownLayouts.js' import { ShelfDashboardLayout } from '../Shelf/ShelfDashboardLayout.js' import { parse as queryStringParse } from 'query-string' import { calculatePartInstanceExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' -import { getPlaylistTimingDiff, RundownTimingContext } from '../../lib/rundownTiming.js' +import { RundownTimingContext } from '../../lib/rundownTiming.js' import { UIShowStyleBases, UIStudios } from '../Collections.js' import { PieceInstances, @@ -50,11 +50,11 @@ import { useSetDocumentClass, useSetDocumentDarkTheme } from '../util/useSetDocu import { useRundownAndShowStyleIdsForPlaylist } from '../util/useRundownAndShowStyleIdsForPlaylist.js' import { RundownPlaylistClientUtil } from '../../lib/rundownPlaylistUtil.js' import { CurrentPartOrSegmentRemaining } from '../RundownView/RundownHeader/CurrentPartOrSegmentRemaining.js' -import { TTimerDisplay } from './TTimerDisplay.js' -import { getDefaultTTimer } from '../../lib/tTimerUtils.js' +import { RundownStatusBar } from './RundownStatusBar.js' import { UIShowStyleBase } from '@sofie-automation/corelib/src/dataModel/ShowStyleBase.js' import { UIStudio } from '@sofie-automation/corelib/src/dataModel/Studio.js' import { PartInstance } from '@sofie-automation/corelib/src/dataModel/PartInstance.js' +import { OverUnderChip } from '../../lib/Components/OverUnderChip.js' // TODO: We have another definition of this in the Director screen, and there is also another SegmentUI type. We should look into clearing this up. interface SegmentUi extends DBSegment { @@ -88,6 +88,8 @@ export interface PresenterScreenTrackedProps { rundownIds: RundownId[] rundownLayouts?: Array presenterLayoutId: RundownLayoutId | undefined + margin: number | undefined + fontSize: number | undefined } function getShowStyleBaseIdSegmentPartUi( @@ -218,6 +220,18 @@ export const getPresenterScreenReactive = ( const params = queryStringParse(location.search) const presenterLayoutId = protectString((params['presenterLayout'] as string) || '') + const margin = (() => { + // Support both `margin` (PrompterView) and `margins` / `m` (legacy/typos in URLs) + const raw = (params['margin'] ?? params['margins'] ?? params['m']) as string + const val = Number.parseInt(raw, 10) + return Number.isNaN(val) ? undefined : val + })() + const fontSize = (() => { + // Support both `fontsize` (PrompterView) and `fontSize` (camelCase URLs) + const raw = (params['fontsize'] ?? params['fontSize']) as string + const val = Number.parseInt(raw, 10) + return Number.isNaN(val) ? undefined : val + })() if (playlist) { rundowns = RundownPlaylistCollectionUtil.getRundownsOrdered(playlist) @@ -301,6 +315,8 @@ export const getPresenterScreenReactive = ( rundownLayouts: rundowns.length > 0 ? RundownLayouts.find({ showStyleBaseId: rundowns[0].showStyleBaseId }).fetch() : undefined, presenterLayoutId, + margin, + fontSize, } } @@ -379,6 +395,8 @@ export function PresenterScreen({ playlistId, studioId }: PresenterScreenProps): rundowns={presenterScreenProps?.rundowns ?? []} segments={presenterScreenProps?.segments ?? []} showStyleBaseIds={presenterScreenProps?.showStyleBaseIds ?? []} + margin={presenterScreenProps?.margin} + fontSize={presenterScreenProps?.fontSize} studio={presenterScreenProps?.studio} studioId={studioId} timingDurations={timing} @@ -496,12 +514,11 @@ function PresenterScreenContentDefaultLayout({ timingDurations.remainingBudgetOnCurrentSegment ?? timingDurations.remainingTimeOnCurrentPart ?? 0 const expectedStart = PlaylistTiming.getExpectedStart(playlist.timing) - const overUnderClock = getPlaylistTimingDiff(playlist, timingDurations) ?? 0 - const activeTTimer = getDefaultTTimer(playlist.tTimers) return (
+
) : null}
-
-
- {playlist ? playlist.name : 'UNKNOWN'} -
-
- {!!activeTTimer && } -
-
= 0, - })} - > - {RundownUtils.formatDiffToTimecode(overUnderClock, true, false, true, true, true, undefined, true, true)} -
-
+
) } diff --git a/packages/webui/src/client/ui/ClockView/RundownStatusBar.tsx b/packages/webui/src/client/ui/ClockView/RundownStatusBar.tsx new file mode 100644 index 00000000000..78022aa5027 --- /dev/null +++ b/packages/webui/src/client/ui/ClockView/RundownStatusBar.tsx @@ -0,0 +1,34 @@ +import ClassNames from 'classnames' +import { getDefaultTTimer } from '../../lib/tTimerUtils.js' +import { TTimerDisplay } from './TTimerDisplay.js' +import { DBRundownPlaylist } from '@sofie-automation/corelib/src/dataModel/RundownPlaylist/RundownPlaylist.js' + +interface RundownStatusBarProps { + playlist?: DBRundownPlaylist + className?: string + showPlaylistName?: boolean + showDiff?: boolean +} + +export function RundownStatusBar({ + playlist, + className, + showPlaylistName = true, +}: Readonly): JSX.Element { + const activeTTimer = playlist ? getDefaultTTimer(playlist.tTimers) : undefined + + return ( +
+ {showPlaylistName && ( +
{playlist ? playlist.name : 'UNKNOWN'}
+ )} +
+
{!!activeTTimer && }
+
+
+ ) +} diff --git a/packages/webui/src/client/ui/ClockView/TTimerDisplay.tsx b/packages/webui/src/client/ui/ClockView/TTimerDisplay.tsx index 0f05043ed44..817ea3eb739 100644 --- a/packages/webui/src/client/ui/ClockView/TTimerDisplay.tsx +++ b/packages/webui/src/client/ui/ClockView/TTimerDisplay.tsx @@ -1,8 +1,9 @@ import { RundownTTimer } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/TTimers' import { RundownUtils } from '../../lib/rundown.js' -import { calculateTTimerDiff, calculateTTimerOverUnder } from '../../lib/tTimerUtils' -import { useTiming } from '../RundownView/RundownTiming/withTiming' -import classNames from 'classnames' +import { calculateTTimerDiff, calculateTTimerOverUnder } from '../../lib/tTimerUtils.js' +import { useTiming } from '../RundownView/RundownTiming/withTiming.js' +import { OverUnderChip } from '../../lib/Components/OverUnderChip.js' +import { Countdown } from '../RundownView/RundownHeader/Countdown' interface TTimerDisplayProps { timer: RundownTTimer @@ -17,39 +18,19 @@ export function TTimerDisplay({ timer }: Readonly): JSX.Elem const diff = calculateTTimerDiff(timer, now) const overUnder = calculateTTimerOverUnder(timer, now) - - const timerStr = RundownUtils.formatDiffToTimecode(Math.abs(diff), false, true, true, false, true) - const timerParts = timerStr.split(':') + const timeStr = RundownUtils.formatDiffToTimecode(Math.abs(diff), false, true, true, false, true) const timerSign = diff >= 0 ? '' : '-' return (
- {timer.label} - - {timerSign} - {timerParts.map((p, i) => ( - - {p} - {i < timerParts.length - 1 && :} - - ))} - - {overUnder !== undefined && ( - 0, - 't-timer-display__over-under--under': overUnder <= 0, - })} - > - {overUnder > 0 ? '+' : '\u2013'} - {RundownUtils.formatDiffToTimecode(Math.abs(overUnder), false, true, true, false, true)} - - )} + } + > + {`${timerSign}${timeStr}`} +
) } diff --git a/packages/webui/src/client/ui/Prompter/OverUnderTimer.tsx b/packages/webui/src/client/ui/Prompter/OverUnderTimer.tsx deleted file mode 100644 index ff7323b499f..00000000000 --- a/packages/webui/src/client/ui/Prompter/OverUnderTimer.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import * as React from 'react' -import { useTiming } from '../RundownView/RundownTiming/withTiming.js' -import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/RundownPlaylist' -import { RundownUtils } from '../../lib/rundown.js' -import ClassNames from 'classnames' -import { getPlaylistTimingDiff } from '../../lib/rundownTiming.js' - -interface IProps { - rundownPlaylist: DBRundownPlaylist - style?: React.CSSProperties | undefined -} - -/** - * Shows an over/under timer for the rundownPlaylist. Requires a RundownTimingContext from the RundownTimingProvider - */ -export function OverUnderTimer({ rundownPlaylist, style }: IProps): JSX.Element { - const timingDurations = useTiming() - - const overUnderClock = getPlaylistTimingDiff(rundownPlaylist, timingDurations) ?? 0 - - return ( - = 0, - })} - > - {RundownUtils.formatDiffToTimecode(overUnderClock, true, false, true, true, true, undefined, true, true)} - - ) -} diff --git a/packages/webui/src/client/ui/Prompter/PrompterView.tsx b/packages/webui/src/client/ui/Prompter/PrompterView.tsx index d8cde5358e9..1e6feba518f 100644 --- a/packages/webui/src/client/ui/Prompter/PrompterView.tsx +++ b/packages/webui/src/client/ui/Prompter/PrompterView.tsx @@ -31,12 +31,13 @@ import { UIStudios } from '../Collections.js' import { RundownTimingProvider } from '../RundownView/RundownTiming/RundownTimingProvider.js' import { StudioScreenSaver } from '../StudioScreenSaver/StudioScreenSaver.js' import { PrompterControlManager } from './controller/manager.js' -import { OverUnderTimer } from './OverUnderTimer.js' +import { RundownStatusBar } from '../ClockView/RundownStatusBar.js' import { PrompterAPI, PrompterData, PrompterDataPart, PrompterDataPiece } from './prompter.js' import { doUserAction, UserAction } from '../../lib/clientUserAction.js' import { MeteorCall } from '../../lib/meteorApi.js' import { MdDisplay } from './Formatted/MdDisplay.js' import { UIStudio } from '@sofie-automation/corelib/src/dataModel/Studio.js' +import { OverUnderChip } from '../../lib/Components/OverUnderChip.js' const DEFAULT_UPDATE_THROTTLE = 250 //ms const PIECE_MISSING_UPDATE_THROTTLE = 2000 //ms @@ -76,6 +77,7 @@ interface PrompterConfig { showScroll: boolean debug: boolean showOverUnder: boolean + showPlaylistName: boolean addBlankLine: boolean } @@ -220,6 +222,7 @@ export class PrompterViewContent extends React.Component 12 ? `12vmin` : undefined, - } - return ( {!this.props.subsReady ? ( @@ -610,9 +605,17 @@ export class PrompterViewContent extends React.Component {this.configOptions.showOverUnder && ( - + )} + {this.configOptions.debug ? (
) { mode.type === 'countdown' && timer.state !== null && diff >= 0, })} ms={mode.type === 'timeOfDay' ? undefined : diff} - postfix={ - overUnder ? ( - 0, - 'rundown-header__clocks-timers__timer__over-under--under': overUnder < 0, - })} - > - {overUnder > 0 ? '+' : '−'} - {RundownUtils.formatDiffToTimecode(Math.abs(overUnder), false, false, true, false, true)} - - ) : undefined - } + postfix={overUnder ? : undefined} > {timeStr} diff --git a/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderTimingDisplay.tsx b/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderTimingDisplay.tsx index 28c5c89c4b9..14462d966a2 100644 --- a/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderTimingDisplay.tsx +++ b/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderTimingDisplay.tsx @@ -3,7 +3,7 @@ import { PlaylistTiming } from '@sofie-automation/corelib/dist/playout/rundownTi import { useTranslation } from 'react-i18next' import { useTiming } from '../RundownTiming/withTiming' import { getPlaylistTimingDiff } from '../../../lib/rundownTiming' -import { RundownUtils } from '../../../lib/rundown.js' +import { OverUnderChip } from '../../../lib/Components/OverUnderChip' export interface IRundownHeaderTimingDisplayProps { playlist: DBRundownPlaylist @@ -26,21 +26,13 @@ export function RundownHeaderTimingDisplay({ playlist }: IRundownHeaderTimingDis return null } - const timeStr = RundownUtils.formatDiffToTimecode(Math.abs(overUnderClock), false, false, true, true, true) const isUnder = overUnderClock <= 0 return (
- + {isUnder ? t('Under') : t('Over')} - - {isUnder ? '−' : '+'} - {timeStr} - +
)