From 76f81d296c5d0b2f00a18adbd793daf6b8e4f199 Mon Sep 17 00:00:00 2001 From: Jan Starzak Date: Tue, 12 Dec 2023 13:40:31 +0100 Subject: [PATCH] feat(Editor): prototype updating paragraphs from the outside --- .../dataHandlers/PartHandler.ts | 2 +- .../apps/client/src/ScriptEditor/Editor.tsx | 82 +++++++++++++++++-- .../apps/client/src/mocks/mockConnection.ts | 44 +++++----- 3 files changed, 99 insertions(+), 29 deletions(-) diff --git a/packages/apps/backend/src/sofie-core-connection/dataHandlers/PartHandler.ts b/packages/apps/backend/src/sofie-core-connection/dataHandlers/PartHandler.ts index c9d3bf9..cd98b2b 100644 --- a/packages/apps/backend/src/sofie-core-connection/dataHandlers/PartHandler.ts +++ b/packages/apps/backend/src/sofie-core-connection/dataHandlers/PartHandler.ts @@ -75,7 +75,7 @@ export class PartHandler extends DataHandler { isNext: false, // TODO display: { label: '', // TODO - type: PartDisplayType.FULL, // TODO + type: PartDisplayType.VT, // TODO }, }, } diff --git a/packages/apps/client/src/ScriptEditor/Editor.tsx b/packages/apps/client/src/ScriptEditor/Editor.tsx index 87d0bbe..e3add54 100644 --- a/packages/apps/client/src/ScriptEditor/Editor.tsx +++ b/packages/apps/client/src/ScriptEditor/Editor.tsx @@ -1,9 +1,11 @@ import React, { useEffect, useRef } from 'react' import { undo, redo, history } from 'prosemirror-history' import { keymap } from 'prosemirror-keymap' -import { EditorState } from 'prosemirror-state' +import { EditorState, TextSelection } from 'prosemirror-state' import { EditorView } from 'prosemirror-view' +import { Fragment, Node, Slice } from 'prosemirror-model' import { baseKeymap } from 'prosemirror-commands' +import { ReplaceStep, replaceStep } from 'prosemirror-transform' import { schema } from './scriptSchema' import 'prosemirror-view/style/prosemirror.css' import { updateModel } from './plugins/updateModel' @@ -20,6 +22,10 @@ const LOREM_IPSUM = 'Duis mollis ut enim vitae lobortis. ~Nulla mi libero~, blandit sit amet congue eu, vehicula vel sem. Donec maximus lacus \\~ac nisi blandit sodales. Fusce sed lectus iaculis, tempus quam lacinia, gravida velit. In imperdiet, sem sit amet commodo eleifend, turpis tellus lobortis metus, et rutrum mi sapien vel nisl. Pellentesque at est non tortor efficitur tincidunt vitae in ex. In gravida pulvinar ligula eget pellentesque. Nullam viverra orci velit, at dictum diam imperdiet sit amet. Morbi consequat est vitae mi consequat fringilla. Phasellus pharetra turpis nulla, at molestie nunc hendrerit ut. \n' + 'Aenean ut nulla ut diam imperdiet laoreet sed sed enim. **Vivamus bibendum** tempus metus ac consectetur. Aliquam ut nisl sed mauris sodales dignissim. Integer consectetur sapien quam, sit amet blandit quam cursus ac. Quisque vel convallis erat. Aliquam ac interdum nisi. Praesent id sapien vitae sem venenatis sollicitudin. ' +const LINE_1_ID = randomId() +const LINE_2_ID = randomId() +const LINE_3_ID = randomId() + export function Editor({ initialValue, className, @@ -30,7 +36,6 @@ export function Editor({ }): React.JSX.Element { const containerEl = useRef(null) const editorView = useRef() - const editorState = useRef() void initialValue @@ -58,7 +63,7 @@ export function Editor({ schema.node( schema.nodes.line, { - lineId: randomId(), + lineId: LINE_1_ID, }, [ schema.node(schema.nodes.lineTitle, undefined, [schema.text('Line title')]), @@ -70,14 +75,14 @@ export function Editor({ schema.node( schema.nodes.line, { - lineId: randomId(), + lineId: LINE_2_ID, }, [schema.node(schema.nodes.lineTitle, undefined, schema.text('Line title')), ...fromMarkdown(LOREM_IPSUM)] ), schema.node( schema.nodes.line, { - lineId: randomId(), + lineId: LINE_3_ID, }, [ schema.node(schema.nodes.lineTitle, undefined, schema.text('Line title')), @@ -105,16 +110,81 @@ export function Editor({ }) editorView.current = view - editorState.current = state return () => { view.destroy() } }, []) + useEffect(() => { + const loop = setInterval(() => { + if (!editorView.current) return + + const editorState = editorView.current.state + + const line = find(editorState.doc, (node) => node.attrs['lineId'] === LINE_2_ID) + + if (!line) return + + const ranges: { offset: number; size: number }[] = [] + + line.node.forEach((node, offset) => { + if (node.type !== schema.nodes.paragraph) return + ranges.push({ offset, size: node.nodeSize }) + }) + ranges.sort((a, b) => a.offset - b.offset) + const beginOffset = line.pos + 1 + ranges[0].offset + const endOffset = line.pos + 1 + ranges[ranges.length - 1].offset + ranges[ranges.length - 1].size + const selectionBookmark = editorState.selection.getBookmark() + const step = replaceStep( + editorState.doc, + beginOffset, + endOffset, + Slice.maxOpen( + Fragment.fromArray([ + schema.node(schema.nodes.paragraph, undefined, schema.text(`Script... ${new Date().toString()}`)), + ]) + ) + ) + if (!step) return + + const tr = editorState.tr.step(step) + + let newState = editorState.apply(tr) + + const restoreSelectionTr = newState.tr.setSelection(selectionBookmark.resolve(newState.doc)) + newState = newState.apply(restoreSelectionTr) + + editorView.current.updateState(newState) + }, 5000) + + return () => { + clearInterval(loop) + } + }, []) + return
} +function find( + node: Node, + predicate: (needle: Node, offset: number, index: number) => boolean +): undefined | NodeWithPosition { + let found: NodeWithPosition | undefined = undefined + node.descendants((maybeNode, pos, _, index) => { + if (found) return + const isOK = predicate(maybeNode, pos, index) + if (isOK) found = { node: maybeNode, pos } + }) + + return found +} + +type NodeWithPosition = { + node: Node + pos: number +} + type OnChangeEvent = { value: string } diff --git a/packages/apps/client/src/mocks/mockConnection.ts b/packages/apps/client/src/mocks/mockConnection.ts index 0a8cb1c..89cb217 100644 --- a/packages/apps/client/src/mocks/mockConnection.ts +++ b/packages/apps/client/src/mocks/mockConnection.ts @@ -160,8 +160,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_0, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'Full', + type: PartDisplayType.VT, }, isOnAir: true, isNext: false, @@ -174,8 +174,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_0, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'KAM', + type: PartDisplayType.Camera, }, isOnAir: true, isNext: false, @@ -188,8 +188,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_0, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'Full', + type: PartDisplayType.VT, }, isOnAir: true, isNext: false, @@ -202,8 +202,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_0, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'KAM', + type: PartDisplayType.Camera, }, isOnAir: true, isNext: false, @@ -216,8 +216,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_1, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'FULL', + type: PartDisplayType.VT, }, isOnAir: true, isNext: false, @@ -230,8 +230,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_1, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'KAM', + type: PartDisplayType.Camera, }, isOnAir: true, isNext: false, @@ -244,8 +244,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_2, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'Kam', + type: PartDisplayType.Camera, }, isOnAir: true, isNext: false, @@ -258,8 +258,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_2, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'FULL', + type: PartDisplayType.VT, }, isOnAir: true, isNext: false, @@ -272,8 +272,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_3, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'Kam', + type: PartDisplayType.Camera, }, isOnAir: true, isNext: false, @@ -286,8 +286,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_3, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'FULL', + type: PartDisplayType.VT, }, isOnAir: true, isNext: false, @@ -300,8 +300,8 @@ export class MockConnection extends EventEmitter { rundownId: RUNDOWN_ID_0_0, segmentId: SEGMENT_ID_0_3, display: { - label: 'VT', - type: PartDisplayType.FULL, + label: 'Kam', + type: PartDisplayType.Camera, }, isOnAir: true, isNext: false,