mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 06:59:09 +00:00
feat: project info view
This commit is contained in:
committed by
Carlos Valente
parent
312b294a61
commit
7aec3e0d73
@@ -32,12 +32,14 @@ const Timeline = React.lazy(() => import('./views/timeline/TimelinePage'));
|
|||||||
const Public = React.lazy(() => import('./features/viewers/public/Public'));
|
const Public = React.lazy(() => import('./features/viewers/public/Public'));
|
||||||
const Lower = React.lazy(() => import('./features/viewers/lower-thirds/LowerThird'));
|
const Lower = React.lazy(() => import('./features/viewers/lower-thirds/LowerThird'));
|
||||||
const StudioClock = React.lazy(() => import('./features/viewers/studio/StudioClock'));
|
const StudioClock = React.lazy(() => import('./features/viewers/studio/StudioClock'));
|
||||||
|
const ProjectInfo = React.lazy(() => import('./views/project-info/ProjectInfo'));
|
||||||
|
|
||||||
const STimer = withPreset(withData(TimerView));
|
const STimer = withPreset(withData(TimerView));
|
||||||
const SMinimalTimer = withPreset(withData(MinimalTimerView));
|
const SMinimalTimer = withPreset(withData(MinimalTimerView));
|
||||||
const SClock = withPreset(withData(ClockView));
|
const SClock = withPreset(withData(ClockView));
|
||||||
const SCountdown = withPreset(withData(Countdown));
|
const SCountdown = withPreset(withData(Countdown));
|
||||||
const SBackstage = withPreset(withData(Backstage));
|
const SBackstage = withPreset(withData(Backstage));
|
||||||
|
const SProjectInfo = withPreset(withData(ProjectInfo));
|
||||||
const SPublic = withPreset(withData(Public));
|
const SPublic = withPreset(withData(Public));
|
||||||
const SLowerThird = withPreset(withData(Lower));
|
const SLowerThird = withPreset(withData(Lower));
|
||||||
const SStudio = withPreset(withData(StudioClock));
|
const SStudio = withPreset(withData(StudioClock));
|
||||||
@@ -142,6 +144,14 @@ export default function AppRouter() {
|
|||||||
</ViewLoader>
|
</ViewLoader>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path='/info'
|
||||||
|
element={
|
||||||
|
<ViewLoader>
|
||||||
|
<SProjectInfo />
|
||||||
|
</ViewLoader>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
{/*/!* Protected Routes *!/*/}
|
{/*/!* Protected Routes *!/*/}
|
||||||
<Route path='/editor' element={<Editor />} />
|
<Route path='/editor' element={<Editor />} />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export const navigatorConstants = [
|
|||||||
{ url: 'lower', label: 'Lower Thirds' },
|
{ url: 'lower', label: 'Lower Thirds' },
|
||||||
{ url: 'studio', label: 'Studio Clock' },
|
{ url: 'studio', label: 'Studio Clock' },
|
||||||
{ url: 'countdown', label: 'Countdown' },
|
{ url: 'countdown', label: 'Countdown' },
|
||||||
|
{ url: 'info', label: 'Project Info' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// default time format to use for users in 12 hour clocks
|
// 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('Editor', `${clientUrl}/editor`),
|
||||||
makeItemOpenInBrowser('Cuesheet', `${clientUrl}/cuesheet`),
|
makeItemOpenInBrowser('Cuesheet', `${clientUrl}/cuesheet`),
|
||||||
makeItemOpenInBrowser('Operator', `${clientUrl}/op`),
|
makeItemOpenInBrowser('Operator', `${clientUrl}/op`),
|
||||||
|
makeItemOpenInBrowser('Project info', `${clientUrl}/info`),
|
||||||
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'forceReload' },
|
{ role: 'forceReload' },
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ test.describe('test view navigation feature', () => {
|
|||||||
page.locator('data-test-id=countdown-view');
|
page.locator('data-test-id=countdown-view');
|
||||||
await expect(page).toHaveURL('http://localhost:4001/countdown');
|
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();
|
await page.getByRole('button', { name: 'toggle menu' }).click();
|
||||||
page.locator('data-test-id=navigation__menu');
|
page.locator('data-test-id=navigation__menu');
|
||||||
await page.getByRole('link', { name: 'Timer', exact: true }).click();
|
await page.getByRole('link', { name: 'Timer', exact: true }).click();
|
||||||
|
|||||||
Reference in New Issue
Block a user