Skip to content

Commit

Permalink
feat(FE): add output preview on top of Editor
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarpl committed Jan 24, 2024
1 parent 242616b commit cb2d5df
Show file tree
Hide file tree
Showing 30 changed files with 303 additions and 72 deletions.
12 changes: 3 additions & 9 deletions packages/apps/backend/src/api-server/services/ViewPortService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,13 @@ export class ViewPortService extends EventEmitter<Definition.Events> implements
public async find(_params?: Params & { paginate?: PaginationParams }): Promise<Data[]> {
return [this.store.viewPort.viewPort]
}
public async get(id: Id, _params?: Params): Promise<Data> {
public async get(_id: null, _params?: Params): Promise<Data> {
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<Result> {
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<Result> {
this.store.viewPort.update(data)
return this.get(data._id)
return this.get(null)
}

public async subscribeToViewPort(_: unknown, params: Params): Promise<void> {
Expand All @@ -81,6 +77,4 @@ export class ViewPortService extends EventEmitter<Definition.Events> implements
}

type Result = Definition.Result
type Id = Definition.Id
type NullId = Definition.NullId
type Data = Definition.Data
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class OutputSettingsStore {
// _id: '',

// TODO: load these from persistent store upon startup?
fontSize: 7,
fontSize: 4,

mirrorHorizontally: false,
mirrorVertically: false,
Expand Down
2 changes: 1 addition & 1 deletion packages/apps/backend/src/data-stores/ViewPortStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class ViewPortStore {
},
timestamp: getCurrentTime(),
},
width: 0,
aspectRatio: 1,
})

constructor() {
Expand Down
1 change: 1 addition & 0 deletions packages/apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 5 additions & 2 deletions packages/apps/client/src/PrompterStyles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -82,6 +81,10 @@
line-height: 1.4;
}

.Prompter .spacer {
height: 100vh;
}

.Prompter .ProseMirror {
outline: none;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.RundownOutput {
overflow: hidden;
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className={classes.RundownOutput}>
<h1>{rundown.name}</h1>
{rundown.segmentsInOrder.map((segment) => (
<Segment key={segment.id} segment={segment} />
))}
<div className="spacer" />
</div>
)
})
RundownOutput.displayName = 'RundownOutput'
Original file line number Diff line number Diff line change
@@ -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;
}
36 changes: 6 additions & 30 deletions packages/apps/client/src/components/ScriptEditor/ScriptEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<number | null>(null)

const rootEl = useRef<HTMLDivElement>(null)

Expand All @@ -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(
() =>
({
Expand All @@ -64,6 +39,7 @@ export const ScriptEditor = observer(function ScriptEditor(): React.JSX.Element
return (
<div className={`bg-black Prompter ${classes.ScriptEditor}`} style={style} ref={rootEl}>
<Editor />
<div className="spacer" />
</div>
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
gap: 2px;
}

.Pane {
position: relative;
overflow: auto;
}

.PaneA {
composes: Pane;
grid-column: PaneA;
overflow: auto;
}

.Divider {
Expand All @@ -26,6 +31,6 @@
}

.PaneB {
composes: Pane;
grid-column: PaneB;
overflow: auto;
}
8 changes: 6 additions & 2 deletions packages/apps/client/src/components/SplitPanel/SplitPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ export function SplitPanel({
childrenBegin,
childrenEnd,
className,
classNameBegin,
classNameEnd,
}: {
className?: string
position?: number
onChange?: ChangeEventHandler
childrenBegin: ReactNode
childrenEnd: ReactNode
children?: null
classNameBegin?: string
classNameEnd?: string
}) {
const [isResizing, setIsResizing] = useState(false)
const beginCoords = useRef<{ x: number; y: number } | null>(null)
Expand Down Expand Up @@ -103,13 +107,13 @@ export function SplitPanel({

return (
<div className={`${className ?? ''} ${classes.SplitPane}`} style={style} ref={container}>
<div className={classes.PaneA}>{childrenBegin}</div>
<div className={`${classes.PaneA} ${classNameBegin ?? ''}`}>{childrenBegin}</div>
<div
className={isResizing ? classes.DividerActive : classes.Divider}
ref={divider}
onMouseDown={onMouseDown}
></div>
<div className={classes.PaneB}>{childrenEnd}</div>
<div className={`${classes.PaneB} ${classNameEnd ?? ''}`}>{childrenEnd}</div>
</div>
)
}
Expand Down
16 changes: 16 additions & 0 deletions packages/apps/client/src/lib/useSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useLayoutEffect, useState } from 'react'
import useResizeObserver from '@react-hook/resize-observer'

export function useSize(target: React.RefObject<HTMLElement>): DOMRect | undefined {
const [size, setSize] = useState<DOMRect>()

useLayoutEffect(() => {
if (!target.current) return

setSize(target.current.getBoundingClientRect())
}, [target])

// Where the magic happens
useResizeObserver(target, (entry) => setSize(entry.contentRect))
return size
}
9 changes: 4 additions & 5 deletions packages/apps/client/src/stores/OutputSettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
3 changes: 3 additions & 0 deletions packages/apps/client/src/stores/RootAppStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -28,6 +29,7 @@ class RootAppStoreClass {
rundownStore: RundownStore
systemStatusStore: SystemStatusStore
outputSettingsStore: OutputSettingsStore
viewportStore: ViewPortStore
uiStore: UIStore

constructor() {
Expand All @@ -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)
Expand Down
70 changes: 70 additions & 0 deletions packages/apps/client/src/stores/ViewportStateStore.ts
Original file line number Diff line number Diff line change
@@ -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<ViewPort>({
_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())
}
}
1 change: 1 addition & 0 deletions packages/apps/client/src/views/Output/Output.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
width: 100vw;
height: 100vh;
overflow: auto;
scrollbar-width: none;
}
Loading

0 comments on commit cb2d5df

Please sign in to comment.