diff --git a/packages/apps/client/src/components/CurrentRundown/Line.module.scss b/packages/apps/client/src/components/CurrentRundown/Line.module.scss index bc37a83..c81179a 100644 --- a/packages/apps/client/src/components/CurrentRundown/Line.module.scss +++ b/packages/apps/client/src/components/CurrentRundown/Line.module.scss @@ -38,6 +38,7 @@ .LineFlagNew { composes: LineFlag; + color: #ffff00; } .LineFlagCaret { @@ -46,6 +47,7 @@ .LineFlagOutput { composes: LineFlag; + color: #ff0000; } .LineIdentifier { diff --git a/packages/apps/client/src/components/CurrentRundown/Line.tsx b/packages/apps/client/src/components/CurrentRundown/Line.tsx index 4900dd4..ced5599 100644 --- a/packages/apps/client/src/components/CurrentRundown/Line.tsx +++ b/packages/apps/client/src/components/CurrentRundown/Line.tsx @@ -8,12 +8,14 @@ import { removeMarkdownish } from '@sofie-prompter-editor/shared-lib' import classes from './Line.module.scss' import ENPS from '../icons/ENPS' import LetterWithPencil from '../icons/LetterWithPencil' +import { FocusTriangle } from '../icons/FocusTriangle' const Line = observer( ({ segmentName, line, edited, + output, onFocus, selected, onRecall, @@ -22,6 +24,7 @@ const Line = observer( line: UILine | undefined selected: boolean edited: boolean + output: boolean onFocus?: React.FocusEventHandler onRecall?: React.EventHandler }): React.JSX.Element | null => { @@ -54,7 +57,7 @@ const Line = observer( >
{line.isNew ? : null}
{edited ? : null}
-
+
{output ? : null}
{segmentName}
{line.identifier}
diff --git a/packages/apps/client/src/components/CurrentRundown/Segment.tsx b/packages/apps/client/src/components/CurrentRundown/Segment.tsx index 3a29eae..7a97ece 100644 --- a/packages/apps/client/src/components/CurrentRundown/Segment.tsx +++ b/packages/apps/client/src/components/CurrentRundown/Segment.tsx @@ -18,6 +18,10 @@ const Segment = observer(({ segment }: { segment: UISegment }): React.JSX.Elemen return lineId === RootAppStore.rundownStore.openRundown?.editorCaretPositionLineId } + function isOutput(lineId: UILineId) { + return lineId === RootAppStore.rundownStore.openRundown?.outputPositionLineId + } + function onFocus(e: React.FocusEvent) { const lineId = e.currentTarget.dataset['objId'] as UILineId RootAppStore.uiStore.setSelectedLineId(lineId) @@ -51,6 +55,7 @@ const Segment = observer(({ segment }: { segment: UISegment }): React.JSX.Elemen line={line} selected={isSelected(line.id)} edited={isEdited(line.id)} + output={isOutput(line.id)} onFocus={onFocus} onRecall={onRecall} /> diff --git a/packages/apps/client/src/components/icons/FocusTriangle.tsx b/packages/apps/client/src/components/icons/FocusTriangle.tsx new file mode 100644 index 0000000..1940f32 --- /dev/null +++ b/packages/apps/client/src/components/icons/FocusTriangle.tsx @@ -0,0 +1,9 @@ +import classes from './IconComponent.module.scss' + +export function FocusTriangle() { + return ( + + + + ) +} diff --git a/packages/apps/client/src/model/UIRundown.ts b/packages/apps/client/src/model/UIRundown.ts index 9a75642..624a6f4 100644 --- a/packages/apps/client/src/model/UIRundown.ts +++ b/packages/apps/client/src/model/UIRundown.ts @@ -32,6 +32,8 @@ export class UIRundown { editorCaretPositionLineId: UILineId | null = null + outputPositionLineId: UILineId | null = null + reactions: IReactionDisposer[] = [] constructor(private store: RundownStore, public id: UIRundownId) { @@ -151,6 +153,10 @@ export class UIRundown { this.editorCaretPositionLineId = partId }) + updatePartInOutput = action('updatePartInOutput', (partId: PartId | null) => { + this.outputPositionLineId = partId + }) + async updatePartScript(partId: PartId, script: ScriptContents): Promise { await this.store.connection.part.updateScript({ partId, diff --git a/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx b/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx index 4d1010c..15e12ee 100644 --- a/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx +++ b/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx @@ -8,6 +8,10 @@ import { useSize } from 'src/lib/useSize' import { useControllerMessages } from 'src/hooks/useControllerMessages' import { useKeepRundownOutputInPosition } from 'src/hooks/useKeepRundownOutputInPosition' import { combineDisposers } from 'src/lib/lib' +import { getAllAnchorElementsByType, getAnchorAbovePositionIndex } from 'src/lib/anchorElements' +import { PartId, protectString } from '@sofie-prompter-editor/shared-model' + +const PREVIEW_SAMPLE_RATE = 250 export const PreviewPanel = observer(function PreviewPanel(): React.ReactNode { const rootEl = useRef(null) @@ -38,6 +42,8 @@ export const PreviewPanel = observer(function PreviewPanel(): React.ReactNode { const fontSizePx = (previewWidth * fontSize) / 100 + const focusPosition = 0 + const { setBaseViewPortState: setBaseState, scrolledPosition, @@ -46,7 +52,7 @@ export const PreviewPanel = observer(function PreviewPanel(): React.ReactNode { } = useControllerMessages(rootEl, fontSizePx, { enableControl: rundownIsInOutput, }) - useKeepRundownOutputInPosition(rootEl, rundown, fontSizePx, speed, scrolledPosition, position, 0) + useKeepRundownOutputInPosition(rootEl, rundown, fontSizePx, speed, scrolledPosition, position, focusPosition) useEffect(() => { if (!lastKnownState) return @@ -72,6 +78,27 @@ export const PreviewPanel = observer(function PreviewPanel(): React.ReactNode { [fontSize, previewWidth] ) + useEffect(() => { + const interval = setInterval(() => { + if (!rundown) return + if (!rootEl.current) return + const els = Array.from(getAllAnchorElementsByType(rootEl.current, 'line')) + const anchorAbovePositionIndex = getAnchorAbovePositionIndex(focusPosition, els) + + const selectedEl = els[anchorAbovePositionIndex] + if (!selectedEl || !(selectedEl instanceof HTMLElement)) { + rundown.updatePartInOutput(null) + return + } + const uiLineId = protectString(selectedEl.dataset['objId']) ?? null + rundown.updatePartInOutput(uiLineId) + }, PREVIEW_SAMPLE_RATE) + + return () => { + clearInterval(interval) + } + }, [rundown]) + return ( <>