mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-10 08:39:34 +00:00
refactor: remove public view
This commit is contained in:
@@ -29,7 +29,6 @@ const Countdown = React.lazy(() => import('./features/viewers/countdown/Countdow
|
|||||||
|
|
||||||
const Backstage = React.lazy(() => import('./views/backstage/Backstage'));
|
const Backstage = React.lazy(() => import('./views/backstage/Backstage'));
|
||||||
const Timeline = React.lazy(() => import('./views/timeline/TimelinePage'));
|
const Timeline = React.lazy(() => import('./views/timeline/TimelinePage'));
|
||||||
const Public = React.lazy(() => import('./views/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 ProjectInfo = React.lazy(() => import('./views/project-info/ProjectInfo'));
|
||||||
@@ -40,7 +39,6 @@ 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 SProjectInfo = withPreset(withData(ProjectInfo));
|
||||||
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));
|
||||||
const STimeline = withPreset(withData(Timeline));
|
const STimeline = withPreset(withData(Timeline));
|
||||||
@@ -88,14 +86,6 @@ export default function AppRouter() {
|
|||||||
</ViewLoader>
|
</ViewLoader>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
|
||||||
path='/public'
|
|
||||||
element={
|
|
||||||
<ViewLoader>
|
|
||||||
<SPublic />
|
|
||||||
</ViewLoader>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
<Route
|
||||||
path='/minimal'
|
path='/minimal'
|
||||||
element={
|
element={
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export default function Backstage(props: BackstageProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showSchedule && <ScheduleExport selectedId={selectedId} isBackstage />}
|
{showSchedule && <ScheduleExport selectedId={selectedId} />}
|
||||||
|
|
||||||
<div className={cx(['info', !showSchedule && 'info--stretch'])}>
|
<div className={cx(['info', !showSchedule && 'info--stretch'])}>
|
||||||
{general.backstageUrl && <QRCode value={general.backstageUrl} size={qrSize} level='L' className='qr' />}
|
{general.backstageUrl && <QRCode value={general.backstageUrl} size={qrSize} level='L' className='qr' />}
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
|
|
||||||
import { getScheduledTimes } from './schedule.utils';
|
|
||||||
import { useSchedule } from './ScheduleContext';
|
import { useSchedule } from './ScheduleContext';
|
||||||
import ScheduleItem from './ScheduleItem';
|
import ScheduleItem from './ScheduleItem';
|
||||||
|
|
||||||
import './Schedule.scss';
|
import './Schedule.scss';
|
||||||
|
|
||||||
interface ScheduleProps {
|
interface ScheduleProps {
|
||||||
isProduction?: boolean;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Schedule({ isProduction, className }: ScheduleProps) {
|
export default function Schedule({ className }: ScheduleProps) {
|
||||||
const { events, isBackstage, containerRef } = useSchedule();
|
const { events, containerRef } = useSchedule();
|
||||||
|
|
||||||
if (events?.length < 1) {
|
if (events?.length < 1) {
|
||||||
return null;
|
return null;
|
||||||
@@ -21,18 +19,15 @@ export default function Schedule({ isProduction, className }: ScheduleProps) {
|
|||||||
return (
|
return (
|
||||||
<ul className={cx(['schedule', className])} ref={containerRef}>
|
<ul className={cx(['schedule', className])} ref={containerRef}>
|
||||||
{events.map((event) => {
|
{events.map((event) => {
|
||||||
const { timeStart, timeEnd, delay } = getScheduledTimes(event, isProduction);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScheduleItem
|
<ScheduleItem
|
||||||
key={event.id}
|
key={event.id}
|
||||||
timeStart={timeStart}
|
timeStart={event.timeStart}
|
||||||
timeEnd={timeEnd}
|
timeEnd={event.timeEnd}
|
||||||
title={event.title}
|
title={event.title}
|
||||||
colour={isBackstage ? event.colour : undefined}
|
colour={event.colour}
|
||||||
backstageEvent={!event.isPublic}
|
|
||||||
skip={event.skip}
|
skip={event.skip}
|
||||||
delay={delay}
|
delay={event.skip ? 0 : event.delay}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -7,16 +7,15 @@ import ScheduleNav from './ScheduleNav';
|
|||||||
|
|
||||||
interface ScheduleExportProps {
|
interface ScheduleExportProps {
|
||||||
selectedId: MaybeString;
|
selectedId: MaybeString;
|
||||||
isBackstage?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(ScheduleExport);
|
export default memo(ScheduleExport);
|
||||||
function ScheduleExport(props: ScheduleExportProps) {
|
function ScheduleExport(props: ScheduleExportProps) {
|
||||||
const { selectedId, isBackstage } = props;
|
const { selectedId } = props;
|
||||||
return (
|
return (
|
||||||
<ScheduleProvider selectedEventId={selectedId} isBackstage={isBackstage}>
|
<ScheduleProvider selectedEventId={selectedId}>
|
||||||
<ScheduleNav className='schedule-nav-container' />
|
<ScheduleNav className='schedule-nav-container' />
|
||||||
<Schedule isProduction={isBackstage} className='schedule-container' />
|
<Schedule className='schedule-container' />
|
||||||
</ScheduleProvider>
|
</ScheduleProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,13 @@ interface ScheduleItemProps {
|
|||||||
timeStart: number;
|
timeStart: number;
|
||||||
timeEnd: number;
|
timeEnd: number;
|
||||||
title: string;
|
title: string;
|
||||||
backstageEvent: boolean;
|
|
||||||
colour?: string;
|
colour?: string;
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
delay: number;
|
delay: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ScheduleItem(props: ScheduleItemProps) {
|
export default function ScheduleItem(props: ScheduleItemProps) {
|
||||||
const { timeStart, timeEnd, title, backstageEvent, colour, skip, delay } = props;
|
const { timeStart, timeEnd, title, colour, skip, delay } = props;
|
||||||
const { showProjected } = useScheduleOptions();
|
const { showProjected } = useScheduleOptions();
|
||||||
|
|
||||||
if (showProjected) {
|
if (showProjected) {
|
||||||
@@ -33,7 +32,6 @@ export default function ScheduleItem(props: ScheduleItemProps) {
|
|||||||
timeEnd={timeEnd}
|
timeEnd={timeEnd}
|
||||||
title={title}
|
title={title}
|
||||||
colour={colour}
|
colour={colour}
|
||||||
backstageEvent={backstageEvent}
|
|
||||||
skip={skip}
|
skip={skip}
|
||||||
delay={delay}
|
delay={delay}
|
||||||
/>
|
/>
|
||||||
@@ -47,7 +45,6 @@ export default function ScheduleItem(props: ScheduleItemProps) {
|
|||||||
timeEnd={timeEnd}
|
timeEnd={timeEnd}
|
||||||
title={title}
|
title={title}
|
||||||
colour={colour}
|
colour={colour}
|
||||||
backstageEvent={backstageEvent}
|
|
||||||
skip={skip}
|
skip={skip}
|
||||||
delay={delay}
|
delay={delay}
|
||||||
/>
|
/>
|
||||||
@@ -63,7 +60,6 @@ export default function ScheduleItem(props: ScheduleItemProps) {
|
|||||||
<SuperscriptTime time={start} />
|
<SuperscriptTime time={start} />
|
||||||
→
|
→
|
||||||
<SuperscriptTime time={end} />
|
<SuperscriptTime time={end} />
|
||||||
{backstageEvent && '*'}
|
|
||||||
</div>
|
</div>
|
||||||
<div className='entry-title'>{title}</div>
|
<div className='entry-title'>{title}</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -71,7 +67,7 @@ export default function ScheduleItem(props: ScheduleItemProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DelayedScheduleItem(props: ScheduleItemProps) {
|
function DelayedScheduleItem(props: ScheduleItemProps) {
|
||||||
const { timeStart, timeEnd, title, backstageEvent, colour, skip, delay } = props;
|
const { timeStart, timeEnd, title, colour, skip, delay } = props;
|
||||||
|
|
||||||
const start = formatTime(timeStart, formatOptions);
|
const start = formatTime(timeStart, formatOptions);
|
||||||
const end = formatTime(timeEnd, formatOptions);
|
const end = formatTime(timeEnd, formatOptions);
|
||||||
@@ -86,13 +82,11 @@ function DelayedScheduleItem(props: ScheduleItemProps) {
|
|||||||
<SuperscriptTime time={start} />
|
<SuperscriptTime time={start} />
|
||||||
→
|
→
|
||||||
<SuperscriptTime time={end} />
|
<SuperscriptTime time={end} />
|
||||||
{backstageEvent && '*'}
|
|
||||||
</span>
|
</span>
|
||||||
<span className='entry-times--delay'>
|
<span className='entry-times--delay'>
|
||||||
<SuperscriptTime time={delayedStart} />
|
<SuperscriptTime time={delayedStart} />
|
||||||
→
|
→
|
||||||
<SuperscriptTime time={delayedEnd} />
|
<SuperscriptTime time={delayedEnd} />
|
||||||
{backstageEvent && '*'}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='entry-title'>{title}</div>
|
<div className='entry-title'>{title}</div>
|
||||||
@@ -101,7 +95,7 @@ function DelayedScheduleItem(props: ScheduleItemProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ProjectedScheduleItem(props: ScheduleItemProps) {
|
function ProjectedScheduleItem(props: ScheduleItemProps) {
|
||||||
const { timeStart, timeEnd, title, backstageEvent, colour, skip, delay } = props;
|
const { timeStart, timeEnd, title, colour, skip, delay } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={cx(['entry', skip && 'entry--skip'])}>
|
<li className={cx(['entry', skip && 'entry--skip'])}>
|
||||||
@@ -110,7 +104,6 @@ function ProjectedScheduleItem(props: ScheduleItemProps) {
|
|||||||
<ProjectedTime time={timeStart} delay={delay} />
|
<ProjectedTime time={timeStart} delay={delay} />
|
||||||
→
|
→
|
||||||
<ProjectedTime time={timeEnd} delay={delay} />
|
<ProjectedTime time={timeEnd} delay={delay} />
|
||||||
{backstageEvent && '*'}
|
|
||||||
</div>
|
</div>
|
||||||
<div className='entry-title'>{title}</div>
|
<div className='entry-title'>{title}</div>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ export const scheduleOptions: ViewOption = {
|
|||||||
{
|
{
|
||||||
id: 'cycleInterval',
|
id: 'cycleInterval',
|
||||||
title: 'Cycle interval',
|
title: 'Cycle interval',
|
||||||
description: 'How long (in seconds) should each schedule page be shown.',
|
description: 'How long (in seconds) should each schedule page be shown',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
defaultValue: 10,
|
defaultValue: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'showProjected',
|
id: 'showProjected',
|
||||||
title: 'Show projected time',
|
title: 'Show projected time',
|
||||||
description: 'Whether scheduled times should account for runtime offset.',
|
description: 'Whether scheduled times should account for runtime offset',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { OntimeEvent } from 'ontime-types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gather rules for how to present scheduled times
|
|
||||||
*/
|
|
||||||
export function getScheduledTimes(event: OntimeEvent, isProduction?: boolean) {
|
|
||||||
if (isProduction) {
|
|
||||||
return {
|
|
||||||
timeStart: event.timeStart,
|
|
||||||
timeEnd: event.timeEnd,
|
|
||||||
delay: event.skip ? 0 : event.delay,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
timeStart: event.timeStart,
|
|
||||||
timeEnd: event.timeEnd,
|
|
||||||
delay: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
@use '../../theme/viewerDefs' as *;
|
|
||||||
|
|
||||||
.public-screen {
|
|
||||||
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);
|
|
||||||
gap: $view-element-gap;
|
|
||||||
padding: $view-outer-padding;
|
|
||||||
font-size: $base-font-size;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 40vw;
|
|
||||||
grid-template-rows: auto 12px 1fr auto;
|
|
||||||
grid-template-areas:
|
|
||||||
'header header'
|
|
||||||
'now schedule-nav'
|
|
||||||
'info schedule';
|
|
||||||
|
|
||||||
.empty-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin-top: 25vh;
|
|
||||||
color: var(--label-color-override, $viewer-label-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================== HEADER + EXTRAS ===================*/
|
|
||||||
|
|
||||||
.project-header {
|
|
||||||
grid-area: header;
|
|
||||||
font-size: $header-font-size;
|
|
||||||
font-weight: 600;
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
max-width: min(200px, 20vw);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
line-height: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clock-container {
|
|
||||||
margin-left: auto;
|
|
||||||
font-weight: 600;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-size: $timer-label-size;
|
|
||||||
color: var(--label-color-override, $viewer-label-color);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time {
|
|
||||||
font-size: $timer-value-size;
|
|
||||||
color: var(--secondary-color-override, $viewer-secondary-color);
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
line-height: 0.95em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================== MAIN - NOW ===================*/
|
|
||||||
|
|
||||||
.card-container {
|
|
||||||
grid-area: now;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $view-element-gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.event {
|
|
||||||
background-color: var(--card-background-color-override, $viewer-card-bg-color);
|
|
||||||
padding: $view-card-padding;
|
|
||||||
border-radius: $element-border-radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timer-group {
|
|
||||||
border-top: 2px solid var(--background-color-override, $viewer-background-color);
|
|
||||||
margin-top: max(1vh, 16px);
|
|
||||||
padding-top: max(1vh, 16px);
|
|
||||||
display: flex;
|
|
||||||
row-gap: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-entry {
|
|
||||||
&__label {
|
|
||||||
font-size: $timer-label-size;
|
|
||||||
color: var(--label-color-override, $viewer-label-color);
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__value {
|
|
||||||
font-size: $base-font-size;
|
|
||||||
color: var(--secondary-color-override, $viewer-secondary-color);
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
line-height: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--pending {
|
|
||||||
color: var(--timer-pending-color-override, $ontime-roll);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================== MAIN - SCHEDULE ===================*/
|
|
||||||
|
|
||||||
$schedule-left-spacing: clamp(16px, 4vw, 64px);
|
|
||||||
.schedule-container {
|
|
||||||
grid-area: schedule;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
|
||||||
padding-left: $schedule-left-spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule-nav-container {
|
|
||||||
grid-area: schedule-nav;
|
|
||||||
padding-left: $schedule-left-spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================== MAIN - INFO ===================*/
|
|
||||||
|
|
||||||
.info {
|
|
||||||
grid-area: info;
|
|
||||||
display: flex;
|
|
||||||
gap: max(1vw, 16px);
|
|
||||||
align-self: flex-end;
|
|
||||||
overflow: hidden;
|
|
||||||
align-items: end;
|
|
||||||
|
|
||||||
&__message {
|
|
||||||
font-size: $base-font-size;
|
|
||||||
line-height: 1.2em;
|
|
||||||
white-space: pre-line;
|
|
||||||
overflow: hidden;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr {
|
|
||||||
padding: 0.5rem;
|
|
||||||
background-color: $ui-white;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================== MOBILE ===================*/
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.public-screen {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
.project-header {
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo img{
|
|
||||||
height: min(50px, 10vh);
|
|
||||||
}
|
|
||||||
|
|
||||||
.timer-group {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clock-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule-nav-container {
|
|
||||||
padding-left: 0;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule-container {
|
|
||||||
padding-left: 0;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
width: 100%;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info--stretch {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import QRCode from 'react-qr-code';
|
|
||||||
import { useViewportSize } from '@mantine/hooks';
|
|
||||||
import { CustomFields, OntimeEvent, ProjectData, Settings } from 'ontime-types';
|
|
||||||
|
|
||||||
import Empty from '../../common/components/state/Empty';
|
|
||||||
import TitleCard from '../../common/components/title-card/TitleCard';
|
|
||||||
import ViewLogo from '../../common/components/view-logo/ViewLogo';
|
|
||||||
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
|
|
||||||
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
|
|
||||||
import { ViewExtendedTimer } from '../../common/models/TimeManager.type';
|
|
||||||
import { cx } from '../../common/utils/styleUtils';
|
|
||||||
import { formatTime, getDefaultFormat } from '../../common/utils/time';
|
|
||||||
import SuperscriptTime from '../../features/viewers/common/superscript-time/SuperscriptTime';
|
|
||||||
import { useTranslation } from '../../translation/TranslationProvider';
|
|
||||||
import { getIsPendingStart } from '../backstage/backstage.utils';
|
|
||||||
import ScheduleExport from '../common/schedule/ScheduleExport';
|
|
||||||
|
|
||||||
import { getPublicOptions, usePublicOptions } from './public.options';
|
|
||||||
import { getCardData, getFirstStartTime } from './public.utils';
|
|
||||||
|
|
||||||
import './Public.scss';
|
|
||||||
|
|
||||||
interface BackstageProps {
|
|
||||||
customFields: CustomFields;
|
|
||||||
events: OntimeEvent[];
|
|
||||||
general: ProjectData;
|
|
||||||
isMirrored: boolean;
|
|
||||||
publicEventNow: OntimeEvent | null;
|
|
||||||
publicEventNext: OntimeEvent | null;
|
|
||||||
time: ViewExtendedTimer;
|
|
||||||
publicSelectedId: string | null;
|
|
||||||
settings: Settings | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Public(props: BackstageProps) {
|
|
||||||
const {
|
|
||||||
customFields,
|
|
||||||
events,
|
|
||||||
general,
|
|
||||||
isMirrored,
|
|
||||||
publicEventNow,
|
|
||||||
publicEventNext,
|
|
||||||
time,
|
|
||||||
publicSelectedId,
|
|
||||||
settings,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const { getLocalizedString } = useTranslation();
|
|
||||||
const { secondarySource } = usePublicOptions();
|
|
||||||
const { height: screenHeight } = useViewportSize();
|
|
||||||
|
|
||||||
useWindowTitle('Public Schedule');
|
|
||||||
|
|
||||||
// gather card data
|
|
||||||
const hasEvents = events.length > 0;
|
|
||||||
const { showNow, nowMain, nowSecondary, showNext, nextMain, nextSecondary } = getCardData(
|
|
||||||
publicEventNow,
|
|
||||||
publicEventNext,
|
|
||||||
'title',
|
|
||||||
secondarySource,
|
|
||||||
time.playback,
|
|
||||||
);
|
|
||||||
|
|
||||||
// gather timer data
|
|
||||||
const clock = formatTime(time.clock);
|
|
||||||
const isPendingStart = getIsPendingStart(time.playback, time.phase);
|
|
||||||
const scheduledStart = (() => {
|
|
||||||
if (showNow) return undefined;
|
|
||||||
if (!hasEvents) return undefined;
|
|
||||||
return getFirstStartTime(events[0]);
|
|
||||||
})();
|
|
||||||
|
|
||||||
// gather presentation styles
|
|
||||||
const qrSize = Math.max(window.innerWidth / 15, 72);
|
|
||||||
const showSchedule = hasEvents && screenHeight > 700; // in vertical screens we may not have space
|
|
||||||
const showPending = scheduledStart !== undefined;
|
|
||||||
|
|
||||||
// gather option data
|
|
||||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
|
||||||
const publicOptions = getPublicOptions(defaultFormat, customFields);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`public-screen ${isMirrored ? 'mirror' : ''}`} data-testid='public-view'>
|
|
||||||
<ViewParamsEditor viewOptions={publicOptions} />
|
|
||||||
<div className='project-header'>
|
|
||||||
{general?.projectLogo ? <ViewLogo name={general.projectLogo} className='logo' /> : <div className='logo' />}
|
|
||||||
<div className='title'>{general.title}</div>
|
|
||||||
<div className='clock-container'>
|
|
||||||
<div className='label'>{getLocalizedString('common.time_now')}</div>
|
|
||||||
<SuperscriptTime time={clock} className='time' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!hasEvents && <Empty text={getLocalizedString('countdown.waiting')} className='empty-container' />}
|
|
||||||
|
|
||||||
<div className='card-container'>
|
|
||||||
{showNow && hasEvents && (
|
|
||||||
<TitleCard className='event now' label='now' title={nowMain} secondary={nowSecondary} />
|
|
||||||
)}
|
|
||||||
{showPending && (
|
|
||||||
<div className='event'>
|
|
||||||
<div className='title-card__placeholder'>{getLocalizedString('countdown.waiting')}</div>
|
|
||||||
<div className='timer-group'>
|
|
||||||
<div className='time-entry'>
|
|
||||||
<div className={cx(['time-entry__label', isPendingStart && 'time-entry--pending'])}>
|
|
||||||
{getLocalizedString('common.scheduled_start')}
|
|
||||||
</div>
|
|
||||||
<SuperscriptTime time={formatTime(scheduledStart)} className='time-entry__value' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{showNext && hasEvents && (
|
|
||||||
<TitleCard className='event next' label='next' title={nextMain} secondary={nextSecondary} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showSchedule && <ScheduleExport selectedId={publicSelectedId} />}
|
|
||||||
|
|
||||||
<div className={cx(['info', !showSchedule && 'info--stretch'])}>
|
|
||||||
{general.publicUrl && <QRCode value={general.publicUrl} size={qrSize} level='L' className='qr' />}
|
|
||||||
{general.publicInfo && <div className='info__message'>{general.publicInfo}</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
import { CustomFields, OntimeEvent } from 'ontime-types';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getTimeOption,
|
|
||||||
makeOptionsFromCustomFields,
|
|
||||||
OptionTitle,
|
|
||||||
} from '../../common/components/view-params-editor/constants';
|
|
||||||
import { ViewOption } from '../../common/components/view-params-editor/types';
|
|
||||||
import { scheduleOptions } from '../common/schedule/schedule.options';
|
|
||||||
|
|
||||||
export const getPublicOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => {
|
|
||||||
const secondaryOptions = makeOptionsFromCustomFields(customFields, { note: 'Note' });
|
|
||||||
|
|
||||||
return [
|
|
||||||
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
|
|
||||||
{
|
|
||||||
title: OptionTitle.DataSources,
|
|
||||||
collapsible: true,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: 'secondary-src',
|
|
||||||
title: 'Event secondary text',
|
|
||||||
description: 'Select the data source for auxiliary text shown in now and next cards',
|
|
||||||
type: 'option',
|
|
||||||
values: secondaryOptions,
|
|
||||||
defaultValue: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
scheduleOptions,
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
type PublicOptions = {
|
|
||||||
secondarySource: keyof OntimeEvent | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility extract the view options from URL Params
|
|
||||||
* the names and fallback are manually matched with timerOptions
|
|
||||||
*/
|
|
||||||
function getOptionsFromParams(searchParams: URLSearchParams): PublicOptions {
|
|
||||||
// we manually make an object that matches the key above
|
|
||||||
return {
|
|
||||||
secondarySource: searchParams.get('secondary-src') as keyof OntimeEvent | null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook exposes the backstage view options
|
|
||||||
*/
|
|
||||||
export function usePublicOptions(): PublicOptions {
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const options = useMemo(() => getOptionsFromParams(searchParams), [searchParams]);
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { OntimeEvent, Playback } from 'ontime-types';
|
|
||||||
|
|
||||||
import { enDash } from '../../common/utils/styleUtils';
|
|
||||||
import { getPropertyValue } from '../../features/viewers/common/viewUtils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* What should we be showing in the cards?
|
|
||||||
*/
|
|
||||||
export function getCardData(
|
|
||||||
eventNow: OntimeEvent | null,
|
|
||||||
eventNext: OntimeEvent | null,
|
|
||||||
mainSource: keyof OntimeEvent | null,
|
|
||||||
secondarySource: keyof OntimeEvent | null,
|
|
||||||
playback: Playback,
|
|
||||||
) {
|
|
||||||
if (playback === Playback.Stop) {
|
|
||||||
return {
|
|
||||||
showNow: false,
|
|
||||||
nowMain: undefined,
|
|
||||||
nowSecondary: undefined,
|
|
||||||
showNext: false,
|
|
||||||
nextMain: undefined,
|
|
||||||
nextSecondary: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we are loaded, we show the upcoming event as next
|
|
||||||
const nowMain = getPropertyValue(eventNow, mainSource ?? 'title') || enDash;
|
|
||||||
const nowSecondary = getPropertyValue(eventNow, secondarySource);
|
|
||||||
const nextMain = getPropertyValue(eventNext, mainSource ?? 'title') || enDash;
|
|
||||||
const nextSecondary = getPropertyValue(eventNext, secondarySource);
|
|
||||||
|
|
||||||
return {
|
|
||||||
showNow: eventNow !== null,
|
|
||||||
nowMain,
|
|
||||||
nowSecondary,
|
|
||||||
showNext: eventNext !== null,
|
|
||||||
nextMain,
|
|
||||||
nextSecondary,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFirstStartTime(firstPublicEvent: OntimeEvent | null): number | undefined {
|
|
||||||
return firstPublicEvent?.timeStart;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user