diff --git a/packages/documentation/docs/for-developers/url-query-parameters.md b/packages/documentation/docs/for-developers/url-query-parameters.md index ec76936777d..b30474946d8 100644 --- a/packages/documentation/docs/for-developers/url-query-parameters.md +++ b/packages/documentation/docs/for-developers/url-query-parameters.md @@ -23,3 +23,4 @@ Appending query parameter(s) to the URL will allow you to modify the behaviour o | `vibrate=1` | Experimental feature that triggers the vibration API in the web browser 3 seconds before each planned _Take_. _Default value is `0`._ | | `zoom=100,...` | Sets the scaling of the entire GUI. _The unit is a percentage where `100` is the default scaling._ **Passing any `zoom` parameter will cause it to be stored in the browser's local storage as `uiZoomLevel` and will then be used for future sessions without notifying the user that they are using the Sofie GUI at a non-standard size!** | | `hideRundownHeader=1` | Hides header on [Rundown view](../user-guide/features/sofie-views-and-screens#rundown-view) and [Active Rundown screen](../user-guide/features/sofie-views-and-screens#active-rundown-screen). _Default value is `0`._ | +| `lockView=1` | Locks the [Active Rundown screen](../user-guide/features/sofie-views-and-screens#active-rundown-screen) for unattended or kiosk use: hides exit controls (header close button and context menu “Close Rundown”), disables the in-app navigation confirmation when a rundown is active, and shows the [Screensaver](../user-guide/features/sofie-views-and-screens#screensaver) when no rundown is active instead of a message with a link back to the lobby. Only applies on `/activeRundown/:studioId` routes (ignored on `/rundown/:playlistId`). _Default value is `0`._ | diff --git a/packages/documentation/docs/user-guide/features/sofie-views-and-screens.mdx b/packages/documentation/docs/user-guide/features/sofie-views-and-screens.mdx index 328d377d0b9..c5efcb82d3a 100644 --- a/packages/documentation/docs/user-guide/features/sofie-views-and-screens.mdx +++ b/packages/documentation/docs/user-guide/features/sofie-views-and-screens.mdx @@ -347,6 +347,20 @@ Example: [http://127.0.0.1/countdowns/studio0/camera?sourceLayerIds=camera0,dve0 A page which automatically displays the currently active rundown. Can be useful for the producer to have on a secondary screen. +When no rundown is active, a message is shown with a link back to the rundown list. + +This screen can be configured using query parameters: + +| Query parameter | Type | Description | Default | +| :-------------- | :---- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | +| `lockView` | 0 / 1 | Locks the view for unattended or kiosk displays. Hides the header close button and the context menu “Close Rundown” action, and disables the in-app navigation confirmation when a rundown is active. When no rundown is active, shows the [Screensaver](#screensaver) instead of the default idle message. Does not block browser back, manual URL changes, or closing the tab. Only applies on Active Rundown routes (not on a specific `/rundown/:playlistId` URL). | `0` | + +Example (locked Active Rundown for a secondary monitor): + +`http://localhost:3000/activeRundown/studio0?lockView=1` + +Other query parameters for the rundown view itself (such as `hideRundownHeader` and layout selection) can be combined with `lockView`. See [URL Query Parameters](../../for-developers/url-query-parameters.md). + ### Active Rundown Shelf Screen `/activeRundown/:studioId/shelf` @@ -357,6 +371,10 @@ A screen which automatically displays the currently active rundown, and shows th A shelf layout can be selected by modifying the query string, see [Shelf Layouts](#shelf-layouts). +The `lockView` parameter described above also applies to this route. Example: + +`http://localhost:3000/activeRundown/studio0/shelf?lockView=1&layout=Stream` + ### Specific Rundown Shelf Screen `/rundown/:rundownId/shelf` @@ -379,7 +397,7 @@ Each embedded screen shows a label to identify it. This screen is mostly intende ### Screensaver -When big screen displays \(like Prompter Screen and the Presenter Screen\) do not have any meaningful content to show, an animated screensaver showing the current time and the next planned show will be displayed. If no Rundown is upcoming, the Studio name will be displayed. +When big screen displays \(like Prompter Screen, the Presenter Screen, and the Active Rundown Screen with `lockView=1`\) do not have any meaningful content to show, an animated screensaver showing the current time and the next planned show will be displayed. If no Rundown is upcoming, the Studio name will be displayed. ![A screensaver showing the next scheduled show](/img/docs/main/features/next-scheduled-show-example.png) diff --git a/packages/webui/src/client/ui/ActiveRundownView.tsx b/packages/webui/src/client/ui/ActiveRundownView.tsx index 11b0f9203c2..5130978115f 100644 --- a/packages/webui/src/client/ui/ActiveRundownView.tsx +++ b/packages/webui/src/client/ui/ActiveRundownView.tsx @@ -1,8 +1,10 @@ -import { NavLink, Route, Switch, useRouteMatch } from 'react-router-dom' +import { NavLink, Route, Switch, useLocation, useRouteMatch } from 'react-router-dom' +import { parse as queryStringParse } from 'query-string' import { useSubscription, useTracker } from '../lib/ReactMeteorData/ReactMeteorData.js' import { Spinner } from '../lib/Spinner.js' import { RundownView } from './RundownView.js' +import { StudioScreenSaver } from './StudioScreenSaver/StudioScreenSaver.js' import { MeteorPubSub } from '@sofie-automation/meteor-lib/dist/api/pubsub' import { UIStudios } from './Collections.js' import type { StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' @@ -14,6 +16,8 @@ export function ActiveRundownView({ studioId }: Readonly<{ studioId: StudioId }> const { t } = useTranslation() const { path } = useRouteMatch() + const { search } = useLocation() + const lockView = queryStringParse(search)['lockView'] === '1' const studioReady = useSubscription(MeteorPubSub.uiStudio, studioId) const playlistReady = useSubscription(MeteorPubSub.rundownPlaylistForStudio, studioId, true) @@ -48,6 +52,9 @@ export function ActiveRundownView({ studioId }: Readonly<{ studioId: StudioId }> ) } else if (studio) { + if (lockView) { + return + } return } else if (studioId) { return diff --git a/packages/webui/src/client/ui/RundownView.tsx b/packages/webui/src/client/ui/RundownView.tsx index f84ed7f11dd..35ef8f0da0e 100644 --- a/packages/webui/src/client/ui/RundownView.tsx +++ b/packages/webui/src/client/ui/RundownView.tsx @@ -288,6 +288,7 @@ export function RundownView(props: Readonly): JSX.Element { ) const hideRundownHeader = params['hideRundownHeader'] === '1' + const lockView = props.inActiveRundownView && params['lockView'] === '1' return (
): JSX.Element { uiSegmentMap={miniShelfData.uiSegmentMap} miniShelfFilter={miniShelfData.miniShelfFilter} hideRundownHeader={hideRundownHeader} + lockView={lockView} />
) @@ -323,6 +325,7 @@ interface IPropsWithReady extends IProps { subsReady: boolean userPermissions: Readonly hideRundownHeader?: boolean + lockView?: boolean } interface IRundownViewContentSnapshot { @@ -1410,6 +1413,7 @@ const RundownViewContent = translateWithTracker )} @@ -1516,7 +1520,7 @@ const RundownViewContent = translateWithTracker {this.props.userPermissions.studio && ( )} diff --git a/packages/webui/src/client/ui/RundownView/RundownHeader/RundownContextMenu.tsx b/packages/webui/src/client/ui/RundownView/RundownHeader/RundownContextMenu.tsx index 137bad1312b..96ad5a138f3 100644 --- a/packages/webui/src/client/ui/RundownView/RundownHeader/RundownContextMenu.tsx +++ b/packages/webui/src/client/ui/RundownView/RundownHeader/RundownContextMenu.tsx @@ -26,6 +26,7 @@ interface RundownContextMenuProps { playlist: DBRundownPlaylist studio: UIStudio firstRundown: Rundown | undefined + lockView?: boolean onShow?: () => void onHide?: () => void } @@ -39,6 +40,7 @@ export function RundownContextMenu({ playlist, studio, firstRundown, + lockView, onShow, onHide, }: Readonly): JSX.Element { @@ -133,8 +135,12 @@ export function RundownContextMenu({ })} {t('Store Snapshot')} - - history.push('/')}>{t('Close Rundown')} + {!lockView && ( + <> + + history.push('/')}>{t('Close Rundown')} + + )} ) : ( diff --git a/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.scss b/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.scss index 41ab77e042e..0ed5bea2101 100644 --- a/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.scss +++ b/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.scss @@ -536,6 +536,11 @@ flex-shrink: 0; transition: opacity 0.2s; + &--placeholder { + visibility: hidden; + pointer-events: none; + } + svg, i { filter: drop-shadow(0 0 0 rgba(255, 255, 255, 0)); @@ -578,7 +583,7 @@ color: #fff; } - .rundown-header__close-btn { + .rundown-header__close-btn:not(.rundown-header__close-btn--placeholder) { opacity: 1; } diff --git a/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.tsx b/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.tsx index dc7414a8d0e..8f3f5539ec7 100644 --- a/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.tsx +++ b/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeader.tsx @@ -27,6 +27,7 @@ interface IRundownHeaderProps { studio: UIStudio firstRundown: Rundown | undefined rundownCount: number + lockView?: boolean } export function RundownHeader({ @@ -35,6 +36,7 @@ export function RundownHeader({ firstRundown, currentRundown, rundownCount, + lockView, }: IRundownHeaderProps): JSX.Element { const { t } = useTranslation() const timingDurations = useTiming() @@ -80,6 +82,7 @@ export function RundownHeader({ playlist={playlist} studio={studio} firstRundown={firstRundown} + lockView={lockView} onShow={() => setIsContextMenuOpen(true)} onHide={() => { setIsMenuOpen(false) @@ -157,9 +160,15 @@ export function RundownHeader({ - - - + {lockView ? ( + + ) : ( + + + + )}