From 0af343a60097a8e01785f7604a966431a4af6a32 Mon Sep 17 00:00:00 2001 From: Jonas Hummelstrand Date: Mon, 23 Mar 2026 14:54:58 +0100 Subject: [PATCH 01/10] chore: Added definition for a specific background and text color for the new class for the segment-event-identifier styling. --- packages/webui/src/client/styles/_colorScheme.scss | 3 +++ packages/webui/src/client/styles/defaultColors.scss | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/packages/webui/src/client/styles/_colorScheme.scss b/packages/webui/src/client/styles/_colorScheme.scss index 603a0dd3bc..60b6b61020 100644 --- a/packages/webui/src/client/styles/_colorScheme.scss +++ b/packages/webui/src/client/styles/_colorScheme.scss @@ -32,6 +32,9 @@ $general-timecode-color: var(--general-timecode-color); $part-identifier: var(--part-identifier); $part-identifier-text: var(--part-identifier-text); +$segment-event-identifier: var(--segment-event-identifier); +$segment-event-identifier-text: var(--segment-event-identifier-text); + $ui-button-primary: var(--ui-button-primary); $ui-button-primary--hover: var(--ui-button-primary--hover); $ui-button-primary--translucent: var(--ui-button-primary--translucent); diff --git a/packages/webui/src/client/styles/defaultColors.scss b/packages/webui/src/client/styles/defaultColors.scss index 86612b5e35..f612bd96a5 100644 --- a/packages/webui/src/client/styles/defaultColors.scss +++ b/packages/webui/src/client/styles/defaultColors.scss @@ -32,6 +32,10 @@ --part-identifier: #363636; --part-identifier-text: #eee; + + --segment-event-identifier: #363636; + --segment-event-identifier-text: #eee; + --general-timecode-color: #ffff00; --ui-button-primary: #1769ff; From 978330a51e9580dd183f17af84fe3805ee4d67e8 Mon Sep 17 00:00:00 2001 From: Jonas Hummelstrand Date: Mon, 23 Mar 2026 16:21:38 +0100 Subject: [PATCH 02/10] chore: Refactored and modernized the font styling of the "parts-identifier" shown in the segment header and on the take lines on each part. --- .../webui/src/client/styles/rundownView.scss | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/packages/webui/src/client/styles/rundownView.scss b/packages/webui/src/client/styles/rundownView.scss index b9c257249e..487519d172 100644 --- a/packages/webui/src/client/styles/rundownView.scss +++ b/packages/webui/src/client/styles/rundownView.scss @@ -332,18 +332,38 @@ svg.icon { grid-row: title-identifiers / end; .segment-timeline__part-identifiers__identifier { - font-weight: 300; - text-shadow: none; - margin: 2.5px; - padding: 1px 4px; + margin: 0.1rem; + padding: 0.1rem 0.35rem 0.1rem 0.35rem; background-color: $part-identifier; color: $part-identifier-text; - border-radius: 10px; - font-size: 0.85rem; + border-radius: 99px; + font-family: "Roboto Flex", "Roboto", sans-serif; + font-style: normal; + font-size: 0.9rem; + letter-spacing: 0.03em; + line-height: 100%; + text-shadow: none; + font-variant-numeric: proportional-nums; + font-variation-settings: + 'wdth' 90, + 'wght' 500, + 'opsz' 120, + 'slnt' 0, + 'GRAD' 0, + 'XOPQ' 96, + 'XTRA' 468, + 'YOPQ' 79, + 'YTAS' 750, + 'YTDE' -203, + 'YTFI' 738, + 'YTLC' 514, + 'YTUC' 712; &:hover { - background-color: #8c8c8c; + background-color: #6f6f6f; color: #fff; + font-weight: 700; + box-shadow: 0px 0px 4px 0px #fff; } } } @@ -2301,12 +2321,34 @@ svg.icon { .segment-timeline__identifier { z-index: -1; - padding: 0 4px 0 10px; + padding: 0.1rem 0.35rem 0.1rem 0.6rem; box-sizing: border-box; + border-radius: 0 99px 99px 0; + margin: 0rem; + background-color: $part-identifier; - border-radius: 0 8px 8px 0; color: $part-identifier-text; - font-size: 0.85rem; + font-family: "Roboto Flex", "Roboto", sans-serif; + font-style: normal; + font-size: 0.9rem; + letter-spacing: 0.03em; + line-height: 100%; + text-shadow: none; + font-variant-numeric: proportional-nums; + font-variation-settings: + 'wdth' 90, + 'wght' 500, + 'opsz' 120, + 'slnt' 0, + 'GRAD' 0, + 'XOPQ' 96, + 'XTRA' 468, + 'YOPQ' 79, + 'YTAS' 750, + 'YTDE' -203, + 'YTFI' 738, + 'YTLC' 514, + 'YTUC' 712; } &.gap { From e42d4467530de78e2f67663644fd265f55355d60 Mon Sep 17 00:00:00 2001 From: Jonas Hummelstrand Date: Tue, 24 Mar 2026 12:16:06 +0100 Subject: [PATCH 03/10] chore: Made the parts Identifiers vertically aligned to the bottom of the segment header. --- .../webui/src/client/styles/rundownView.scss | 17 +++++++++++------ .../ui/SegmentTimeline/SegmentTimeline.scss | 4 ---- .../ui/SegmentTimeline/SegmentTimeline.tsx | 1 + 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/webui/src/client/styles/rundownView.scss b/packages/webui/src/client/styles/rundownView.scss index 487519d172..18b9cc5096 100644 --- a/packages/webui/src/client/styles/rundownView.scss +++ b/packages/webui/src/client/styles/rundownView.scss @@ -284,9 +284,8 @@ svg.icon { } .segment-timeline__title { - display: grid; - grid-template-columns: auto; - grid-template-rows: [title-title] min-content [title-notifications] 1fr [title-identifiers] min-content [end]; + display: flex; + flex-direction: column; margin: 0; padding: 0; @@ -307,6 +306,12 @@ svg.icon { grid-row: title / timeline-header; } + .segment-timeline__title__content { + display: flex; + flex-direction: column; + flex-grow: 1; + } + h2 { margin: 0; padding: 0.2em; @@ -316,7 +321,6 @@ svg.icon { hyphens: auto; -webkit-hyphens: auto; -ms-hyphens: auto; - grid-row: title-title / title-notifications; } .segment-timeline__title__notes { @@ -325,13 +329,13 @@ svg.icon { line-height: 1.4em; font-size: 0.75em; font-weight: 400; - grid-row: title-notifications / title-identifiers; } .segment-timeline__part-identifiers { - grid-row: title-identifiers / end; + margin-top: auto; .segment-timeline__part-identifiers__identifier { + // The pill-shaped labels that appear in the title area of a segment, showing and linking to the part name or number margin: 0.1rem; padding: 0.1rem 0.35rem 0.1rem 0.35rem; background-color: $part-identifier; @@ -2320,6 +2324,7 @@ svg.icon { } .segment-timeline__identifier { + // The Parts identifier label on the right side of the part, showing the Part name or number z-index: -1; padding: 0.1rem 0.35rem 0.1rem 0.6rem; box-sizing: border-box; diff --git a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.scss b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.scss index d49920a8d4..753593d306 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.scss +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.scss @@ -29,10 +29,6 @@ $timeline-layer-height: 1em; vertical-align: text-bottom; } .segment-timeline__title__user-edit-states { - position: absolute; - bottom: 0; - left: 0; - right: 0; display: flex; flex-flow: row nowrap; } diff --git a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx index 970fe3f7df..cd13c21f7f 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx @@ -1050,6 +1050,7 @@ export class SegmentTimelineClass extends React.Component
{ if (this.props.studio.settings.enableUserEdits) { const segment = this.props.segment From 1c7db795f66a1601371f53f5a695a315bc16de56 Mon Sep 17 00:00:00 2001 From: Jonas Hummelstrand Date: Tue, 24 Mar 2026 12:45:01 +0100 Subject: [PATCH 04/10] chore: Created a separate class for the segment-event-identifier, as well as mock data in the form of the string "Seg. Identifier". --- .../src/client/styles/defaultColors.scss | 4 +-- .../webui/src/client/styles/rundownView.scss | 34 ++++++++++++++++++- .../SegmentStoryboard/SegmentStoryboard.tsx | 1 + .../ui/SegmentTimeline/SegmentTimeline.tsx | 1 + 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/webui/src/client/styles/defaultColors.scss b/packages/webui/src/client/styles/defaultColors.scss index f612bd96a5..64e8a46853 100644 --- a/packages/webui/src/client/styles/defaultColors.scss +++ b/packages/webui/src/client/styles/defaultColors.scss @@ -33,8 +33,8 @@ --part-identifier: #363636; --part-identifier-text: #eee; - --segment-event-identifier: #363636; - --segment-event-identifier-text: #eee; + --segment-event-identifier: #ffee00; + --segment-event-identifier-text: #000000; --general-timecode-color: #ffff00; diff --git a/packages/webui/src/client/styles/rundownView.scss b/packages/webui/src/client/styles/rundownView.scss index 18b9cc5096..47738396d0 100644 --- a/packages/webui/src/client/styles/rundownView.scss +++ b/packages/webui/src/client/styles/rundownView.scss @@ -335,7 +335,7 @@ svg.icon { margin-top: auto; .segment-timeline__part-identifiers__identifier { - // The pill-shaped labels that appear in the title area of a segment, showing and linking to the part name or number + // The pill-shaped labels that appear in the segment header, showing and linking to the part name or number margin: 0.1rem; padding: 0.1rem 0.35rem 0.1rem 0.35rem; background-color: $part-identifier; @@ -371,6 +371,38 @@ svg.icon { } } } + .segment-timeline__segment-event-identifier{ + // The pill-shaped labels that appear in the segment header, showing extra information about the segment. + align-self: flex-start; + display: inline-block; + margin-top: auto; + margin: 0.25rem; + padding: 0.1rem 0.35rem 0.1rem 0.35rem; + background-color: $segment-event-identifier; + color: $segment-event-identifier-text; + border-radius: 99px; + font-family: "Roboto Flex", "Roboto", sans-serif; + font-style: normal; + font-size: 0.9rem; + letter-spacing: 0.03em; + line-height: 100%; + text-shadow: none; + font-variant-numeric: proportional-nums; + font-variation-settings: + 'wdth' 70, + 'wght' 500, + 'opsz' 120, + 'slnt' 0, + 'GRAD' 0, + 'XOPQ' 96, + 'XTRA' 468, + 'YOPQ' 79, + 'YTAS' 750, + 'YTDE' -203, + 'YTFI' 738, + 'YTLC' 514, + 'YTUC' 712; + } } &.has-identifiers { diff --git a/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx b/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx index 2b0877fbee..ba9b3cd3cb 100644 --- a/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx +++ b/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx @@ -604,6 +604,7 @@ export const SegmentStoryboard = React.memo( )}
)} +
Seg. Identifier
{identifiers.length > 0 && (
{identifiers.map((ident) => ( diff --git a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx index cd13c21f7f..d209fe30cd 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx @@ -1106,6 +1106,7 @@ export class SegmentTimelineClass extends React.Component )} +
Seg. Identifier
{identifiers.length > 0 && (
{identifiers.map((ident) => ( From f9eeb020d9d90ab15e1b5cec73a2d426ac54fd76 Mon Sep 17 00:00:00 2001 From: Jonas Hummelstrand Date: Wed, 25 Mar 2026 10:46:20 +0100 Subject: [PATCH 05/10] chore: Updated the colours of the segment event identifier. --- packages/webui/src/client/styles/defaultColors.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webui/src/client/styles/defaultColors.scss b/packages/webui/src/client/styles/defaultColors.scss index 64e8a46853..528e26af36 100644 --- a/packages/webui/src/client/styles/defaultColors.scss +++ b/packages/webui/src/client/styles/defaultColors.scss @@ -33,8 +33,8 @@ --part-identifier: #363636; --part-identifier-text: #eee; - --segment-event-identifier: #ffee00; - --segment-event-identifier-text: #000000; + --segment-event-identifier: #363636; + --segment-event-identifier-text: #fff; --general-timecode-color: #ffff00; From 6e608e390c49ec4e18b9caae70959273e27d453f Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 25 Mar 2026 13:02:03 +0000 Subject: [PATCH 06/10] chore: ui refactoring --- .../SegmentAdlibTesting.tsx | 36 +--- .../SegmentAdlibTestingContainer.tsx | 1 - .../getReactivePieceNoteCountsForSegment.tsx | 104 ---------- .../SegmentContainer/withResolvedSegment.ts | 13 +- .../ui/SegmentHeader/SegmentHeaderNotes.tsx | 183 ++++++++++++++++++ .../src/client/ui/SegmentList/SegmentList.tsx | 6 +- .../ui/SegmentList/SegmentListContainer.tsx | 1 - .../ui/SegmentList/SegmentListHeader.tsx | 41 +--- .../SegmentStoryboard/SegmentStoryboard.tsx | 41 +--- .../SegmentStoryboardContainer.tsx | 1 - .../ui/SegmentTimeline/SegmentTimeline.tsx | 46 +---- .../SegmentTimelineContainer.tsx | 1 - 12 files changed, 214 insertions(+), 260 deletions(-) delete mode 100644 packages/webui/src/client/ui/SegmentContainer/getReactivePieceNoteCountsForSegment.tsx create mode 100644 packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx diff --git a/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTesting.tsx b/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTesting.tsx index 78b2ccd0c8..a07d75ae65 100644 --- a/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTesting.tsx +++ b/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTesting.tsx @@ -1,9 +1,8 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' import { NoteSeverity } from '@sofie-automation/blueprints-integration' -import type { IContextMenuContext } from '../RundownView.js' -import type { IOutputLayerUi, PartUi, SegmentNoteCounts, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' +import { IContextMenuContext } from '../RundownView.js' +import { IOutputLayerUi, PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' import { ContextMenuTrigger } from '@jstarpl/react-contextmenu' -import { CriticalIconSmall, WarningIconSmall } from '../../lib/ui/icons/notifications.js' import { contextMenuHoldToDisplayTime, useCombinedRefs, useRundownViewEventBusListener } from '../../lib/lib.js' import { useTranslation } from 'react-i18next' import { literal } from '@sofie-automation/corelib/dist/lib' @@ -30,7 +29,7 @@ import { isPartPlayable } from '@sofie-automation/corelib/dist/dataModel/Part' import type { UIStudio } from '@sofie-automation/corelib/src/dataModel/Studio.js' import type { PieceUi } from '@sofie-automation/corelib/src/dataModel/Piece.js' import { isLoopRunning } from '@sofie-automation/corelib/src/playout/stateCacheResolver.js' -import { HOVER_TIMEOUT } from '../Shelf/DashboardPieceButton/types.js' +import { SegmentHeaderNotes } from '../SegmentHeader/SegmentHeaderNotes.js' interface IProps { id: string @@ -39,7 +38,6 @@ interface IProps { playlist: DBRundownPlaylist studio: UIStudio parts: Array - segmentNoteCounts: SegmentNoteCounts hasAlreadyPlayed: boolean hasGuestItems: boolean hasRemoteItems: boolean @@ -82,9 +80,6 @@ export const SegmentAdlibTesting = React.memo( const [squishedHover, setSquishedHover] = useState(null) const squishedHoverTimeout = useRef(null) - const criticalNotes = props.segmentNoteCounts.criticial - const warningNotes = props.segmentNoteCounts.warning - const getSegmentContext = () => { const ctx = literal({ segment: props.segment, @@ -466,30 +461,7 @@ export const SegmentAdlibTesting = React.memo( > {t('Adlib Testing')} - {(criticalNotes > 0 || warningNotes > 0) && ( -
- {criticalNotes > 0 && ( -
props.onHeaderNoteClick?.(props.segment._id, NoteSeverity.ERROR)} - aria-label={t('Critical problems')} - > - -
{criticalNotes}
-
- )} - {warningNotes > 0 && ( -
props.onHeaderNoteClick?.(props.segment._id, NoteSeverity.WARNING)} - aria-label={t('Warnings')} - > - -
{warningNotes}
-
- )} -
- )} +
{Object.values(props.segment.outputLayers) diff --git a/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTestingContainer.tsx b/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTestingContainer.tsx index 4250e2ff7b..846d6eb068 100644 --- a/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTestingContainer.tsx +++ b/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTestingContainer.tsx @@ -209,7 +209,6 @@ export const SegmentAdlibTestingContainer = withResolvedSegment(function segment={props.segmentui} studio={props.studio} parts={props.parts} - segmentNoteCounts={props.segmentNoteCounts} onItemClick={props.onPieceClick} onItemDoubleClick={props.onPieceDoubleClick} playlist={props.playlist} diff --git a/packages/webui/src/client/ui/SegmentContainer/getReactivePieceNoteCountsForSegment.tsx b/packages/webui/src/client/ui/SegmentContainer/getReactivePieceNoteCountsForSegment.tsx deleted file mode 100644 index 3d251d965a..0000000000 --- a/packages/webui/src/client/ui/SegmentContainer/getReactivePieceNoteCountsForSegment.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { NoteSeverity } from '@sofie-automation/blueprints-integration' -import { assertNever, literal } from '@sofie-automation/corelib/dist/lib' -import type { MongoFieldSpecifierOnes } from '@sofie-automation/corelib/dist/mongo' -import type { UISegmentPartNote } from '@sofie-automation/meteor-lib/dist/api/rundownNotifications' -import { PieceStatusCode } from '@sofie-automation/corelib/dist/dataModel/Piece' -import { getIgnorePieceContentStatus } from '../../lib/localStorage.js' -import { UIPartInstances, UIPieceContentStatuses, UISegmentPartNotes } from '../Collections.js' -import type { SegmentNoteCounts, SegmentUi } from './withResolvedSegment.js' -import { Notifications } from '../../collections/index.js' -import type { DBNotificationObj } from '@sofie-automation/corelib/dist/dataModel/Notifications' -import type { UIPieceContentStatus } from '@sofie-automation/corelib/dist/dataModel/PieceContentStatus' -import type { PartInstance } from '@sofie-automation/corelib/src/dataModel/PartInstance.js' - -export function getReactivePieceNoteCountsForSegment(segment: SegmentUi): SegmentNoteCounts { - const segmentNoteCounts: SegmentNoteCounts = { - criticial: 0, - warning: 0, - } - - const rawNotes = UISegmentPartNotes.find({ segmentId: segment._id }, { fields: { note: 1 } }).fetch() as Pick< - UISegmentPartNote, - 'note' - >[] - for (const note of rawNotes) { - if (note.note.type === NoteSeverity.ERROR) { - segmentNoteCounts.criticial++ - } else if (note.note.type === NoteSeverity.WARNING) { - segmentNoteCounts.warning++ - } - } - - const mediaObjectStatuses = UIPieceContentStatuses.find( - { - rundownId: segment.rundownId, - segmentId: segment._id, - }, - { - fields: literal>({ - _id: 1, - // @ts-expect-error deep property - 'status.status': 1, - }), - } - ).fetch() as Array & { status: Pick }> - - if (!getIgnorePieceContentStatus()) { - for (const obj of mediaObjectStatuses) { - switch (obj.status.status) { - case PieceStatusCode.OK: - case PieceStatusCode.SOURCE_NOT_READY: - case PieceStatusCode.UNKNOWN: - // Ignore - break - case PieceStatusCode.SOURCE_NOT_SET: - segmentNoteCounts.criticial++ - break - case PieceStatusCode.SOURCE_HAS_ISSUES: - case PieceStatusCode.SOURCE_BROKEN: - case PieceStatusCode.SOURCE_MISSING: - case PieceStatusCode.SOURCE_UNKNOWN_STATE: - segmentNoteCounts.warning++ - break - default: - assertNever(obj.status.status) - segmentNoteCounts.warning++ - break - } - } - } - - // Find any relevant notifications - const partInstancesForSegment = UIPartInstances.find( - { segmentId: segment._id, reset: { $ne: true } }, - { - fields: { - _id: 1, - }, - } - ).fetch() as Array> - const rawNotifications = Notifications.find( - { - $or: [ - { 'relatedTo.segmentId': segment._id }, - { - 'relatedTo.partInstanceId': { $in: partInstancesForSegment.map((p) => p._id) }, - }, - ], - }, - { - fields: { - severity: 1, - }, - } - ).fetch() as Array> - for (const notification of rawNotifications) { - if (notification.severity === NoteSeverity.ERROR) { - segmentNoteCounts.criticial++ - } else if (notification.severity === NoteSeverity.WARNING) { - segmentNoteCounts.warning++ - } - } - - return segmentNoteCounts -} diff --git a/packages/webui/src/client/ui/SegmentContainer/withResolvedSegment.ts b/packages/webui/src/client/ui/SegmentContainer/withResolvedSegment.ts index 5bd9e331c1..29815725f5 100644 --- a/packages/webui/src/client/ui/SegmentContainer/withResolvedSegment.ts +++ b/packages/webui/src/client/ui/SegmentContainer/withResolvedSegment.ts @@ -15,8 +15,7 @@ import type { RundownLayoutFilterBase, RundownViewLayout, } from '@sofie-automation/meteor-lib/dist/collections/RundownLayouts' -import { getReactivePieceNoteCountsForSegment } from './getReactivePieceNoteCountsForSegment.js' -import type { SegmentViewMode } from './SegmentViewModes.js' +import { SegmentViewMode } from './SegmentViewModes.js' import { PlaylistTiming } from '@sofie-automation/corelib/dist/playout/rundownTiming' import type { AdlibSegmentUi } from '../../lib/shelf.js' import type { @@ -93,15 +92,9 @@ export interface IResolvedSegmentProps { showDurationSourceLayers?: Set } -export interface SegmentNoteCounts { - criticial: number - warning: number -} - export interface ITrackedResolvedSegmentProps { segmentui: SegmentUi | undefined parts: Array - segmentNoteCounts: SegmentNoteCounts hasRemoteItems: boolean hasGuestItems: boolean hasAlreadyPlayed: boolean @@ -125,7 +118,6 @@ export function withResolvedSegment 0; i--) { @@ -280,7 +270,6 @@ export function withResolvedSegment void +} +interface SegmentNoteCounts { + criticalNotes: number + warningNotes: number + headerNotes: ITranslatableMessage[] +} + +export function SegmentHeaderNotes({ classname, segmentId, onHeaderNoteClick }: SegmentHeaderNotesProps): JSX.Element { + const { t } = useTranslation() + + const { criticalNotes, warningNotes, headerNotes } = useTracker( + () => getReactivePieceNoteCountsForSegment(segmentId), + [segmentId], + { criticalNotes: 0, warningNotes: 0, headerNotes: [] } + ) + + return ( + <> + {(criticalNotes > 0 || warningNotes > 0) && ( +
+ {criticalNotes > 0 && ( +
onHeaderNoteClick?.(segmentId, NoteSeverity.ERROR)} + aria-label={t('Critical problems')} + > + +
{criticalNotes}
+
+ )} + {warningNotes > 0 && ( +
onHeaderNoteClick?.(segmentId, NoteSeverity.WARNING)} + aria-label={t('Warnings')} + > + +
{warningNotes}
+
+ )} +
+ )} + + {headerNotes.map((event, index) => ( +
+ {event.key} +
+ ))} + + ) +} + +function getReactivePieceNoteCountsForSegment(segmentId: SegmentId): SegmentNoteCounts { + const segmentNoteCounts: SegmentNoteCounts = { + criticalNotes: 0, + warningNotes: 0, + headerNotes: [], // TODO - define + } + + const rawNotes = UISegmentPartNotes.find({ segmentId }, { fields: { note: 1 } }).fetch() as Pick< + UISegmentPartNote, + 'note' + >[] + for (const note of rawNotes) { + switch (note.note.type) { + case NoteSeverity.ERROR: + segmentNoteCounts.criticalNotes++ + break + case NoteSeverity.WARNING: + segmentNoteCounts.warningNotes++ + break + case NoteSeverity.INFO: + // Ignore + break + default: + assertNever(note.note.type) + } + } + + const mediaObjectStatuses = UIPieceContentStatuses.find( + { + segmentId, + }, + { + fields: literal>({ + _id: 1, + // @ts-expect-error deep property + 'status.status': 1, + }), + } + ).fetch() as Array & { status: Pick }> + + if (!getIgnorePieceContentStatus()) { + for (const obj of mediaObjectStatuses) { + switch (obj.status.status) { + case PieceStatusCode.OK: + case PieceStatusCode.SOURCE_NOT_READY: + case PieceStatusCode.UNKNOWN: + // Ignore + break + case PieceStatusCode.SOURCE_NOT_SET: + segmentNoteCounts.criticalNotes++ + break + case PieceStatusCode.SOURCE_HAS_ISSUES: + case PieceStatusCode.SOURCE_BROKEN: + case PieceStatusCode.SOURCE_MISSING: + case PieceStatusCode.SOURCE_UNKNOWN_STATE: + segmentNoteCounts.warningNotes++ + break + default: + assertNever(obj.status.status) + segmentNoteCounts.warningNotes++ + break + } + } + } + + // Find any relevant notifications + const partInstancesForSegment = UIPartInstances.find( + { segmentId: segmentId, reset: { $ne: true } }, + { + fields: { + _id: 1, + }, + } + ).fetch() as Array> + const rawNotifications = Notifications.find( + { + $or: [ + { 'relatedTo.segmentId': segmentId }, + { + 'relatedTo.partInstanceId': { $in: partInstancesForSegment.map((p) => p._id) }, + }, + ], + }, + { + fields: { + severity: 1, + message: 1, + }, + } + ).fetch() as Array> + for (const notification of rawNotifications) { + switch (notification.severity) { + case NoteSeverity.ERROR: + segmentNoteCounts.criticalNotes++ + break + case NoteSeverity.WARNING: + segmentNoteCounts.warningNotes++ + break + case NoteSeverity.INFO: + // Ignore + break + default: + assertNever(notification.severity) + } + } + + return segmentNoteCounts +} diff --git a/packages/webui/src/client/ui/SegmentList/SegmentList.tsx b/packages/webui/src/client/ui/SegmentList/SegmentList.tsx index 726b3eaaa0..d766036ffb 100644 --- a/packages/webui/src/client/ui/SegmentList/SegmentList.tsx +++ b/packages/webui/src/client/ui/SegmentList/SegmentList.tsx @@ -3,8 +3,8 @@ import classNames from 'classnames' import { RundownHoldState } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/RundownPlaylist' import type { RundownViewPlaylist } from '../../lib/rundownPlaylistProjection.js' import { UIStateStorage } from '../../lib/UIStateStorage.js' -import type { PartUi, SegmentNoteCounts, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' -import type { IContextMenuContext } from '../RundownView.js' +import { PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' +import { IContextMenuContext } from '../RundownView.js' import { useCombinedRefs } from '../../lib/lib.js' import { literal } from '@sofie-automation/corelib/dist/lib' import { isPartPlayable } from '@sofie-automation/corelib/dist/dataModel/Part' @@ -38,7 +38,6 @@ interface IProps { segment: SegmentUi playlist: RundownViewPlaylist parts: Array - segmentNoteCounts: SegmentNoteCounts fixedSegmentDuration: boolean showCountdownToSegment: boolean @@ -245,7 +244,6 @@ const SegmentListInner = React.forwardRef(function Segme parts={props.parts} segment={props.segment} playlist={props.playlist} - segmentNoteCounts={props.segmentNoteCounts} highlight={highlight} isLiveSegment={props.isLiveSegment} isNextSegment={props.isNextSegment} diff --git a/packages/webui/src/client/ui/SegmentList/SegmentListContainer.tsx b/packages/webui/src/client/ui/SegmentList/SegmentListContainer.tsx index 8f3bb96a9e..78535080ff 100644 --- a/packages/webui/src/client/ui/SegmentList/SegmentListContainer.tsx +++ b/packages/webui/src/client/ui/SegmentList/SegmentListContainer.tsx @@ -201,7 +201,6 @@ export const SegmentListContainer = withResolvedSegment(function Segment parts={props.parts} playlist={props.playlist} currentPartWillAutoNext={currentPartWillAutoNext} - segmentNoteCounts={props.segmentNoteCounts} isLiveSegment={isLiveSegment} isNextSegment={isNextSegment} isQueuedSegment={props.playlist.queuedSegmentId === props.segmentui._id} diff --git a/packages/webui/src/client/ui/SegmentList/SegmentListHeader.tsx b/packages/webui/src/client/ui/SegmentList/SegmentListHeader.tsx index 382ceed09b..a4dd50bdc7 100644 --- a/packages/webui/src/client/ui/SegmentList/SegmentListHeader.tsx +++ b/packages/webui/src/client/ui/SegmentList/SegmentListHeader.tsx @@ -5,7 +5,7 @@ import { contextMenuHoldToDisplayTime } from '../../lib/lib.js' import { ErrorBoundary } from '../../lib/ErrorBoundary.js' import { SwitchViewModeButton } from '../SegmentContainer/SwitchViewModeButton.js' import { SegmentViewMode } from '../SegmentContainer/SegmentViewModes.js' -import type { PartUi, SegmentNoteCounts, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' +import { PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' import { PartCountdown } from '../RundownView/RundownTiming/PartCountdown.js' import { SegmentDuration } from '../RundownView/RundownTiming/SegmentDuration.js' import type { PartId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' @@ -13,7 +13,8 @@ import { useTranslation } from 'react-i18next' import type { RundownViewPlaylist } from '../../lib/rundownPlaylistProjection.js' import type { IContextMenuContext } from '../RundownView.js' import { NoteSeverity } from '@sofie-automation/blueprints-integration' -import { CriticalIconSmall, WarningIconSmall } from '../../lib/ui/icons/notifications.js' +import { SegmentTimeAnchorTime } from '../RundownView/RundownTiming/SegmentTimeAnchorTime.js' +import { SegmentHeaderNotes } from '../SegmentHeader/SegmentHeaderNotes.js' export function SegmentListHeader({ isDetached, @@ -22,7 +23,6 @@ export function SegmentListHeader({ parts, playlist, highlight, - segmentNoteCounts, isLiveSegment, isNextSegment, isQueuedSegment, @@ -41,7 +41,6 @@ export function SegmentListHeader({ segment: SegmentUi playlist: RundownViewPlaylist parts: Array - segmentNoteCounts: SegmentNoteCounts highlight: boolean isLiveSegment: boolean isNextSegment: boolean @@ -77,9 +76,6 @@ export function SegmentListHeader({ // setDetached(shouldDetach) // } - const criticalNotes = segmentNoteCounts.criticial - const warningNotes = segmentNoteCounts.warning - const contents = (
- {(criticalNotes > 0 || warningNotes > 0) && ( -
- {criticalNotes > 0 && ( -
onHeaderNoteClick?.(segment._id, NoteSeverity.ERROR)} - aria-label={t('Critical problems')} - > - -
{criticalNotes}
-
- )} - {warningNotes > 0 && ( -
onHeaderNoteClick?.(segment._id, NoteSeverity.WARNING)} - aria-label={t('Warnings')} - > - -
{warningNotes}
-
- )} -
- )} + + + diff --git a/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx b/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx index ba9b3cd3cb..4c8d6c61b1 100644 --- a/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx +++ b/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx @@ -1,11 +1,9 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' import { NoteSeverity } from '@sofie-automation/blueprints-integration' -import { RundownHoldState } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/RundownPlaylist' -import type { RundownViewPlaylist } from '../../lib/rundownPlaylistProjection.js' -import type { IContextMenuContext } from '../RundownView.js' -import type { IOutputLayerUi, PartUi, SegmentNoteCounts, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' +import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { IContextMenuContext } from '../RundownView.js' +import { IOutputLayerUi, PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' import { ContextMenuTrigger } from '@jstarpl/react-contextmenu' -import { CriticalIconSmall, WarningIconSmall } from '../../lib/ui/icons/notifications.js' import { SegmentDuration } from '../RundownView/RundownTiming/SegmentDuration.js' import { PartCountdown } from '../RundownView/RundownTiming/PartCountdown.js' import { contextMenuHoldToDisplayTime, useCombinedRefs, useRundownViewEventBusListener } from '../../lib/lib.js' @@ -46,7 +44,7 @@ import { isQuickLoopEnd as getIsQuickLoopEnd, isEntirePlaylistLooping as getIsEntirePlaylistLooping, } from '@sofie-automation/corelib/src/playout/stateCacheResolver.js' -import { HOVER_TIMEOUT } from '../Shelf/DashboardPieceButton/types.js' +import { SegmentHeaderNotes } from '../SegmentHeader/SegmentHeaderNotes.js' interface IProps { id: string @@ -55,7 +53,6 @@ interface IProps { playlist: RundownViewPlaylist studio: UIStudio parts: Array - segmentNoteCounts: SegmentNoteCounts // timeScale: number // maxTimeScale: number // onRecalculateMaxTimeScale: () => Promise @@ -131,9 +128,6 @@ export const SegmentStoryboard = React.memo( } } - const criticalNotes = props.segmentNoteCounts.criticial - const warningNotes = props.segmentNoteCounts.warning - const [useTimeOfDayCountdowns, setUseTimeOfDayCountdowns] = useState( UIStateStorage.getItemBoolean( `rundownView.${props.playlist._id}`, @@ -580,31 +574,8 @@ export const SegmentStoryboard = React.memo( > {props.segment.name} - {(criticalNotes > 0 || warningNotes > 0) && ( -
- {criticalNotes > 0 && ( -
props.onHeaderNoteClick?.(props.segment._id, NoteSeverity.ERROR)} - aria-label={t('Critical problems')} - > - -
{criticalNotes}
-
- )} - {warningNotes > 0 && ( -
props.onHeaderNoteClick?.(props.segment._id, NoteSeverity.WARNING)} - aria-label={t('Warnings')} - > - -
{warningNotes}
-
- )} -
- )} -
Seg. Identifier
+ + {identifiers.length > 0 && (
{identifiers.map((ident) => ( diff --git a/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboardContainer.tsx b/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboardContainer.tsx index 33b8c5e386..18d5443521 100644 --- a/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboardContainer.tsx +++ b/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboardContainer.tsx @@ -211,7 +211,6 @@ export const SegmentStoryboardContainer = withResolvedSegment(function S segment={props.segmentui} studio={props.studio} parts={props.parts} - segmentNoteCounts={props.segmentNoteCounts} onItemClick={props.onPieceClick} onItemDoubleClick={props.onPieceDoubleClick} playlist={props.playlist} diff --git a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx index d209fe30cd..a74457a0a5 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx @@ -27,7 +27,6 @@ import { literal } from '@sofie-automation/corelib/dist/lib' import { protectString, unprotectString } from '@sofie-automation/shared-lib/dist/lib/protectedString' import { isPartPlayable, type PartExtended } from '@sofie-automation/corelib/dist/dataModel/Part' import { contextMenuHoldToDisplayTime } from '../../lib/lib.js' -import { WarningIconSmall, CriticalIconSmall } from '../../lib/ui/icons/notifications.js' import RundownViewEventBus, { RundownViewEvents, type HighlightEvent, @@ -46,8 +45,8 @@ import { import { SegmentTimelineZoomButtons } from './SegmentTimelineZoomButtons.js' import { SegmentViewMode } from '../SegmentContainer/SegmentViewModes.js' import { SwitchViewModeButton } from '../SegmentContainer/SwitchViewModeButton.js' -import type { PartId, PartInstanceId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' -import type { SegmentNoteCounts } from '../SegmentContainer/withResolvedSegment.js' +import { PartId, PartInstanceId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { RundownHoldState } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' import { withTiming, TimingTickResolution, @@ -62,6 +61,7 @@ import { hasUserEditableContent } from '../UserEditOperations/PropertiesPanel.js import type { UIStudio } from '@sofie-automation/corelib/src/dataModel/Studio.js' import type { PieceUi } from '@sofie-automation/corelib/src/dataModel/Piece.js' import { isLoopRunning, wrapPartToTemporaryInstance } from '@sofie-automation/corelib/src/playout/stateCacheResolver.js' +import { SegmentHeaderNotes } from '../SegmentHeader/SegmentHeaderNotes.js' interface IProps { id: string @@ -71,7 +71,6 @@ interface IProps { followLiveSegments: boolean studio: UIStudio parts: Array - segmentNoteCounts: SegmentNoteCounts timeScale: number maxTimeScale: number onRecalculateMaxTimeScale: () => Promise @@ -979,9 +978,6 @@ export class SegmentTimelineClass extends React.Component = this.props.parts .map((p) => p.instance.part.identifier @@ -1076,37 +1072,11 @@ export class SegmentTimelineClass extends React.Component {this.props.segment.name} - {(criticalNotes > 0 || warningNotes > 0) && ( -
- {criticalNotes > 0 && ( -
- this.props.onHeaderNoteClick && - this.props.onHeaderNoteClick(this.props.segment._id, NoteSeverity.ERROR) - } - aria-label={t('Critical problems')} - > - -
{criticalNotes}
-
- )} - {warningNotes > 0 && ( -
- this.props.onHeaderNoteClick && - this.props.onHeaderNoteClick(this.props.segment._id, NoteSeverity.WARNING) - } - aria-label={t('Warnings')} - > - -
{warningNotes}
-
- )} -
- )} -
Seg. Identifier
+ + {identifiers.length > 0 && (
{identifiers.map((ident) => ( diff --git a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimelineContainer.tsx b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimelineContainer.tsx index ec310f92b1..a8a9072837 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimelineContainer.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimelineContainer.tsx @@ -701,7 +701,6 @@ const SegmentTimelineContainerContent = withResolvedSegment( segment={this.props.segmentui} studio={this.props.studio} parts={this.props.parts} - segmentNoteCounts={this.props.segmentNoteCounts} timeScale={this.state.timeScale} maxTimeScale={this.state.maxTimeScale} onRecalculateMaxTimeScale={this.updateMaxTimeScale} From 21b06d280f2e9d3893d9d6611d7b5dc40fe5f849 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 31 Mar 2026 14:23:43 +0100 Subject: [PATCH 07/10] feat: use segmentHeaderNotes --- .../src/documents/part.ts | 3 ++ .../job-worker/src/blueprints/context/lib.ts | 12 ++++- .../src/ingest/generationSegment.ts | 3 ++ .../ui/SegmentHeader/SegmentHeaderNotes.tsx | 45 +++++++++++++++++-- 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/packages/blueprints-integration/src/documents/part.ts b/packages/blueprints-integration/src/documents/part.ts index 3554e7576a..c27de0381d 100644 --- a/packages/blueprints-integration/src/documents/part.ts +++ b/packages/blueprints-integration/src/documents/part.ts @@ -98,6 +98,9 @@ export interface IBlueprintMutatablePart): IBlueprintP displayDurationGroup: part.displayDurationGroup, displayDuration: part.displayDuration, identifier: part.identifier, + segmentHeaderNotes: clone(part.segmentHeaderNotes), hackListenToMediaObjectUpdates: clone( part.hackListenToMediaObjectUpdates ), @@ -741,7 +743,13 @@ export function convertPartialBlueprintMutablePartToCore( blueprintId, ]) } else { - delete playoutUpdatePart.userEditOperations + delete playoutUpdatePart.userEditProperties + } + + if ('segmentHeaderNotes' in updatePart) { + playoutUpdatePart.segmentHeaderNotes = updatePart.segmentHeaderNotes?.map((note) => + wrapTranslatableMessageFromBlueprints(note, [blueprintId]) + ) } return playoutUpdatePart diff --git a/packages/job-worker/src/ingest/generationSegment.ts b/packages/job-worker/src/ingest/generationSegment.ts index 8526472a47..6b7cf909e4 100644 --- a/packages/job-worker/src/ingest/generationSegment.ts +++ b/packages/job-worker/src/ingest/generationSegment.ts @@ -405,6 +405,9 @@ function updateModelWithGeneratedPart( ]), } : undefined, + segmentHeaderNotes: blueprintPart.part.segmentHeaderNotes?.map((note) => + wrapTranslatableMessageFromBlueprints(note, [blueprintId]) + ), userEditOperations: translateUserEditsFromBlueprint(blueprintPart.part.userEditOperations, [blueprintId]), userEditProperties: translateUserEditPropertiesFromBlueprint(blueprintPart.part.userEditProperties, [ blueprintId, diff --git a/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx b/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx index 7672455aaf..a0b5c20448 100644 --- a/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx +++ b/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx @@ -12,9 +12,10 @@ import { PartInstance } from '@sofie-automation/meteor-lib/dist/collections/Part import { literal } from 'shuttle-webhid' import { Notifications } from '../../collections' import { getIgnorePieceContentStatus } from '../../lib/localStorage' -import { UISegmentPartNotes, UIPieceContentStatuses, UIPartInstances } from '../Collections' +import { UISegmentPartNotes, UIPieceContentStatuses, UIPartInstances, UIParts } from '../Collections' import { useTracker } from '../../lib/ReactMeteorData/ReactMeteorData' import type { ITranslatableMessage } from '@sofie-automation/corelib/dist/TranslatableMessage' +import type { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' export interface SegmentHeaderNotesProps { /** Override the classname of the root div */ @@ -77,7 +78,7 @@ function getReactivePieceNoteCountsForSegment(segmentId: SegmentId): SegmentNote const segmentNoteCounts: SegmentNoteCounts = { criticalNotes: 0, warningNotes: 0, - headerNotes: [], // TODO - define + headerNotes: [], } const rawNotes = UISegmentPartNotes.find({ segmentId }, { fields: { note: 1 } }).fetch() as Pick< @@ -144,9 +145,15 @@ function getReactivePieceNoteCountsForSegment(segmentId: SegmentId): SegmentNote { fields: { _id: 1, + // @ts-expect-error deep property + 'part._id': 1, + 'part._rank': 1, + 'part.segmentHeaderNotes': 1, }, } - ).fetch() as Array> + ).fetch() as Array< + Pick & { part: Pick } + > const rawNotifications = Notifications.find( { $or: [ @@ -179,5 +186,37 @@ function getReactivePieceNoteCountsForSegment(segmentId: SegmentId): SegmentNote } } + const partsForSegment = UIParts.find( + { segmentId }, + { + fields: { + _id: 1, + _rank: 1, + segmentHeaderNotes: 1, + }, + } + ).fetch() as Array> + + // Collect the segment header notes from the parts in part rank order, with partinstance taking priority over the part + const partIdsWithInstance = new Set(partInstancesForSegment.map((pi) => pi.part._id)) + + const mergedNoteEntries: Array<{ rank: number; notes: ITranslatableMessage[] }> = [] + for (const partInstance of partInstancesForSegment) { + if (partInstance.part.segmentHeaderNotes?.length) { + mergedNoteEntries.push({ rank: partInstance.part._rank, notes: partInstance.part.segmentHeaderNotes }) + } + } + for (const part of partsForSegment) { + if (partIdsWithInstance.has(part._id)) continue + if (part.segmentHeaderNotes?.length) { + mergedNoteEntries.push({ rank: part._rank, notes: part.segmentHeaderNotes }) + } + } + + mergedNoteEntries.sort((a, b) => a.rank - b.rank) + for (const entry of mergedNoteEntries) { + segmentNoteCounts.headerNotes.push(...entry.notes) + } + return segmentNoteCounts } From a3a80846019b654e9bc456ca3ec70cc593ed9481 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 13 Apr 2026 10:30:14 +0100 Subject: [PATCH 08/10] rename --- packages/webui/src/client/styles/_colorScheme.scss | 4 ++-- .../webui/src/client/styles/defaultColors.scss | 4 ++-- packages/webui/src/client/styles/rundownView.scss | 14 ++++++-------- .../client/ui/SegmentHeader/SegmentHeaderNotes.tsx | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/webui/src/client/styles/_colorScheme.scss b/packages/webui/src/client/styles/_colorScheme.scss index 60b6b61020..a0283d7a3b 100644 --- a/packages/webui/src/client/styles/_colorScheme.scss +++ b/packages/webui/src/client/styles/_colorScheme.scss @@ -32,8 +32,8 @@ $general-timecode-color: var(--general-timecode-color); $part-identifier: var(--part-identifier); $part-identifier-text: var(--part-identifier-text); -$segment-event-identifier: var(--segment-event-identifier); -$segment-event-identifier-text: var(--segment-event-identifier-text); +$segment-header-note-identifier: var(--segment-header-note-identifier); +$segment-header-note-identifier-text: var(--segment-header-note-identifier-text); $ui-button-primary: var(--ui-button-primary); $ui-button-primary--hover: var(--ui-button-primary--hover); diff --git a/packages/webui/src/client/styles/defaultColors.scss b/packages/webui/src/client/styles/defaultColors.scss index 528e26af36..17cb732d08 100644 --- a/packages/webui/src/client/styles/defaultColors.scss +++ b/packages/webui/src/client/styles/defaultColors.scss @@ -33,8 +33,8 @@ --part-identifier: #363636; --part-identifier-text: #eee; - --segment-event-identifier: #363636; - --segment-event-identifier-text: #fff; + --segment-header-note-identifier: #363636; + --segment-header-note-identifier-text: #fff; --general-timecode-color: #ffff00; diff --git a/packages/webui/src/client/styles/rundownView.scss b/packages/webui/src/client/styles/rundownView.scss index 47738396d0..4259ef155c 100644 --- a/packages/webui/src/client/styles/rundownView.scss +++ b/packages/webui/src/client/styles/rundownView.scss @@ -341,7 +341,7 @@ svg.icon { background-color: $part-identifier; color: $part-identifier-text; border-radius: 99px; - font-family: "Roboto Flex", "Roboto", sans-serif; + font-family: 'Roboto Flex', 'Roboto', sans-serif; font-style: normal; font-size: 0.9rem; letter-spacing: 0.03em; @@ -371,17 +371,17 @@ svg.icon { } } } - .segment-timeline__segment-event-identifier{ + .segment-timeline__segment-header-note-identifier { // The pill-shaped labels that appear in the segment header, showing extra information about the segment. align-self: flex-start; display: inline-block; margin-top: auto; margin: 0.25rem; padding: 0.1rem 0.35rem 0.1rem 0.35rem; - background-color: $segment-event-identifier; - color: $segment-event-identifier-text; + background-color: $segment-header-note-identifier; + color: $segment-header-note-identifier-text; border-radius: 99px; - font-family: "Roboto Flex", "Roboto", sans-serif; + font-family: 'Roboto Flex', 'Roboto', sans-serif; font-style: normal; font-size: 0.9rem; letter-spacing: 0.03em; @@ -905,7 +905,6 @@ svg.icon { } .segment-timeline__part { .segment-timeline__part__invalid-cover { - mix-blend-mode: color-burn; background-image: repeating-linear-gradient( 45deg, @@ -1390,7 +1389,6 @@ svg.icon { right: 1px; z-index: 10; pointer-events: all; - mix-blend-mode: color-burn; background-image: repeating-linear-gradient( 45deg, @@ -2365,7 +2363,7 @@ svg.icon { background-color: $part-identifier; color: $part-identifier-text; - font-family: "Roboto Flex", "Roboto", sans-serif; + font-family: 'Roboto Flex', 'Roboto', sans-serif; font-style: normal; font-size: 0.9rem; letter-spacing: 0.03em; diff --git a/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx b/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx index a0b5c20448..83bf081f9d 100644 --- a/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx +++ b/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx @@ -66,7 +66,7 @@ export function SegmentHeaderNotes({ classname, segmentId, onHeaderNoteClick }: )} {headerNotes.map((event, index) => ( -
+
{event.key}
))} From a5f09291ff6062e2caa5d817040670a53e3e6d90 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 13 Apr 2026 10:47:42 +0100 Subject: [PATCH 09/10] lint --- .../webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx b/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx index 83bf081f9d..74cd02aefa 100644 --- a/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx +++ b/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx @@ -8,7 +8,6 @@ import { UIPieceContentStatus } from '@sofie-automation/corelib/dist/dataModel/P import { assertNever } from '@sofie-automation/corelib/dist/lib' import { MongoFieldSpecifierOnes } from '@sofie-automation/corelib/dist/mongo' import { UISegmentPartNote } from '@sofie-automation/meteor-lib/dist/api/rundownNotifications' -import { PartInstance } from '@sofie-automation/meteor-lib/dist/collections/PartInstances' import { literal } from 'shuttle-webhid' import { Notifications } from '../../collections' import { getIgnorePieceContentStatus } from '../../lib/localStorage' @@ -16,6 +15,7 @@ import { UISegmentPartNotes, UIPieceContentStatuses, UIPartInstances, UIParts } import { useTracker } from '../../lib/ReactMeteorData/ReactMeteorData' import type { ITranslatableMessage } from '@sofie-automation/corelib/dist/TranslatableMessage' import type { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' +import type { PartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' export interface SegmentHeaderNotesProps { /** Override the classname of the root div */ From bb7cc9dfe8884ae5da3d3cf8ca602016a2e66539 Mon Sep 17 00:00:00 2001 From: Peter C <12292660+PeterC89@users.noreply.github.com> Date: Tue, 25 Aug 2026 09:50:31 +0100 Subject: [PATCH 10/10] fix: Merge issues --- packages/job-worker/src/blueprints/context/lib.ts | 1 + .../ui/SegmentAdlibTesting/SegmentAdlibTesting.tsx | 7 ++++--- .../client/ui/SegmentContainer/withResolvedSegment.ts | 2 +- .../src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx | 10 +++++----- .../webui/src/client/ui/SegmentList/SegmentList.tsx | 4 ++-- .../src/client/ui/SegmentList/SegmentListHeader.tsx | 5 ++--- .../client/ui/SegmentStoryboard/SegmentStoryboard.tsx | 10 ++++++---- .../src/client/ui/SegmentTimeline/SegmentTimeline.tsx | 7 +++---- 8 files changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/job-worker/src/blueprints/context/lib.ts b/packages/job-worker/src/blueprints/context/lib.ts index 7a498009f8..c2944db427 100644 --- a/packages/job-worker/src/blueprints/context/lib.ts +++ b/packages/job-worker/src/blueprints/context/lib.ts @@ -54,6 +54,7 @@ import { IOutputLayer, ISourceLayer, ITranslatableMessage, + NoteSeverity, PieceAbSessionInfo, RundownPlaylistTiming, } from '@sofie-automation/blueprints-integration' diff --git a/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTesting.tsx b/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTesting.tsx index a07d75ae65..f9b9e935d3 100644 --- a/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTesting.tsx +++ b/packages/webui/src/client/ui/SegmentAdlibTesting/SegmentAdlibTesting.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' -import { NoteSeverity } from '@sofie-automation/blueprints-integration' -import { IContextMenuContext } from '../RundownView.js' -import { IOutputLayerUi, PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' +import type { NoteSeverity } from '@sofie-automation/blueprints-integration' +import type { IContextMenuContext } from '../RundownView.js' +import type { IOutputLayerUi, PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' import { ContextMenuTrigger } from '@jstarpl/react-contextmenu' import { contextMenuHoldToDisplayTime, useCombinedRefs, useRundownViewEventBusListener } from '../../lib/lib.js' import { useTranslation } from 'react-i18next' @@ -30,6 +30,7 @@ import type { UIStudio } from '@sofie-automation/corelib/src/dataModel/Studio.js import type { PieceUi } from '@sofie-automation/corelib/src/dataModel/Piece.js' import { isLoopRunning } from '@sofie-automation/corelib/src/playout/stateCacheResolver.js' import { SegmentHeaderNotes } from '../SegmentHeader/SegmentHeaderNotes.js' +import { HOVER_TIMEOUT } from '../Shelf/DashboardPieceButton/types.js' interface IProps { id: string diff --git a/packages/webui/src/client/ui/SegmentContainer/withResolvedSegment.ts b/packages/webui/src/client/ui/SegmentContainer/withResolvedSegment.ts index 29815725f5..57cc27526d 100644 --- a/packages/webui/src/client/ui/SegmentContainer/withResolvedSegment.ts +++ b/packages/webui/src/client/ui/SegmentContainer/withResolvedSegment.ts @@ -15,7 +15,7 @@ import type { RundownLayoutFilterBase, RundownViewLayout, } from '@sofie-automation/meteor-lib/dist/collections/RundownLayouts' -import { SegmentViewMode } from './SegmentViewModes.js' +import type { SegmentViewMode } from './SegmentViewModes.js' import { PlaylistTiming } from '@sofie-automation/corelib/dist/playout/rundownTiming' import type { AdlibSegmentUi } from '../../lib/shelf.js' import type { diff --git a/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx b/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx index 74cd02aefa..b0d9541dc8 100644 --- a/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx +++ b/packages/webui/src/client/ui/SegmentHeader/SegmentHeaderNotes.tsx @@ -1,13 +1,13 @@ import { NoteSeverity } from '@sofie-automation/blueprints-integration' import { CriticalIconSmall, WarningIconSmall } from '../../lib/ui/icons/notifications' -import { SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import type { SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { useTranslation } from 'react-i18next' -import { DBNotificationObj } from '@sofie-automation/corelib/dist/dataModel/Notifications' +import type { DBNotificationObj } from '@sofie-automation/corelib/dist/dataModel/Notifications' import { PieceStatusCode } from '@sofie-automation/corelib/dist/dataModel/Piece' -import { UIPieceContentStatus } from '@sofie-automation/corelib/dist/dataModel/PieceContentStatus' +import type { UIPieceContentStatus } from '@sofie-automation/corelib/dist/dataModel/PieceContentStatus' import { assertNever } from '@sofie-automation/corelib/dist/lib' -import { MongoFieldSpecifierOnes } from '@sofie-automation/corelib/dist/mongo' -import { UISegmentPartNote } from '@sofie-automation/meteor-lib/dist/api/rundownNotifications' +import type { MongoFieldSpecifierOnes } from '@sofie-automation/corelib/dist/mongo' +import type { UISegmentPartNote } from '@sofie-automation/meteor-lib/dist/api/rundownNotifications' import { literal } from 'shuttle-webhid' import { Notifications } from '../../collections' import { getIgnorePieceContentStatus } from '../../lib/localStorage' diff --git a/packages/webui/src/client/ui/SegmentList/SegmentList.tsx b/packages/webui/src/client/ui/SegmentList/SegmentList.tsx index d766036ffb..395ff2b889 100644 --- a/packages/webui/src/client/ui/SegmentList/SegmentList.tsx +++ b/packages/webui/src/client/ui/SegmentList/SegmentList.tsx @@ -3,8 +3,8 @@ import classNames from 'classnames' import { RundownHoldState } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/RundownPlaylist' import type { RundownViewPlaylist } from '../../lib/rundownPlaylistProjection.js' import { UIStateStorage } from '../../lib/UIStateStorage.js' -import { PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' -import { IContextMenuContext } from '../RundownView.js' +import type { PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' +import type { IContextMenuContext } from '../RundownView.js' import { useCombinedRefs } from '../../lib/lib.js' import { literal } from '@sofie-automation/corelib/dist/lib' import { isPartPlayable } from '@sofie-automation/corelib/dist/dataModel/Part' diff --git a/packages/webui/src/client/ui/SegmentList/SegmentListHeader.tsx b/packages/webui/src/client/ui/SegmentList/SegmentListHeader.tsx index a4dd50bdc7..d437054785 100644 --- a/packages/webui/src/client/ui/SegmentList/SegmentListHeader.tsx +++ b/packages/webui/src/client/ui/SegmentList/SegmentListHeader.tsx @@ -5,15 +5,14 @@ import { contextMenuHoldToDisplayTime } from '../../lib/lib.js' import { ErrorBoundary } from '../../lib/ErrorBoundary.js' import { SwitchViewModeButton } from '../SegmentContainer/SwitchViewModeButton.js' import { SegmentViewMode } from '../SegmentContainer/SegmentViewModes.js' -import { PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' +import type { PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' import { PartCountdown } from '../RundownView/RundownTiming/PartCountdown.js' import { SegmentDuration } from '../RundownView/RundownTiming/SegmentDuration.js' import type { PartId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { useTranslation } from 'react-i18next' import type { RundownViewPlaylist } from '../../lib/rundownPlaylistProjection.js' import type { IContextMenuContext } from '../RundownView.js' -import { NoteSeverity } from '@sofie-automation/blueprints-integration' -import { SegmentTimeAnchorTime } from '../RundownView/RundownTiming/SegmentTimeAnchorTime.js' +import type { NoteSeverity } from '@sofie-automation/blueprints-integration' import { SegmentHeaderNotes } from '../SegmentHeader/SegmentHeaderNotes.js' export function SegmentListHeader({ diff --git a/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx b/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx index 4c8d6c61b1..fc697653c1 100644 --- a/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx +++ b/packages/webui/src/client/ui/SegmentStoryboard/SegmentStoryboard.tsx @@ -1,8 +1,7 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' -import { NoteSeverity } from '@sofie-automation/blueprints-integration' -import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' -import { IContextMenuContext } from '../RundownView.js' -import { IOutputLayerUi, PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' +import type { NoteSeverity } from '@sofie-automation/blueprints-integration' +import type { IContextMenuContext } from '../RundownView.js' +import type { IOutputLayerUi, PartUi, SegmentUi } from '../SegmentContainer/withResolvedSegment.js' import { ContextMenuTrigger } from '@jstarpl/react-contextmenu' import { SegmentDuration } from '../RundownView/RundownTiming/SegmentDuration.js' import { PartCountdown } from '../RundownView/RundownTiming/PartCountdown.js' @@ -45,6 +44,9 @@ import { isEntirePlaylistLooping as getIsEntirePlaylistLooping, } from '@sofie-automation/corelib/src/playout/stateCacheResolver.js' import { SegmentHeaderNotes } from '../SegmentHeader/SegmentHeaderNotes.js' +import type { RundownViewPlaylist } from '../../lib/rundownPlaylistProjection.js' +import { HOVER_TIMEOUT } from '../Shelf/DashboardPieceButton/types.js' +import { RundownHoldState } from '@sofie-automation/corelib/src/dataModel/RundownPlaylist/RundownPlaylist.js' interface IProps { id: string diff --git a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx index a74457a0a5..df4c816943 100644 --- a/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx +++ b/packages/webui/src/client/ui/SegmentTimeline/SegmentTimeline.tsx @@ -4,7 +4,6 @@ import { type WithTranslation, withTranslation } from 'react-i18next' import ClassNames from 'classnames' import { ContextMenuTrigger } from '@jstarpl/react-contextmenu' -import { RundownHoldState } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/RundownPlaylist' import type { RundownViewPlaylist } from '../../lib/rundownPlaylistProjection.js' import type { SegmentUi, PartUi, IOutputLayerUi } from './SegmentTimelineContainer.js' import { TimelineGrid } from './TimelineGrid.js' @@ -39,14 +38,13 @@ import { DEFAULT_DISPLAY_DURATION } from '@sofie-automation/shared-lib/dist/core import { type IOutputLayer, type ISourceLayer, - NoteSeverity, + type NoteSeverity, UserEditingType, } from '@sofie-automation/blueprints-integration' import { SegmentTimelineZoomButtons } from './SegmentTimelineZoomButtons.js' import { SegmentViewMode } from '../SegmentContainer/SegmentViewModes.js' import { SwitchViewModeButton } from '../SegmentContainer/SwitchViewModeButton.js' -import { PartId, PartInstanceId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' -import { RundownHoldState } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import type { PartId, PartInstanceId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { withTiming, TimingTickResolution, @@ -62,6 +60,7 @@ import type { UIStudio } from '@sofie-automation/corelib/src/dataModel/Studio.js import type { PieceUi } from '@sofie-automation/corelib/src/dataModel/Piece.js' import { isLoopRunning, wrapPartToTemporaryInstance } from '@sofie-automation/corelib/src/playout/stateCacheResolver.js' import { SegmentHeaderNotes } from '../SegmentHeader/SegmentHeaderNotes.js' +import { RundownHoldState } from '@sofie-automation/corelib/src/dataModel/RundownPlaylist/RundownPlaylist.js' interface IProps { id: string