Skip to content

Commit 6deba15

Browse files
committed
add simplified mode to top bar
1 parent 7322b8e commit 6deba15

File tree

7 files changed

+185
-45
lines changed

7 files changed

+185
-45
lines changed

packages/webui/src/client/ui/RundownView/RundownHeader/Countdown.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
}
1616

1717
&__value {
18+
margin-left: auto;
1819
font-size: 1.4em;
1920
font-variant-numeric: tabular-nums;
2021
letter-spacing: 0.05em;

packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.scss

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
border-bottom: 1px solid #333;
99
transition: background-color 0.5s;
1010
font-family: 'Roboto Flex', 'Roboto', sans-serif;
11-
font-feature-settings: 'liga' 0, 'tnum';
11+
font-feature-settings:
12+
'liga' 0,
13+
'tnum';
1214
font-variant-numeric: tabular-nums;
1315

1416
.rundown-header__trigger {
@@ -73,12 +75,25 @@
7375
color: #40b8fa;
7476
font-size: 1.4em;
7577

76-
letter-spacing: 0.0 em;
78+
letter-spacing: 0 em;
7779
transition: color 0.2s;
7880

7981
&.time-now {
8082
font-size: 1.8em;
81-
font-variation-settings: 'wdth' 70, 'wght' 400, 'slnt' -5, 'GRAD' 0, 'opsz' 44, 'XOPQ' 96, 'XTRA' 468, 'YOPQ' 79, 'YTAS' 750, 'YTFI' 738, 'YTLC' 548, 'YTDE' -203, 'YTUC' 712;
83+
font-variation-settings:
84+
'wdth' 70,
85+
'wght' 400,
86+
'slnt' -5,
87+
'GRAD' 0,
88+
'opsz' 44,
89+
'XOPQ' 96,
90+
'XTRA' 468,
91+
'YOPQ' 79,
92+
'YTAS' 750,
93+
'YTFI' 738,
94+
'YTLC' 548,
95+
'YTDE' -203,
96+
'YTUC' 712;
8297
}
8398
}
8499

@@ -98,21 +113,47 @@
98113
.rundown-header__diff__label {
99114
@extend .rundown-header__hoverable-label;
100115
font-size: 0.7em;
101-
font-variation-settings: 'wdth' 25, 'wght' 500, 'slnt' 0, 'GRAD' 0, 'opsz' 14, 'XOPQ' 96, 'XTRA' 468, 'YOPQ' 79, 'YTAS' 750, 'YTFI' 738, 'YTLC' 548, 'YTDE' -203, 'YTUC' 712;
116+
font-variation-settings:
117+
'wdth' 25,
118+
'wght' 500,
119+
'slnt' 0,
120+
'GRAD' 0,
121+
'opsz' 14,
122+
'XOPQ' 96,
123+
'XTRA' 468,
124+
'YOPQ' 79,
125+
'YTAS' 750,
126+
'YTFI' 738,
127+
'YTLC' 548,
128+
'YTDE' -203,
129+
'YTUC' 712;
102130
opacity: 0.6;
103131
}
104132

105133
.rundown-header__diff__chip {
106134
font-size: 1.2em;
107-
padding: 0.0em 0.3em;
135+
padding: 0em 0.3em;
108136
border-radius: 999px;
109-
font-variation-settings: 'wdth' 25, 'wght' 600, 'slnt' 0, 'GRAD' 0, 'opsz' 20, 'XOPQ' 96, 'XTRA' 468, 'YOPQ' 79, 'YTAS' 750, 'YTFI' 738, 'YTLC' 548, 'YTDE' -203, 'YTUC' 712;
137+
font-variation-settings:
138+
'wdth' 25,
139+
'wght' 600,
140+
'slnt' 0,
141+
'GRAD' 0,
142+
'opsz' 20,
143+
'XOPQ' 96,
144+
'XTRA' 468,
145+
'YOPQ' 79,
146+
'YTAS' 750,
147+
'YTFI' 738,
148+
'YTLC' 548,
149+
'YTDE' -203,
150+
'YTUC' 712;
110151
letter-spacing: -0.02em;
111152
}
112153

113154
&.rundown-header__diff--under {
114155
.rundown-header__diff__chip {
115-
background-color: #ff0;//$general-fast-color;
156+
background-color: #ff0; //$general-fast-color;
116157
color: #000;
117158
}
118159
}
@@ -200,7 +241,20 @@
200241
// Common label style for header labels that react to hover
201242
.rundown-header__hoverable-label {
202243
font-size: 0.75em;
203-
font-variation-settings: 'wdth' 25, 'wght' 500, 'slnt' 0, 'GRAD' 0, 'opsz' 14, 'XOPQ' 96, 'XTRA' 468, 'YOPQ' 79, 'YTAS' 750, 'YTFI' 738, 'YTLC' 548, 'YTDE' -203, 'YTUC' 712;
244+
font-variation-settings:
245+
'wdth' 25,
246+
'wght' 500,
247+
'slnt' 0,
248+
'GRAD' 0,
249+
'opsz' 14,
250+
'XOPQ' 96,
251+
'XTRA' 468,
252+
'YOPQ' 79,
253+
'YTAS' 750,
254+
'YTFI' 738,
255+
'YTLC' 548,
256+
'YTDE' -203,
257+
'YTUC' 712;
204258
letter-spacing: 0.01em;
205259
text-transform: uppercase;
206260
opacity: 0.6;
@@ -234,6 +288,13 @@
234288
min-width: 7em;
235289
}
236290

291+
.rundown-header__timing-group {
292+
display: flex;
293+
align-items: center;
294+
gap: 1em;
295+
cursor: pointer;
296+
}
297+
237298
.rundown-header__onair-remaining__label {
238299
background-color: $general-live-color;
239300
color: #ffffff;
@@ -242,7 +303,20 @@
242303
// Label font styling override meant to match the ON AIR label on the On Air line
243304
font-size: 0.8em;
244305
letter-spacing: 0.05em;
245-
font-variation-settings: 'wdth' 80, 'wght' 700, 'slnt' 0, 'GRAD' 0, 'opsz' 14, 'XOPQ' 96, 'XTRA' 468, 'YOPQ' 79, 'YTAS' 750, 'YTFI' 738, 'YTLC' 548, 'YTDE' -203, 'YTUC' 712;
306+
font-variation-settings:
307+
'wdth' 80,
308+
'wght' 700,
309+
'slnt' 0,
310+
'GRAD' 0,
311+
'opsz' 14,
312+
'XOPQ' 96,
313+
'XTRA' 468,
314+
'YOPQ' 79,
315+
'YTAS' 750,
316+
'YTFI' 738,
317+
'YTLC' 548,
318+
'YTDE' -203,
319+
'YTUC' 712;
246320

247321
opacity: 1 !important;
248322

packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useState } from 'react'
12
import ClassNames from 'classnames'
23
import { NavLink } from 'react-router-dom'
34
import * as CoreIcon from '@nrk/core-icons/jsx'
@@ -38,6 +39,7 @@ interface IRundownHeaderProps {
3839

3940
export function RundownHeader({ playlist, studio, firstRundown }: IRundownHeaderProps): JSX.Element {
4041
const { t } = useTranslation()
42+
const [simplified, setSimplified] = useState(false)
4143

4244
return (
4345
<>
@@ -85,9 +87,11 @@ export function RundownHeader({ playlist, studio, firstRundown }: IRundownHeader
8587
</div>
8688

8789
<div className="rundown-header__right">
88-
<RundownHeaderPlannedStart playlist={playlist} />
89-
<RundownHeaderDurations playlist={playlist} />
90-
<RundownHeaderExpectedEnd playlist={playlist} />
90+
<div className="rundown-header__timing-group" onClick={() => setSimplified((s) => !s)}>
91+
<RundownHeaderPlannedStart playlist={playlist} simplified={simplified} />
92+
<RundownHeaderDurations playlist={playlist} simplified={simplified} />
93+
<RundownHeaderExpectedEnd playlist={playlist} simplified={simplified} />
94+
</div>
9195
<NavLink to="/" title={t('Exit')} className="rundown-header__close-btn">
9296
<CoreIcon.NrkClose />
9397
</NavLink>

packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderDurations.tsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,41 @@ import { useTranslation } from 'react-i18next'
44
import { Countdown } from './Countdown'
55
import { useTiming } from '../RundownTiming/withTiming'
66
import { RundownUtils } from '../../../lib/rundown'
7+
import { getRemainingDurationFromCurrentPart } from './remainingDuration'
78

8-
export function RundownHeaderDurations({ playlist }: { playlist: DBRundownPlaylist }): JSX.Element | null {
9+
export function RundownHeaderDurations({
10+
playlist,
11+
simplified,
12+
}: {
13+
playlist: DBRundownPlaylist
14+
simplified?: boolean
15+
}): JSX.Element | null {
916
const { t } = useTranslation()
1017
const timingDurations = useTiming()
1118

1219
const expectedDuration = PlaylistTiming.getExpectedDuration(playlist.timing)
1320
const planned =
1421
expectedDuration != null ? RundownUtils.formatDiffToTimecode(expectedDuration, false, true, true, true, true) : null
1522

16-
const remainingMs = timingDurations.remainingPlaylistDuration
17-
const startedMs = playlist.startedPlayback
18-
const estDuration =
19-
remainingMs != null && startedMs != null
20-
? (timingDurations.currentTime ?? Date.now()) - startedMs + remainingMs
21-
: null
23+
const now = timingDurations.currentTime ?? Date.now()
24+
const currentPartInstanceId = playlist.currentPartInfo?.partInstanceId
25+
26+
let estDuration: number | null = null
27+
if (currentPartInstanceId && timingDurations.partStartsAt && timingDurations.partExpectedDurations) {
28+
const remaining = getRemainingDurationFromCurrentPart(
29+
currentPartInstanceId,
30+
timingDurations.partStartsAt,
31+
timingDurations.partExpectedDurations
32+
)
33+
if (remaining != null) {
34+
const elapsed =
35+
playlist.startedPlayback != null
36+
? now - playlist.startedPlayback
37+
: (timingDurations.asDisplayedPlaylistDuration ?? 0)
38+
estDuration = elapsed + remaining
39+
}
40+
}
41+
2242
const estimated =
2343
estDuration != null ? RundownUtils.formatDiffToTimecode(estDuration, false, true, true, true, true) : null
2444

@@ -27,7 +47,7 @@ export function RundownHeaderDurations({ playlist }: { playlist: DBRundownPlayli
2747
return (
2848
<div className="rundown-header__endtimes">
2949
{planned ? <Countdown label={t('Plan. Dur')}>{planned}</Countdown> : null}
30-
{estimated ? <Countdown label={t('Est. Dur')}>{estimated}</Countdown> : null}
50+
{!simplified && estimated ? <Countdown label={t('Est. Dur')}>{estimated}</Countdown> : null}
3151
</div>
3252
)
3353
}
Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,33 @@
11
import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
22
import { PlaylistTiming } from '@sofie-automation/corelib/dist/playout/rundownTiming'
3-
import { unprotectString } from '@sofie-automation/corelib/dist/protectedString'
43
import { useTranslation } from 'react-i18next'
54
import { Countdown } from './Countdown'
65
import { useTiming } from '../RundownTiming/withTiming'
7-
8-
export function RundownHeaderExpectedEnd({ playlist }: { playlist: DBRundownPlaylist }): JSX.Element | null {
6+
import { getRemainingDurationFromCurrentPart } from './remainingDuration'
7+
8+
export function RundownHeaderExpectedEnd({
9+
playlist,
10+
simplified,
11+
}: {
12+
playlist: DBRundownPlaylist
13+
simplified?: boolean
14+
}): JSX.Element | null {
915
const { t } = useTranslation()
1016
const timingDurations = useTiming()
1117

1218
const expectedEnd = PlaylistTiming.getExpectedEnd(playlist.timing)
13-
1419
const now = timingDurations.currentTime ?? Date.now()
1520

16-
// Calculate Est. End by summing partExpectedDurations for all parts after the current one.
17-
// Both partStartsAt and partExpectedDurations use PartInstanceId keys, so they match.
1821
let estEnd: number | null = null
1922
const currentPartInstanceId = playlist.currentPartInfo?.partInstanceId
20-
const partStartsAt = timingDurations.partStartsAt
21-
const partExpectedDurations = timingDurations.partExpectedDurations
22-
23-
if (currentPartInstanceId && partStartsAt && partExpectedDurations) {
24-
const currentKey = unprotectString(currentPartInstanceId)
25-
const currentStartsAt = partStartsAt[currentKey]
26-
27-
if (currentStartsAt != null) {
28-
let remainingDuration = 0
29-
for (const [partId, startsAt] of Object.entries<number>(partStartsAt)) {
30-
if (startsAt > currentStartsAt) {
31-
remainingDuration += partExpectedDurations[partId] ?? 0
32-
}
33-
}
34-
if (remainingDuration > 0) {
35-
estEnd = now + remainingDuration
36-
}
23+
if (currentPartInstanceId && timingDurations.partStartsAt && timingDurations.partExpectedDurations) {
24+
const remaining = getRemainingDurationFromCurrentPart(
25+
currentPartInstanceId,
26+
timingDurations.partStartsAt,
27+
timingDurations.partExpectedDurations
28+
)
29+
if (remaining != null && remaining > 0) {
30+
estEnd = now + remaining
3731
}
3832
}
3933

@@ -42,7 +36,7 @@ export function RundownHeaderExpectedEnd({ playlist }: { playlist: DBRundownPlay
4236
return (
4337
<div className="rundown-header__endtimes">
4438
{expectedEnd ? <Countdown label={t('Plan. End')} time={expectedEnd} /> : null}
45-
{estEnd ? <Countdown label={t('Est. End')} time={estEnd} /> : null}
39+
{!simplified && estEnd ? <Countdown label={t('Est. End')} time={estEnd} /> : null}
4640
</div>
4741
)
4842
}

packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderPlannedStart.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,37 @@ import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/Rund
22
import { PlaylistTiming } from '@sofie-automation/corelib/dist/playout/rundownTiming'
33
import { useTranslation } from 'react-i18next'
44
import { Countdown } from './Countdown'
5+
import { useTiming } from '../RundownTiming/withTiming'
6+
import { RundownUtils } from '../../../lib/rundown'
57

6-
export function RundownHeaderPlannedStart({ playlist }: { playlist: DBRundownPlaylist }): JSX.Element | null {
8+
export function RundownHeaderPlannedStart({
9+
playlist,
10+
simplified,
11+
}: {
12+
playlist: DBRundownPlaylist
13+
simplified?: boolean
14+
}): JSX.Element | null {
715
const { t } = useTranslation()
16+
const timingDurations = useTiming()
817
const expectedStart = PlaylistTiming.getExpectedStart(playlist.timing)
918

1019
if (expectedStart == null) return null
1120

21+
const now = timingDurations.currentTime ?? Date.now()
22+
const diff = now - expectedStart
23+
1224
return (
1325
<div className="rundown-header__endtimes">
1426
<Countdown label={t('Plan. Start')} time={expectedStart} />
27+
{!simplified &&
28+
(playlist.startedPlayback ? (
29+
<Countdown label={t('Started')} time={playlist.startedPlayback} />
30+
) : (
31+
<Countdown>
32+
{diff >= 0 && '-'}
33+
{RundownUtils.formatDiffToTimecode(Math.abs(diff), false, false, true, true, true)}
34+
</Countdown>
35+
))}
1536
</div>
1637
)
1738
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { PartInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids'
2+
import { unprotectString } from '@sofie-automation/corelib/dist/protectedString'
3+
4+
/**
5+
* Compute the sum of expected durations of all parts after the current part.
6+
* Uses partStartsAt to determine ordering and partExpectedDurations for the values.
7+
* Returns 0 if the current part can't be found or there are no future parts.
8+
*/
9+
export function getRemainingDurationFromCurrentPart(
10+
currentPartInstanceId: PartInstanceId,
11+
partStartsAt: Record<string, number>,
12+
partExpectedDurations: Record<string, number>
13+
): number | null {
14+
const currentKey = unprotectString(currentPartInstanceId)
15+
const currentStartsAt = partStartsAt[currentKey]
16+
17+
if (currentStartsAt == null) return null
18+
19+
let remaining = 0
20+
for (const [partId, startsAt] of Object.entries<number>(partStartsAt)) {
21+
if (startsAt > currentStartsAt) {
22+
remaining += partExpectedDurations[partId] ?? 0
23+
}
24+
}
25+
return remaining
26+
}

0 commit comments

Comments
 (0)