From 7aec3e0d73d74f09d5d6dc7c9e7d87a3b35e46b6 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Mon, 18 Nov 2024 14:53:00 +0100 Subject: [PATCH] feat: project info view --- apps/client/src/AppRouter.tsx | 10 ++++ apps/client/src/viewerConfig.ts | 1 + .../src/views/project-info/ProjectInfo.scss | 48 +++++++++++++++++++ .../src/views/project-info/ProjectInfo.tsx | 44 +++++++++++++++++ .../backstage-info/BackstageInfo.tsx | 38 +++++++++++++++ .../views/project-info/projectInfo.options.ts | 19 ++++++++ .../project-info/public-info/PublicInfo.tsx | 38 +++++++++++++++ apps/electron/src/menu/applicationMenu.js | 1 + e2e/tests/002-view-navigation.spec.ts | 6 +++ 9 files changed, 205 insertions(+) create mode 100644 apps/client/src/views/project-info/ProjectInfo.scss create mode 100644 apps/client/src/views/project-info/ProjectInfo.tsx create mode 100644 apps/client/src/views/project-info/backstage-info/BackstageInfo.tsx create mode 100644 apps/client/src/views/project-info/projectInfo.options.ts create mode 100644 apps/client/src/views/project-info/public-info/PublicInfo.tsx diff --git a/apps/client/src/AppRouter.tsx b/apps/client/src/AppRouter.tsx index 0248f7bb4..24789c754 100644 --- a/apps/client/src/AppRouter.tsx +++ b/apps/client/src/AppRouter.tsx @@ -32,12 +32,14 @@ const Timeline = React.lazy(() => import('./views/timeline/TimelinePage')); const Public = React.lazy(() => import('./features/viewers/public/Public')); const Lower = React.lazy(() => import('./features/viewers/lower-thirds/LowerThird')); const StudioClock = React.lazy(() => import('./features/viewers/studio/StudioClock')); +const ProjectInfo = React.lazy(() => import('./views/project-info/ProjectInfo')); const STimer = withPreset(withData(TimerView)); const SMinimalTimer = withPreset(withData(MinimalTimerView)); const SClock = withPreset(withData(ClockView)); const SCountdown = withPreset(withData(Countdown)); const SBackstage = withPreset(withData(Backstage)); +const SProjectInfo = withPreset(withData(ProjectInfo)); const SPublic = withPreset(withData(Public)); const SLowerThird = withPreset(withData(Lower)); const SStudio = withPreset(withData(StudioClock)); @@ -142,6 +144,14 @@ export default function AppRouter() { } /> + + + + } + /> {/*/!* Protected Routes *!/*/} } /> diff --git a/apps/client/src/viewerConfig.ts b/apps/client/src/viewerConfig.ts index 610b282bc..1ac97f085 100644 --- a/apps/client/src/viewerConfig.ts +++ b/apps/client/src/viewerConfig.ts @@ -8,6 +8,7 @@ export const navigatorConstants = [ { url: 'lower', label: 'Lower Thirds' }, { url: 'studio', label: 'Studio Clock' }, { url: 'countdown', label: 'Countdown' }, + { url: 'info', label: 'Project Info' }, ]; // default time format to use for users in 12 hour clocks diff --git a/apps/client/src/views/project-info/ProjectInfo.scss b/apps/client/src/views/project-info/ProjectInfo.scss new file mode 100644 index 000000000..cee7777ed --- /dev/null +++ b/apps/client/src/views/project-info/ProjectInfo.scss @@ -0,0 +1,48 @@ +@use '../../theme/viewerDefs' as *; + +.project { + margin: 0; + box-sizing: border-box; /* reset */ + overflow: hidden; + width: 100%; /* restrict the page width to viewport */ + height: 100vh; + + font-family: var(--font-family-override, $viewer-font-family); + background: var(--background-color-override, $viewer-background-color); + color: var(--color-override, $viewer-color); + padding: min(2vh, 16px) clamp(16px, 10vw, 64px); + + display: flex; + flex-direction: column; + align-items: center; + + .logo { + max-width: min(200px, 30vw); + max-height: min(100px, 20vh); + } + + .info { + flex: 1; + max-height: 100%; + overflow-y: auto; + width: min(calc(100vw - 4rem), 800px); + font-size: clamp(16px, 1.5vw, 24px); + } + + .info__label { + font-weight: 600; + color: var(--label-color-override, $viewer-label-color); + text-transform: uppercase; + } + .info__label { + margin-top: 1rem; + } + + + a.info__value { + color: $action-text-color; + &:hover { + color: $ontime-color; + } + } +} diff --git a/apps/client/src/views/project-info/ProjectInfo.tsx b/apps/client/src/views/project-info/ProjectInfo.tsx new file mode 100644 index 000000000..ccd06281d --- /dev/null +++ b/apps/client/src/views/project-info/ProjectInfo.tsx @@ -0,0 +1,44 @@ +import { ProjectData } from 'ontime-types'; + +import Empty from '../../common/components/state/Empty'; +import ViewLogo from '../../common/components/view-logo/ViewLogo'; +import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor'; +import { useWindowTitle } from '../../common/hooks/useWindowTitle'; + +import BackstageInfo from './backstage-info/BackstageInfo'; +import PublicInfo from './public-info/PublicInfo'; +import { projectInfoOptions } from './projectInfo.options'; + +import './ProjectInfo.scss'; + +interface ProjectInfoProps { + general: ProjectData; + isMirrored: boolean; +} + +export default function ProjectInfoProps(props: ProjectInfoProps) { + const { general, isMirrored } = props; + + useWindowTitle('Project info'); + + if (!general) { + return ; + } + + return ( +
+ + {general.projectLogo && } +
+ {general.title && ( + <> +
Title
+
{general.title}
+ + )} + + +
+
+ ); +} diff --git a/apps/client/src/views/project-info/backstage-info/BackstageInfo.tsx b/apps/client/src/views/project-info/backstage-info/BackstageInfo.tsx new file mode 100644 index 000000000..c60202f6f --- /dev/null +++ b/apps/client/src/views/project-info/backstage-info/BackstageInfo.tsx @@ -0,0 +1,38 @@ +import { useSearchParams } from 'react-router-dom'; +import { ProjectData } from 'ontime-types'; + +import { isStringBoolean } from '../../../features/viewers/common/viewUtils'; + +interface BackstageInfoProps { + general: ProjectData; +} + +export default function BackstageInfo(props: BackstageInfoProps) { + const { general } = props; + const [searchParams] = useSearchParams(); + + const showBackstage = isStringBoolean(searchParams.get('showBackstage')); + + if (!showBackstage) { + return null; + } + + return ( + <> + {general.backstageInfo && ( + <> +
Backstage info
+
{general.backstageInfo}
+ + )} + {general.backstageUrl && ( + <> +
Backstage URL
+ + {general.backstageUrl} + + + )} + + ); +} diff --git a/apps/client/src/views/project-info/projectInfo.options.ts b/apps/client/src/views/project-info/projectInfo.options.ts new file mode 100644 index 000000000..73b01220f --- /dev/null +++ b/apps/client/src/views/project-info/projectInfo.options.ts @@ -0,0 +1,19 @@ +import { ViewOption } from '../../common/components/view-params-editor/types'; + +export const projectInfoOptions: ViewOption[] = [ + { section: 'Data visibility' }, + { + id: 'showBackstage', + title: 'Show backstage Data', + description: 'Weather to show fields related to the backstage views', + type: 'boolean', + defaultValue: false, + }, + { + id: 'showPublic', + title: 'Show Public Data', + description: 'Weather to show fields related to the public views', + type: 'boolean', + defaultValue: false, + }, +]; diff --git a/apps/client/src/views/project-info/public-info/PublicInfo.tsx b/apps/client/src/views/project-info/public-info/PublicInfo.tsx new file mode 100644 index 000000000..8165c030a --- /dev/null +++ b/apps/client/src/views/project-info/public-info/PublicInfo.tsx @@ -0,0 +1,38 @@ +import { useSearchParams } from 'react-router-dom'; +import { ProjectData } from 'ontime-types'; + +import { isStringBoolean } from '../../../features/viewers/common/viewUtils'; + +interface PublicInfoProps { + general: ProjectData; +} + +export default function PublicInfo(props: PublicInfoProps) { + const { general } = props; + const [searchParams] = useSearchParams(); + + const showPublic = isStringBoolean(searchParams.get('showPublic')); + + if (!showPublic) { + return null; + } + + return ( + <> + {general.publicInfo && ( + <> +
Public info
+
{general.publicInfo}
+ + )} + {general.publicUrl && ( + <> +
Public URL
+ + {general.publicUrl} + + + )} + + ); +} diff --git a/apps/electron/src/menu/applicationMenu.js b/apps/electron/src/menu/applicationMenu.js index 072b7e653..f3613c1c0 100644 --- a/apps/electron/src/menu/applicationMenu.js +++ b/apps/electron/src/menu/applicationMenu.js @@ -139,6 +139,7 @@ function makeViewMenu(clientUrl) { makeItemOpenInBrowser('Editor', `${clientUrl}/editor`), makeItemOpenInBrowser('Cuesheet', `${clientUrl}/cuesheet`), makeItemOpenInBrowser('Operator', `${clientUrl}/op`), + makeItemOpenInBrowser('Project info', `${clientUrl}/info`), { type: 'separator' }, { role: 'forceReload' }, diff --git a/e2e/tests/002-view-navigation.spec.ts b/e2e/tests/002-view-navigation.spec.ts index d37cb4acb..3ed5501bd 100644 --- a/e2e/tests/002-view-navigation.spec.ts +++ b/e2e/tests/002-view-navigation.spec.ts @@ -55,6 +55,12 @@ test.describe('test view navigation feature', () => { page.locator('data-test-id=countdown-view'); await expect(page).toHaveURL('http://localhost:4001/countdown'); + await page.getByRole('button', { name: 'toggle menu' }).click(); + page.locator('data-test-id=navigation__menu'); + await page.getByRole('link', { name: 'Project Info' }).click(); + page.locator('data-test-id=project-view'); + await expect(page).toHaveURL('http://localhost:4001/info'); + await page.getByRole('button', { name: 'toggle menu' }).click(); page.locator('data-test-id=navigation__menu'); await page.getByRole('link', { name: 'Timer', exact: true }).click();