From cb2d5dfaf816d71d954aa4914efc5f2a9f2ce052 Mon Sep 17 00:00:00 2001 From: Jan Starzak Date: Wed, 24 Jan 2024 16:37:43 +0100 Subject: [PATCH] feat(FE): add output preview on top of Editor --- .../api-server/services/ViewPortService.ts | 12 +--- .../src/data-stores/OutputSettingsStore.ts | 2 +- .../backend/src/data-stores/ViewPortStore.ts | 2 +- packages/apps/client/package.json | 1 + packages/apps/client/src/PrompterStyles.css | 7 +- .../RundownOutput}/Line.tsx | 0 .../RundownOutput/RundownOutput.module.scss | 3 + .../RundownOutput/RundownOutput.tsx | 18 +++++ .../RundownOutput}/Segment.tsx | 0 .../ScriptEditor/ScriptEditor.module.scss | 9 ++- .../components/ScriptEditor/ScriptEditor.tsx | 36 ++-------- .../SplitPanel/SplitPanel.module.css | 9 ++- .../src/components/SplitPanel/SplitPanel.tsx | 8 ++- packages/apps/client/src/lib/useSize.ts | 16 +++++ .../client/src/stores/OutputSettingsStore.ts | 9 ++- .../apps/client/src/stores/RootAppStore.ts | 3 + .../client/src/stores/ViewportStateStore.ts | 70 +++++++++++++++++++ .../src/views/Output/Output.module.scss | 1 + .../apps/client/src/views/Output/Output.tsx | 17 ++--- .../EditorAndPreviewPanel.module.scss | 18 +++++ .../RundownScript/EditorAndPreviewPanel.tsx | 18 +++++ .../RundownScript/PreviewPanel.module.scss | 6 ++ .../src/views/RundownScript/PreviewPanel.tsx | 40 +++++++++++ .../RundownScript/RundownScript.module.scss | 4 ++ .../src/views/RundownScript/RundownScript.tsx | 7 +- .../src/views/TestController/TestUtil.tsx | 7 ++ .../src/client-server-api/ViewPortService.ts | 6 +- .../shared/model/src/model/OutputSettings.ts | 1 + packages/shared/model/src/model/ViewPort.ts | 6 +- yarn.lock | 39 +++++++++++ 30 files changed, 303 insertions(+), 72 deletions(-) rename packages/apps/client/src/{views/Output => components/RundownOutput}/Line.tsx (100%) create mode 100644 packages/apps/client/src/components/RundownOutput/RundownOutput.module.scss create mode 100644 packages/apps/client/src/components/RundownOutput/RundownOutput.tsx rename packages/apps/client/src/{views/Output => components/RundownOutput}/Segment.tsx (100%) create mode 100644 packages/apps/client/src/lib/useSize.ts create mode 100644 packages/apps/client/src/stores/ViewportStateStore.ts create mode 100644 packages/apps/client/src/views/RundownScript/EditorAndPreviewPanel.module.scss create mode 100644 packages/apps/client/src/views/RundownScript/EditorAndPreviewPanel.tsx create mode 100644 packages/apps/client/src/views/RundownScript/PreviewPanel.module.scss create mode 100644 packages/apps/client/src/views/RundownScript/PreviewPanel.tsx diff --git a/packages/apps/backend/src/api-server/services/ViewPortService.ts b/packages/apps/backend/src/api-server/services/ViewPortService.ts index d6c5adf..64d05e0 100644 --- a/packages/apps/backend/src/api-server/services/ViewPortService.ts +++ b/packages/apps/backend/src/api-server/services/ViewPortService.ts @@ -61,17 +61,13 @@ export class ViewPortService extends EventEmitter implements public async find(_params?: Params & { paginate?: PaginationParams }): Promise { return [this.store.viewPort.viewPort] } - public async get(id: Id, _params?: Params): Promise { + public async get(_id: null, _params?: Params): Promise { const data = this.store.viewPort.viewPort - if (!data) throw new NotFound(`ViewPort "${id}" not found`) return data } - public async update(id: NullId, data: Data, _params?: Params): Promise { - if (id === null) throw new BadRequest(`id must not be null`) - if (id !== data._id) throw new BadRequest(`Cannot change id of ViewPort`) - + public async update(_id: null, data: Data, _params?: Params): Promise { this.store.viewPort.update(data) - return this.get(data._id) + return this.get(null) } public async subscribeToViewPort(_: unknown, params: Params): Promise { @@ -81,6 +77,4 @@ export class ViewPortService extends EventEmitter implements } type Result = Definition.Result -type Id = Definition.Id -type NullId = Definition.NullId type Data = Definition.Data diff --git a/packages/apps/backend/src/data-stores/OutputSettingsStore.ts b/packages/apps/backend/src/data-stores/OutputSettingsStore.ts index a760606..ead9289 100644 --- a/packages/apps/backend/src/data-stores/OutputSettingsStore.ts +++ b/packages/apps/backend/src/data-stores/OutputSettingsStore.ts @@ -7,7 +7,7 @@ export class OutputSettingsStore { // _id: '', // TODO: load these from persistent store upon startup? - fontSize: 7, + fontSize: 4, mirrorHorizontally: false, mirrorVertically: false, diff --git a/packages/apps/backend/src/data-stores/ViewPortStore.ts b/packages/apps/backend/src/data-stores/ViewPortStore.ts index 327b36e..509e8df 100644 --- a/packages/apps/backend/src/data-stores/ViewPortStore.ts +++ b/packages/apps/backend/src/data-stores/ViewPortStore.ts @@ -18,7 +18,7 @@ export class ViewPortStore { }, timestamp: getCurrentTime(), }, - width: 0, + aspectRatio: 1, }) constructor() { diff --git a/packages/apps/client/package.json b/packages/apps/client/package.json index 8b0272e..321284b 100644 --- a/packages/apps/client/package.json +++ b/packages/apps/client/package.json @@ -35,6 +35,7 @@ }, "dependencies": { "@popperjs/core": "^2.11.8", + "@react-hook/resize-observer": "^1.2.6", "@sofie-prompter-editor/shared-lib": "0.0.0", "@sofie-prompter-editor/shared-model": "0.0.0", "bootstrap": "^5.3.2", diff --git a/packages/apps/client/src/PrompterStyles.css b/packages/apps/client/src/PrompterStyles.css index bcda6f8..a4d12b4 100644 --- a/packages/apps/client/src/PrompterStyles.css +++ b/packages/apps/client/src/PrompterStyles.css @@ -13,8 +13,7 @@ white-space: pre-wrap; white-space: break-spaces; - padding: 0 2em 100vh; - height: 100%; + padding: 0 2em; overflow: auto; box-sizing: border-box; @@ -82,6 +81,10 @@ line-height: 1.4; } +.Prompter .spacer { + height: 100vh; +} + .Prompter .ProseMirror { outline: none; } diff --git a/packages/apps/client/src/views/Output/Line.tsx b/packages/apps/client/src/components/RundownOutput/Line.tsx similarity index 100% rename from packages/apps/client/src/views/Output/Line.tsx rename to packages/apps/client/src/components/RundownOutput/Line.tsx diff --git a/packages/apps/client/src/components/RundownOutput/RundownOutput.module.scss b/packages/apps/client/src/components/RundownOutput/RundownOutput.module.scss new file mode 100644 index 0000000..18c5d86 --- /dev/null +++ b/packages/apps/client/src/components/RundownOutput/RundownOutput.module.scss @@ -0,0 +1,3 @@ +.RundownOutput { + overflow: hidden; +} diff --git a/packages/apps/client/src/components/RundownOutput/RundownOutput.tsx b/packages/apps/client/src/components/RundownOutput/RundownOutput.tsx new file mode 100644 index 0000000..e732829 --- /dev/null +++ b/packages/apps/client/src/components/RundownOutput/RundownOutput.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { observer } from 'mobx-react-lite' +import { UIRundown } from 'src/model/UIRundown' +import classes from './RundownOutput.module.scss' +import { Segment } from './Segment' + +export const RundownOutput = observer(function RundownOutput({ rundown }: { rundown: UIRundown }): React.ReactNode { + return ( +
+

{rundown.name}

+ {rundown.segmentsInOrder.map((segment) => ( + + ))} +
+
+ ) +}) +RundownOutput.displayName = 'RundownOutput' diff --git a/packages/apps/client/src/views/Output/Segment.tsx b/packages/apps/client/src/components/RundownOutput/Segment.tsx similarity index 100% rename from packages/apps/client/src/views/Output/Segment.tsx rename to packages/apps/client/src/components/RundownOutput/Segment.tsx diff --git a/packages/apps/client/src/components/ScriptEditor/ScriptEditor.module.scss b/packages/apps/client/src/components/ScriptEditor/ScriptEditor.module.scss index 594ca0e..9b068fd 100644 --- a/packages/apps/client/src/components/ScriptEditor/ScriptEditor.module.scss +++ b/packages/apps/client/src/components/ScriptEditor/ScriptEditor.module.scss @@ -1,3 +1,10 @@ .ScriptEditor { - container-type: inline-size; + overflow: auto; + scrollbar-width: none; + min-height: 0; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; } diff --git a/packages/apps/client/src/components/ScriptEditor/ScriptEditor.tsx b/packages/apps/client/src/components/ScriptEditor/ScriptEditor.tsx index f29f76a..a210047 100644 --- a/packages/apps/client/src/components/ScriptEditor/ScriptEditor.tsx +++ b/packages/apps/client/src/components/ScriptEditor/ScriptEditor.tsx @@ -6,10 +6,10 @@ import classes from './ScriptEditor.module.scss' import { observer } from 'mobx-react-lite' import { RootAppStore } from 'src/stores/RootAppStore' import { useDebouncedCallback } from 'src/lib/lib' +import { useSize } from 'src/lib/useSize' export const ScriptEditor = observer(function ScriptEditor(): React.JSX.Element { const [fontSize, setFontSize] = useState(10) - const [width, setWidth] = useState(null) const rootEl = useRef(null) @@ -19,40 +19,15 @@ export const ScriptEditor = observer(function ScriptEditor(): React.JSX.Element const viewportFontSize = RootAppStore.outputSettingsStore.outputSettings.fontSize + const size = useSize(rootEl) + const width = size?.width + useEffect(() => { - if (width === null) return + if (width === undefined) return setFontSize((width * viewportFontSize) / 100) }, [viewportFontSize, width]) - const dividerSplitPosition = RootAppStore.uiStore.viewDividerPosition - - const onResize = useDebouncedCallback( - function onResize() { - if (!rootEl.current) return - const { width: elementWidth } = rootEl.current.getBoundingClientRect() - - setWidth(elementWidth) - }, - [], - { - delay: 100, - } - ) - - useLayoutEffect(() => { - void dividerSplitPosition - onResize() - }, [dividerSplitPosition, onResize]) - - useEffect(() => { - window.addEventListener('resize', onResize) - - return () => { - window.removeEventListener('resize', onResize) - } - }, [onResize]) - const style = useMemo( () => ({ @@ -64,6 +39,7 @@ export const ScriptEditor = observer(function ScriptEditor(): React.JSX.Element return (
+
) }) diff --git a/packages/apps/client/src/components/SplitPanel/SplitPanel.module.css b/packages/apps/client/src/components/SplitPanel/SplitPanel.module.css index 5f82efc..ce9d1a2 100644 --- a/packages/apps/client/src/components/SplitPanel/SplitPanel.module.css +++ b/packages/apps/client/src/components/SplitPanel/SplitPanel.module.css @@ -6,9 +6,14 @@ gap: 2px; } +.Pane { + position: relative; + overflow: auto; +} + .PaneA { + composes: Pane; grid-column: PaneA; - overflow: auto; } .Divider { @@ -26,6 +31,6 @@ } .PaneB { + composes: Pane; grid-column: PaneB; - overflow: auto; } diff --git a/packages/apps/client/src/components/SplitPanel/SplitPanel.tsx b/packages/apps/client/src/components/SplitPanel/SplitPanel.tsx index 022f6d3..8ce9ec0 100644 --- a/packages/apps/client/src/components/SplitPanel/SplitPanel.tsx +++ b/packages/apps/client/src/components/SplitPanel/SplitPanel.tsx @@ -7,6 +7,8 @@ export function SplitPanel({ childrenBegin, childrenEnd, className, + classNameBegin, + classNameEnd, }: { className?: string position?: number @@ -14,6 +16,8 @@ export function SplitPanel({ childrenBegin: ReactNode childrenEnd: ReactNode children?: null + classNameBegin?: string + classNameEnd?: string }) { const [isResizing, setIsResizing] = useState(false) const beginCoords = useRef<{ x: number; y: number } | null>(null) @@ -103,13 +107,13 @@ export function SplitPanel({ return (
-
{childrenBegin}
+
{childrenBegin}
-
{childrenEnd}
+
{childrenEnd}
) } diff --git a/packages/apps/client/src/lib/useSize.ts b/packages/apps/client/src/lib/useSize.ts new file mode 100644 index 0000000..96047a1 --- /dev/null +++ b/packages/apps/client/src/lib/useSize.ts @@ -0,0 +1,16 @@ +import { useLayoutEffect, useState } from 'react' +import useResizeObserver from '@react-hook/resize-observer' + +export function useSize(target: React.RefObject): DOMRect | undefined { + const [size, setSize] = useState() + + useLayoutEffect(() => { + if (!target.current) return + + setSize(target.current.getBoundingClientRect()) + }, [target]) + + // Where the magic happens + useResizeObserver(target, (entry) => setSize(entry.contentRect)) + return size +} diff --git a/packages/apps/client/src/stores/OutputSettingsStore.ts b/packages/apps/client/src/stores/OutputSettingsStore.ts index 540433b..925c16b 100644 --- a/packages/apps/client/src/stores/OutputSettingsStore.ts +++ b/packages/apps/client/src/stores/OutputSettingsStore.ts @@ -30,12 +30,11 @@ export class OutputSettingsStore { } public initialize() { - if (!this.initializing && !this.initialized) { - this.initializing = true + if (this.initializing || this.initialized) return + this.initializing = true - this.setupSubscription() - this.loadInitialData() - } + this.setupSubscription() + this.loadInitialData() } private setupSubscription = action(() => { diff --git a/packages/apps/client/src/stores/RootAppStore.ts b/packages/apps/client/src/stores/RootAppStore.ts index c3bb9f7..8bd96e5 100644 --- a/packages/apps/client/src/stores/RootAppStore.ts +++ b/packages/apps/client/src/stores/RootAppStore.ts @@ -18,6 +18,7 @@ import { } from '@sofie-prompter-editor/shared-model' import { OutputSettingsStore } from './OutputSettingsStore.ts' import { SystemStatusStore } from './SystemStatusStore.ts' +import { ViewPortStore } from './ViewportStateStore.ts' const USE_MOCK_CONNECTION = false @@ -28,6 +29,7 @@ class RootAppStoreClass { rundownStore: RundownStore systemStatusStore: SystemStatusStore outputSettingsStore: OutputSettingsStore + viewportStore: ViewPortStore uiStore: UIStore constructor() { @@ -42,6 +44,7 @@ class RootAppStoreClass { this.rundownStore = new RundownStore(this, this.connection) this.systemStatusStore = new SystemStatusStore(this, this.connection) this.outputSettingsStore = new OutputSettingsStore(this, this.connection) + this.viewportStore = new ViewPortStore(this, this.connection) this.uiStore = new UIStore() this.connection.on('disconnected', this.onDisconnected) diff --git a/packages/apps/client/src/stores/ViewportStateStore.ts b/packages/apps/client/src/stores/ViewportStateStore.ts new file mode 100644 index 0000000..4fa16a1 --- /dev/null +++ b/packages/apps/client/src/stores/ViewportStateStore.ts @@ -0,0 +1,70 @@ +import { observable, action, flow, makeObservable, IReactionDisposer, reaction } from 'mobx' +import { ViewPort } from '@sofie-prompter-editor/shared-model' +import { APIConnection, RootAppStore } from './RootAppStore.ts' + +export class ViewPortStore { + viewPort = observable.object({ + _id: '', + aspectRatio: 1, + lastKnownState: null, + }) + + initialized = false + private initializing = false + + reactions: IReactionDisposer[] = [] + + constructor(public appStore: typeof RootAppStore, public connection: APIConnection) { + makeObservable(this, { + viewPort: observable, + initialized: observable, + }) + + // Note: we DON'T initialize here, + // instead, when anyone wants to use this store, they should call initialize() first. + } + + public initialize() { + if (this.initializing || this.initialized) return + + this.initializing = true + + this.setupSubscription() + this.loadInitialData() + } + + private setupSubscription = action(() => { + this.reactions.push( + reaction( + () => this.appStore.connected, + async (connected) => { + if (!connected) return + + await this.connection.viewPort.subscribeToViewPort() + }, + { + fireImmediately: true, + } + ) + ) + + this.connection.viewPort.on('updated', this.onUpdatedViewPort) + }) + private loadInitialData = flow(function* (this: ViewPortStore) { + const outputSettings = yield this.connection.viewPort.get(null) + this.onUpdatedViewPort(outputSettings) + + this.initialized = true + }) + + private onUpdatedViewPort = action('onUpdatedViewPort', (newData: ViewPort) => { + for (const [key, value] of Object.entries(newData)) { + // @ts-expect-error hack + this.viewPort[key] = value + } + }) + + destroy = () => { + this.reactions.forEach((dispose) => dispose()) + } +} diff --git a/packages/apps/client/src/views/Output/Output.module.scss b/packages/apps/client/src/views/Output/Output.module.scss index b9a54f9..53d0fc2 100644 --- a/packages/apps/client/src/views/Output/Output.module.scss +++ b/packages/apps/client/src/views/Output/Output.module.scss @@ -5,4 +5,5 @@ width: 100vw; height: 100vh; overflow: auto; + scrollbar-width: none; } diff --git a/packages/apps/client/src/views/Output/Output.tsx b/packages/apps/client/src/views/Output/Output.tsx index da13b4c..0d4bdb1 100644 --- a/packages/apps/client/src/views/Output/Output.tsx +++ b/packages/apps/client/src/views/Output/Output.tsx @@ -3,8 +3,8 @@ import { observer } from 'mobx-react-lite' import { RootAppStore } from 'src/stores/RootAppStore.ts' import 'src/PrompterStyles.css' -import { Segment } from './Segment' import { Helmet } from 'react-helmet-async' +import { RundownOutput } from 'src/components/RundownOutput/RundownOutput' import { getCurrentTime } from 'src/lib/getCurrentTime' import { useQueryParam } from 'src/lib/useQueryParam' @@ -41,9 +41,9 @@ const Output = observer(function Output(): React.ReactElement { const onViewPortSizeChanged = useCallback(() => { if (!isPrimary) return - RootAppStore.connection.viewPort.update('', { + RootAppStore.connection.viewPort.update(null, { _id: '', - width: window.innerWidth / window.innerHeight, + aspectRatio: window.innerWidth / window.innerHeight, // TODO: This should return the actual lastKnownState lastKnownState: { timestamp: getCurrentTime(), @@ -142,9 +142,9 @@ const Output = observer(function Output(): React.ReactElement { */ - const fontSize = RootAppStore.outputSettingsStore.outputSettings.fontSize - const scaleVertical = RootAppStore.outputSettingsStore.outputSettings.mirrorVertically ? '-1' : '1' - const scaleHorizontal = RootAppStore.outputSettingsStore.outputSettings.mirrorHorizontally ? '-1' : '1' + const fontSize = outputSettings.fontSize + const scaleVertical = outputSettings.mirrorVertically ? '-1' : '1' + const scaleHorizontal = outputSettings.mirrorHorizontally ? '-1' : '1' const styleVariables = useMemo( () => @@ -170,10 +170,7 @@ const Output = observer(function Output(): React.ReactElement { <> {GLOBAL_SETTINGS}
-

{rundown.name}

- {rundown.segmentsInOrder.map((segment) => ( - - ))} +
) diff --git a/packages/apps/client/src/views/RundownScript/EditorAndPreviewPanel.module.scss b/packages/apps/client/src/views/RundownScript/EditorAndPreviewPanel.module.scss new file mode 100644 index 0000000..0054cd5 --- /dev/null +++ b/packages/apps/client/src/views/RundownScript/EditorAndPreviewPanel.module.scss @@ -0,0 +1,18 @@ +.EditorAndPreviewPanel { + display: flex; + flex-direction: column; + gap: 10px; +} + +.PreviewArea { + flex-grow: 0; + flex-shrink: 1; + min-height: 0; + position: relative; +} + +.EditorArea { + position: relative; + flex-grow: 1; + flex-shrink: 1; +} diff --git a/packages/apps/client/src/views/RundownScript/EditorAndPreviewPanel.tsx b/packages/apps/client/src/views/RundownScript/EditorAndPreviewPanel.tsx new file mode 100644 index 0000000..81505b9 --- /dev/null +++ b/packages/apps/client/src/views/RundownScript/EditorAndPreviewPanel.tsx @@ -0,0 +1,18 @@ +import React from 'react' + +import classes from './EditorAndPreviewPanel.module.scss' +import { ScriptEditor } from 'src/components/ScriptEditor/ScriptEditor' +import { PreviewPanel } from './PreviewPanel' + +export function EditorAndPreviewPanel(): React.ReactNode { + return ( +
+
+ +
+
+ +
+
+ ) +} diff --git a/packages/apps/client/src/views/RundownScript/PreviewPanel.module.scss b/packages/apps/client/src/views/RundownScript/PreviewPanel.module.scss new file mode 100644 index 0000000..0582440 --- /dev/null +++ b/packages/apps/client/src/views/RundownScript/PreviewPanel.module.scss @@ -0,0 +1,6 @@ +.PreviewPanel { + overflow: auto; + user-select: none; + pointer-events: none; + scrollbar-width: none; +} diff --git a/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx b/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx new file mode 100644 index 0000000..a847af3 --- /dev/null +++ b/packages/apps/client/src/views/RundownScript/PreviewPanel.tsx @@ -0,0 +1,40 @@ +import { observer } from 'mobx-react-lite' +import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' +import { RundownOutput } from 'src/components/RundownOutput/RundownOutput' +import { RootAppStore } from 'src/stores/RootAppStore' + +import classes from './PreviewPanel.module.scss' +import { useSize } from 'src/lib/useSize' + +export const PreviewPanel = observer(function PreviewPanel(): React.ReactNode { + const rootEl = useRef(null) + + useEffect(() => { + RootAppStore.outputSettingsStore.initialize() + RootAppStore.viewportStore.initialize() + }, []) + + const rundown = RootAppStore.rundownStore.openRundown + const outputSettings = RootAppStore.outputSettingsStore.outputSettings + + const viewPortAspectRatio = RootAppStore.viewportStore.viewPort.aspectRatio + + const size = useSize(rootEl) + const previewWidth = size?.width ?? 0 + + const style = useMemo( + () => + ({ + '--prompter-font-size-base': `${(previewWidth * outputSettings.fontSize) / 100}px`, + height: `${previewWidth / viewPortAspectRatio}px`, + } as React.CSSProperties), + [outputSettings.fontSize, previewWidth, viewPortAspectRatio] + ) + + return ( +
+ {rundown && } +
+ ) +}) +PreviewPanel.displayName = 'PreviewPanel' diff --git a/packages/apps/client/src/views/RundownScript/RundownScript.module.scss b/packages/apps/client/src/views/RundownScript/RundownScript.module.scss index aadcd3b..53d79c1 100644 --- a/packages/apps/client/src/views/RundownScript/RundownScript.module.scss +++ b/packages/apps/client/src/views/RundownScript/RundownScript.module.scss @@ -1,3 +1,7 @@ .RundownScript { height: 100vh; } + +.EditorAndPreviewPanel { + display: grid; +} diff --git a/packages/apps/client/src/views/RundownScript/RundownScript.tsx b/packages/apps/client/src/views/RundownScript/RundownScript.tsx index c6cac05..4cec142 100644 --- a/packages/apps/client/src/views/RundownScript/RundownScript.tsx +++ b/packages/apps/client/src/views/RundownScript/RundownScript.tsx @@ -1,13 +1,13 @@ -import { ChangeEvent, useEffect, useRef } from 'react' +import { useEffect } from 'react' import { observer } from 'mobx-react-lite' import classes from './RundownScript.module.scss' import { CurrentRundown } from 'src/components/CurrentRundown/CurrentRundown' -import { ScriptEditor } from 'src/components/ScriptEditor/ScriptEditor' import { Helmet } from 'react-helmet-async' import { RundownPlaylistId, protectString } from '@sofie-prompter-editor/shared-model' import { RootAppStore } from 'src/stores/RootAppStore' import { useParams } from 'react-router-dom' import { SplitPanel } from 'src/components/SplitPanel/SplitPanel' +import { EditorAndPreviewPanel } from './EditorAndPreviewPanel' const RundownScript = observer((): React.JSX.Element => { const params = useParams() @@ -35,7 +35,8 @@ const RundownScript = observer((): React.JSX.Element => { onChange={onChange} className={classes.RundownScript} childrenBegin={} - childrenEnd={} + childrenEnd={} + classNameEnd={classes.EditorAndPreviewPanel} /> ) diff --git a/packages/apps/client/src/views/TestController/TestUtil.tsx b/packages/apps/client/src/views/TestController/TestUtil.tsx index 185cc2f..1dee566 100644 --- a/packages/apps/client/src/views/TestController/TestUtil.tsx +++ b/packages/apps/client/src/views/TestController/TestUtil.tsx @@ -30,6 +30,9 @@ export function useApiConnection( }, [connected, ...(deps || [])]) } +/** + * Use **only** in test UI's, do not use in production code + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export const EditObject: React.FC<{ obj: any; onChange: (value: any) => void }> = observer(({ obj, onChange }) => { const updateProperty = React.useCallback( @@ -68,6 +71,10 @@ export const EditObject: React.FC<{ obj: any; onChange: (value: any) => void }> ) }) + +/** + * Use **only** in test UI's, do not use in production code + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export const EditValue: React.FC<{ value: any; onChange: (value: any) => void }> = ({ value, onChange }) => { const valueType = typeof value diff --git a/packages/shared/model/src/client-server-api/ViewPortService.ts b/packages/shared/model/src/client-server-api/ViewPortService.ts index cf85c21..19c1123 100644 --- a/packages/shared/model/src/client-server-api/ViewPortService.ts +++ b/packages/shared/model/src/client-server-api/ViewPortService.ts @@ -20,11 +20,11 @@ export const ALL_METHODS = [ 'subscribeToViewPort', ] as const /** The methods exposed by this class are exposed in the API */ -interface Methods extends Omit { +interface Methods { find(params?: Params & { paginate?: PaginationParams }): Promise - get(id: Id, params?: Params): Promise + get(id: null, params?: Params): Promise // create(data: Data, params?: Params): Promise - update(id: NullId, data: Data, params?: Params): Promise + update(id: null, data: Data, params?: Params): Promise /** Subscribe to ViewPort data */ subscribeToViewPort(_?: unknown, params?: Params): Promise diff --git a/packages/shared/model/src/model/OutputSettings.ts b/packages/shared/model/src/model/OutputSettings.ts index 584ea42..4ebc8f5 100644 --- a/packages/shared/model/src/model/OutputSettings.ts +++ b/packages/shared/model/src/model/OutputSettings.ts @@ -8,6 +8,7 @@ export type OutputSettings = z.infer export const OutputSettingsSchema = z.object({ // _id: z.literal(''), + /** Percentage points of viewport width */ fontSize: z.number().min(0).max(100), mirrorHorizontally: z.boolean(), diff --git a/packages/shared/model/src/model/ViewPort.ts b/packages/shared/model/src/model/ViewPort.ts index 8b922a1..d761d30 100644 --- a/packages/shared/model/src/model/ViewPort.ts +++ b/packages/shared/model/src/model/ViewPort.ts @@ -17,9 +17,9 @@ export const ViewPortLastKnownStateSchema = z.object({ export const ViewPortSchema = z.object({ _id: z.literal(''), - /** The width of the viewport (as percentage of viewport height (viewportUnits)) */ - width: z.number(), + /** Aspect ratio of the viewport */ + aspectRatio: z.number(), /** Current position of the viewport */ - lastKnownState: ViewPortLastKnownStateSchema, + lastKnownState: ViewPortLastKnownStateSchema.nullable(), }) diff --git a/yarn.lock b/yarn.lock index 43a17b5..e032bef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1179,6 +1179,13 @@ __metadata: languageName: node linkType: hard +"@juggle/resize-observer@npm:^3.3.1": + version: 3.4.0 + resolution: "@juggle/resize-observer@npm:3.4.0" + checksum: 12930242357298c6f2ad5d4ec7cf631dfb344ca7c8c830ab7f64e6ac11eb1aae486901d8d880fd08fb1b257800c160a0da3aee1e7ed9adac0ccbb9b7c5d93347 + languageName: node + linkType: hard + "@koa/cors@npm:^4.0.0": version: 4.0.0 resolution: "@koa/cors@npm:4.0.0" @@ -1694,6 +1701,37 @@ __metadata: languageName: node linkType: hard +"@react-hook/latest@npm:^1.0.2": + version: 1.0.3 + resolution: "@react-hook/latest@npm:1.0.3" + peerDependencies: + react: ">=16.8" + checksum: d6a166c21121da519a516e8089ba28a2779d37b6017732ab55476c0d354754ad215394135765254f8752a7c6661c3fb868d088769a644848602f00f8821248ed + languageName: node + linkType: hard + +"@react-hook/passive-layout-effect@npm:^1.2.0": + version: 1.2.1 + resolution: "@react-hook/passive-layout-effect@npm:1.2.1" + peerDependencies: + react: ">=16.8" + checksum: 5c9e6b3df1c91fc2b1d4f711ca96b5f8cb3f6a13a2e97dac7cce623e58d7ee57999c45db3778d0af0b2522b3a5b7463232ef21cb3ee9900437172d48f766d933 + languageName: node + linkType: hard + +"@react-hook/resize-observer@npm:^1.2.6": + version: 1.2.6 + resolution: "@react-hook/resize-observer@npm:1.2.6" + dependencies: + "@juggle/resize-observer": "npm:^3.3.1" + "@react-hook/latest": "npm:^1.0.2" + "@react-hook/passive-layout-effect": "npm:^1.2.0" + peerDependencies: + react: ">=16.8" + checksum: 6ebe4ded4dc4602906c4c727871f93ea73754dd5758f90d50e5fc7382b1844324a46a4c2e0842d8ad5bf95886091ba8a0c9d3a1ef0f10bd0c9e011ecd7aeea42 + languageName: node + linkType: hard + "@remix-run/router@npm:1.11.0": version: 1.11.0 resolution: "@remix-run/router@npm:1.11.0" @@ -1932,6 +1970,7 @@ __metadata: resolution: "@sofie-prompter-editor/apps-client@workspace:packages/apps/client" dependencies: "@popperjs/core": "npm:^2.11.8" + "@react-hook/resize-observer": "npm:^1.2.6" "@sofie-prompter-editor/shared-lib": "npm:0.0.0" "@sofie-prompter-editor/shared-model": "npm:0.0.0" "@types/bootstrap": "npm:^5"