Skip to content

Commit

Permalink
feat: add RundownScript view, UIStore
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarpl committed Nov 28, 2023
1 parent e8641ed commit 0057b27
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 38 deletions.
13 changes: 6 additions & 7 deletions packages/apps/client/src/RundownList/RundownEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import React from 'react'
import { observer } from 'mobx-react-lite'
import { AppStore } from '../stores/AppStore'
import { UIRundownId } from '../model/UIRundown'
import { action } from 'mobx'
import { Button } from 'react-bootstrap'
import { useNavigate } from 'react-router-dom'

const RundownEntry = observer(({ rundownId }: { rundownId: UIRundownId }): React.JSX.Element => {
export const RundownEntry = observer(({ rundownId }: { rundownId: UIRundownId }): React.JSX.Element => {
const rundownEntry = AppStore.rundownStore.allRundowns.get(rundownId)
const navigate = useNavigate()

const onOpen = action(() => {
const onOpen = () => {
if (!rundownEntry) return
AppStore.rundownStore.loadRundown(rundownEntry.playlistId)
})
navigate(`/rundown/${rundownEntry.playlistId}`)
}

return (
<p>
Expand All @@ -20,5 +21,3 @@ const RundownEntry = observer(({ rundownId }: { rundownId: UIRundownId }): React
)
})
RundownEntry.displayName = 'RundownEntry'

export { RundownEntry }
4 changes: 1 addition & 3 deletions packages/apps/client/src/RundownList/RundownList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AppStore } from '../stores/AppStore'
import { UIRundownId } from '../model/UIRundown'
import { RundownEntry } from './RundownEntry'

const RundownList = observer((): React.JSX.Element => {
export const RundownList = observer((): React.JSX.Element => {
const allRundownIds = keys<UIRundownId>(AppStore.rundownStore.allRundowns)

return (
Expand All @@ -21,5 +21,3 @@ const RundownList = observer((): React.JSX.Element => {
)
})
RundownList.displayName = 'RundownList'

export { RundownList }
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.RundownScript {
height: 100vh;
}
39 changes: 39 additions & 0 deletions packages/apps/client/src/RundownScript/RundownScript.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useEffect } from 'react'
import { observer } from 'mobx-react-lite'
import classes from './RundownScript.module.scss'
import { CurrentRundown } from '../CurrentRundown/CurrentRundown'
import { ScriptEditor } from '../ScriptEditor/ScriptEditor'
import { Helmet } from 'react-helmet-async'
import { RundownPlaylistId, protectString } from '@sofie-prompter-editor/shared-model'
import { AppStore } from '../stores/AppStore'
import { useParams } from 'react-router-dom'
import { SplitPanel } from '../components/SplitPanel/SplitPanel'

export const RundownScript = observer((): React.JSX.Element => {
const params = useParams()

const playlistId = protectString<RundownPlaylistId>(params.playlistId)

useEffect(() => {
if (!playlistId) return

AppStore.rundownStore.loadRundown(playlistId)
}, [playlistId])

return (
<>
<Helmet>
<title>Rundown</title>
<body data-bs-theme="dark" />
</Helmet>
<SplitPanel
position={AppStore.uiStore.viewDividerPosition}
onChange={(e) => AppStore.uiStore.setViewDividerPosition(e.value)}
className={classes.RundownScript}
childrenBegin={<CurrentRundown />}
childrenEnd={<ScriptEditor />}
/>
</>
)
})
RundownScript.displayName = 'RundownScript'
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.SplitPane {
--divider-width: 5px;
display: grid;
grid-template-rows: auto;
grid-template-columns: [PaneA] var(--position-a, 1fr) [Divider] var(--divider-width) [PaneB] var(--position-b, 1fr);
gap: 2px;
}

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

.Divider {
grid-column: Divider;
--test: 1;
cursor: ew-resize;
}

.Divider:hover,
.DividerActive {
background-color: var(--bs-primary);
}

.PaneB {
grid-column: PaneB;
overflow: auto;
}
96 changes: 96 additions & 0 deletions packages/apps/client/src/components/SplitPanel/SplitPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'
import classes from './SplitPanel.module.css'

export function SplitPanel({
position,
onChange,
childrenBegin,
childrenEnd,
className,
}: {
className?: string
position?: number
onChange?: ChangeEventHandler
childrenBegin: ReactNode
childrenEnd: ReactNode
children?: null
}) {
const [isResizing, setIsResizing] = useState(false)
const beginCoords = useRef<{ x: number; y: number } | null>(null)
const initialPos = useRef<number>(position ?? 0.5)
const container = useRef<HTMLDivElement>(null)
const contRect = useRef<DOMRect | null>(null)

const defaultedPosition = position ?? 0.5

function onMouseDown(e: React.MouseEvent) {
setIsResizing(true)
beginCoords.current = { x: e.clientX, y: e.clientY }
contRect.current = container.current?.getBoundingClientRect() ?? null
}

const style = useMemo<React.CSSProperties>(() => {
const fract = Math.max(0, Math.min(1, defaultedPosition))
return {
'--position-a': `${fract}fr`,
'--position-b': `${1 - fract}fr`,
} as React.CSSProperties
}, [defaultedPosition])

useEffect(() => {
if (!isResizing) return

function onMouseMove(e: MouseEvent) {
if (!beginCoords.current || !contRect.current) return

const diffX = (e.clientX - beginCoords.current.x) / contRect.current.width
const diffY = (e.clientY - beginCoords.current.y) / contRect.current.height

const newValue = Math.max(0, Math.min(1, initialPos.current + diffX))

onChange?.({
value: newValue,
})

e.preventDefault()
}

function onMouseUp(e: MouseEvent) {
setIsResizing(false)
}

window.addEventListener('mousemove', onMouseMove, {
capture: true,
})
window.addEventListener('mouseup', onMouseUp, {
once: true,
capture: true,
})

return () => {
window.removeEventListener('mousemove', onMouseMove, {
capture: true,
})
window.removeEventListener('mouseup', onMouseUp, {
capture: true,
})
}
}, [isResizing, onChange])

useEffect(() => {
if (isResizing) return

initialPos.current = defaultedPosition
}, [isResizing, defaultedPosition])

return (
<div className={`${className ?? ''} ${classes.SplitPane}`} style={style} ref={container}>
<div className={classes.PaneA}>{childrenBegin}</div>
<div className={isResizing ? classes.DividerActive : classes.Divider} onMouseDown={onMouseDown}></div>
<div className={classes.PaneB}>{childrenEnd}</div>
</div>
)
}

type ChangeEvent = { value: number }
type ChangeEventHandler = (e: ChangeEvent) => void
4 changes: 3 additions & 1 deletion packages/apps/client/src/index.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* The following line can be included in a src/App.scss */
$primary: #1769ff;
$dark: #252627;
//$dark: #252627; // this is Origo's original dark color
$dark: #1d1d1d;
$body-bg-dark: $dark;

$font-family-sans-serif: Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
Expand Down
5 changes: 5 additions & 0 deletions packages/apps/client/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import { MobXPlayground } from './MobXPlayground/MobXPlayground.tsx'
import { BackendPlayground } from './BackendPlayground/BackendPlayground.tsx'
import { ScriptEditor } from './ScriptEditor/ScriptEditor.tsx'
import { HelmetProvider } from 'react-helmet-async'
import { RundownScript } from './RundownScript/RundownScript.tsx'

const router = createBrowserRouter([
{
path: '/rundown/:playlistId',
element: <RundownScript />,
},
{
path: '/',
element: <App />,
Expand Down
3 changes: 3 additions & 0 deletions packages/apps/client/src/stores/AppStore.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { makeAutoObservable, action } from 'mobx'
import { RundownStore } from './RundownStore'
import { MockConnection } from '../mocks/mockConnection'
import { UIStore } from './UIStore'

class AppStoreClass {
connected = false
rundownStore: RundownStore
uiStore: UIStore
connection = new MockConnection()

constructor() {
makeAutoObservable(this)
this.rundownStore = new RundownStore(this, this.connection)
this.uiStore = new UIStore()

this.connection.on(
'connected',
Expand Down
47 changes: 20 additions & 27 deletions packages/apps/client/src/stores/RundownStore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeAutoObservable, observable, action } from 'mobx'
import { makeAutoObservable, observable, action, flow } from 'mobx'
import { RundownPlaylistId } from '@sofie-prompter-editor/shared-model'
import { APIConnection, AppStore } from './AppStore'
import { UIRundown, UIRundownId } from '../model/UIRundown'
Expand All @@ -22,43 +22,36 @@ export class RundownStore {
this.loadAllRudnowns()
}

loadAllRudnowns() {
this.connection.playlist.find().then(
action('receiveRundowns', (playlists) => {
// add UIRundownEntries to allRundowns
loadAllRudnowns = flow(function* (this: RundownStore) {
const playlists = yield this.connection.playlist.find()
// add UIRundownEntries to allRundowns

this.clearAllRundowns()
this.clearAllRundowns()

for (const playlist of playlists) {
const newRundownEntry = new UIRundownEntry(this, playlist._id)
this.allRundowns.set(newRundownEntry.id, newRundownEntry)
newRundownEntry.updateFromJson(playlist)
}
})
)
}
for (const playlist of playlists) {
const newRundownEntry = new UIRundownEntry(this, playlist._id)
this.allRundowns.set(newRundownEntry.id, newRundownEntry)
newRundownEntry.updateFromJson(playlist)
}
})

clearAllRundowns() {
for (const rundown of this.allRundowns.values()) {
rundown.remove()
}
}

loadRundown(id: RundownPlaylistId) {
loadRundown = flow(function* (this: RundownStore, id: RundownPlaylistId) {
this.openRundown?.close()
// get a full rundown from backend and create a UIRundown object
// assign to openRundown
this.connection.playlist.get(id).then(
action('receiveRundown', (playlist) => {
if (!playlist) {
console.error('Playlist not found!')
return
}
const playlist = yield this.connection.playlist.get(id)
if (!playlist) {
throw new Error('Playlist not found')
}

const newRundown = new UIRundown(this, playlist._id)
newRundown.updateFromJson(playlist)
this.openRundown = newRundown
})
)
}
const newRundown = new UIRundown(this, playlist._id)
newRundown.updateFromJson(playlist)
this.openRundown = newRundown
})
}
13 changes: 13 additions & 0 deletions packages/apps/client/src/stores/UIStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { makeAutoObservable } from 'mobx'

export class UIStore {
viewDividerPosition: number = 0.5

constructor() {
makeAutoObservable(this, {})
}

setViewDividerPosition(value: number) {
this.viewDividerPosition = value
}
}

0 comments on commit 0057b27

Please sign in to comment.