feat: project info view

This commit is contained in:
Carlos Valente
2024-11-18 14:53:00 +01:00
committed by Carlos Valente
parent 312b294a61
commit 7aec3e0d73
9 changed files with 205 additions and 0 deletions
+10
View File
@@ -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() {
</ViewLoader>
}
/>
<Route
path='/info'
element={
<ViewLoader>
<SProjectInfo />
</ViewLoader>
}
/>
{/*/!* Protected Routes *!/*/}
<Route path='/editor' element={<Editor />} />
+1
View File
@@ -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
@@ -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;
}
}
}
@@ -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 <Empty text='No data found' />;
}
return (
<div className={`project ${isMirrored ? 'mirror' : ''}`} data-testid='project-view'>
<ViewParamsEditor viewOptions={projectInfoOptions} />
{general.projectLogo && <ViewLogo name={general.projectLogo} className='logo' />}
<div className='info'>
{general.title && (
<>
<div className='info__label'>Title</div>
<div className='info__value'>{general.title}</div>
</>
)}
<BackstageInfo general={general} />
<PublicInfo general={general} />
</div>
</div>
);
}
@@ -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 && (
<>
<div className='info__label'>Backstage info</div>
<div className='info__value'>{general.backstageInfo}</div>
</>
)}
{general.backstageUrl && (
<>
<div className='info__label'>Backstage URL</div>
<a href={general.backstageUrl} target='_blank' rel='noreferrer' className='info__value'>
{general.backstageUrl}
</a>
</>
)}
</>
);
}
@@ -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,
},
];
@@ -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 && (
<>
<div className='info__label'>Public info</div>
<div className='info__value'>{general.publicInfo}</div>
</>
)}
{general.publicUrl && (
<>
<div className='info__label'>Public URL</div>
<a href={general.publicUrl} target='_blank' rel='noreferrer' className='info__value'>
{general.publicUrl}
</a>
</>
)}
</>
);
}
@@ -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' },
+6
View File
@@ -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();