mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-15 20:33:47 +00:00
chore: folder structure
This commit is contained in:
committed by
Carlos Valente
parent
665496907e
commit
f99ad83049
@@ -1,164 +0,0 @@
|
||||
@use '../../../theme/viewerDefs' as *;
|
||||
|
||||
.backstage {
|
||||
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: min(2vh, 16px);
|
||||
padding: min(2vh, 16px) clamp(16px, 10vw, 64px);
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 40vw;
|
||||
grid-template-rows: auto 12px 1fr auto;
|
||||
grid-template-areas:
|
||||
' header header header'
|
||||
' progress progress schedule-nav'
|
||||
' now now schedule'
|
||||
' info info schedule';
|
||||
|
||||
/* =================== HEADER + EXTRAS ===================*/
|
||||
|
||||
.project-header {
|
||||
grid-area: header;
|
||||
font-size: clamp(32px, 4.5vw, 64px);
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: min(200px, 20vw);
|
||||
max-height: min(100px, 20vh);
|
||||
}
|
||||
|
||||
.clock-container {
|
||||
margin-left: auto;
|
||||
|
||||
.label {
|
||||
font-size: clamp(16px, 1.5vw, 24px);
|
||||
font-weight: 600;
|
||||
color: var(--label-color-override, $viewer-label-color);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: clamp(32px, 3.5vw, 50px);
|
||||
font-weight: 600;
|
||||
color: var(--secondary-color-override, $viewer-secondary-color);
|
||||
letter-spacing: 0.05em;
|
||||
line-height: 0.95em;
|
||||
}
|
||||
}
|
||||
|
||||
/* =================== MAIN - NOW ===================*/
|
||||
|
||||
.progress-container {
|
||||
grid-area: progress;
|
||||
width: 100%;
|
||||
margin: 0 auto -8px;
|
||||
}
|
||||
|
||||
.now-container {
|
||||
grid-area: now;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: min(2vh, 16px);
|
||||
}
|
||||
|
||||
.event {
|
||||
background-color: var(--card-background-color-override, $viewer-card-bg-color);
|
||||
padding: 16px 24px;
|
||||
border-radius: 8px;
|
||||
|
||||
&.blink {
|
||||
animation-name: blink;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-iteration-count: 3;
|
||||
animation-duration: 1s;
|
||||
}
|
||||
}
|
||||
|
||||
.timer-group {
|
||||
grid-area: timer;
|
||||
border-top: 2px solid var(--background-color-override, $viewer-background-color);
|
||||
margin-top: 2em;
|
||||
padding-top: 1em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.timer-gap {
|
||||
flex: 1;
|
||||
max-width: 7.5em;
|
||||
}
|
||||
|
||||
.aux-timers {
|
||||
font-size: max(1vw, 16px);
|
||||
|
||||
&__label {
|
||||
color: var(--label-color-override, $viewer-label-color);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&__value {
|
||||
color: var(--secondary-color-override, $viewer-secondary-color);
|
||||
font-size: clamp(24px, 2vw, 32px);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
}
|
||||
|
||||
/* =================== MAIN - SCHEDULE ===================*/
|
||||
|
||||
.schedule-container {
|
||||
grid-area: schedule;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
margin-left: clamp(16px, 5vw, 64px);
|
||||
}
|
||||
|
||||
.schedule-nav-container {
|
||||
grid-area: schedule-nav;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.info {
|
||||
grid-area: info;
|
||||
display: flex;
|
||||
gap: max(1vw, 16px);
|
||||
align-self: flex-end;
|
||||
overflow: hidden;
|
||||
|
||||
&__message {
|
||||
font-size: clamp(16px, 1.5vw, 24px);
|
||||
line-height: 1.3em;
|
||||
white-space: pre-line;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.qr {
|
||||
padding: 4px;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* =================== AMIMATION ===================*/
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
background-color: var(--card-background-color-override, $viewer-card-bg-color);
|
||||
}
|
||||
50% {
|
||||
background-color: var(--card-background-color-blink-override, $playback-start);
|
||||
}
|
||||
100% {
|
||||
background-color: var(--card-background-color-override, $viewer-card-bg-color);
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import QRCode from 'react-qr-code';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { CustomFields, OntimeEvent, ProjectData, Settings, SupportedEvent } from 'ontime-types';
|
||||
import { millisToString, removeLeadingZero } from 'ontime-utils';
|
||||
|
||||
import ProgressBar from '../../../common/components/progress-bar/ProgressBar';
|
||||
import Schedule from '../../../common/components/schedule/Schedule';
|
||||
import { ScheduleProvider } from '../../../common/components/schedule/ScheduleContext';
|
||||
import ScheduleNav from '../../../common/components/schedule/ScheduleNav';
|
||||
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 { timerPlaceholderMin } from '../../../common/utils/styleUtils';
|
||||
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import { titleVariants } from '../common/animation';
|
||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||
import { getPropertyValue } from '../common/viewUtils';
|
||||
|
||||
import { getBackstageOptions } from './backstage.options';
|
||||
|
||||
import './Backstage.scss';
|
||||
|
||||
export const MotionTitleCard = motion(TitleCard);
|
||||
|
||||
interface BackstageProps {
|
||||
backstageEvents: OntimeEvent[];
|
||||
customFields: CustomFields;
|
||||
eventNext: OntimeEvent | null;
|
||||
eventNow: OntimeEvent | null;
|
||||
general: ProjectData;
|
||||
isMirrored: boolean;
|
||||
time: ViewExtendedTimer;
|
||||
selectedId: string | null;
|
||||
settings: Settings | undefined;
|
||||
}
|
||||
|
||||
export default function Backstage(props: BackstageProps) {
|
||||
const { backstageEvents, customFields, eventNext, eventNow, general, time, isMirrored, selectedId, settings } = props;
|
||||
|
||||
const { getLocalizedString } = useTranslation();
|
||||
const [blinkClass, setBlinkClass] = useState(false);
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
useWindowTitle('Backstage');
|
||||
|
||||
// blink on change
|
||||
useEffect(() => {
|
||||
setBlinkClass(false);
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setBlinkClass(true);
|
||||
}, 10);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [selectedId]);
|
||||
|
||||
const clock = formatTime(time.clock);
|
||||
const startedAt = formatTime(time.startedAt);
|
||||
const isNegative = (time.current ?? 0) < 0;
|
||||
const expectedFinish = isNegative ? getLocalizedString('countdown.overtime') : formatTime(time.expectedFinish);
|
||||
|
||||
const qrSize = Math.max(window.innerWidth / 15, 128);
|
||||
const filteredEvents = backstageEvents.filter((event) => event.type === SupportedEvent.Event);
|
||||
const showProgress = time.playback !== 'stop';
|
||||
|
||||
const secondarySource = searchParams.get('secondary-src');
|
||||
const secondaryTextNext = getPropertyValue(eventNext, secondarySource);
|
||||
const secondaryTextNow = getPropertyValue(eventNow, secondarySource);
|
||||
|
||||
let stageTimer = millisToString(time.current, { fallback: timerPlaceholderMin });
|
||||
stageTimer = removeLeadingZero(stageTimer);
|
||||
|
||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||
const backstageOptions = getBackstageOptions(defaultFormat, customFields);
|
||||
|
||||
return (
|
||||
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
|
||||
<ViewParamsEditor viewOptions={backstageOptions} />
|
||||
<div className='project-header'>
|
||||
{general?.projectLogo && <ViewLogo name={general.projectLogo} className='logo' />}
|
||||
{general.title}
|
||||
<div className='clock-container'>
|
||||
<div className='label'>{getLocalizedString('common.time_now')}</div>
|
||||
<SuperscriptTime time={clock} className='time' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProgressBar
|
||||
className='progress-container'
|
||||
current={time.current}
|
||||
duration={time.duration}
|
||||
hidden={!showProgress}
|
||||
/>
|
||||
|
||||
<div className='now-container'>
|
||||
<AnimatePresence>
|
||||
{eventNow && (
|
||||
<motion.div
|
||||
className={`event now ${blinkClass ? 'blink' : ''}`}
|
||||
key='now'
|
||||
variants={titleVariants}
|
||||
initial='hidden'
|
||||
animate='visible'
|
||||
exit='exit'
|
||||
>
|
||||
<TitleCard title={eventNow.title} secondary={secondaryTextNow} />
|
||||
<div className='timer-group'>
|
||||
<div className='aux-timers'>
|
||||
<div className='aux-timers__label'>{getLocalizedString('common.started_at')}</div>
|
||||
<SuperscriptTime time={startedAt} className='aux-timers__value' />
|
||||
</div>
|
||||
<div className='timer-gap' />
|
||||
<div className='aux-timers'>
|
||||
<div className='aux-timers__label'>{getLocalizedString('common.expected_finish')}</div>
|
||||
{isNegative ? (
|
||||
<div className='aux-timers__value'>{expectedFinish}</div>
|
||||
) : (
|
||||
<SuperscriptTime time={expectedFinish} className='aux-timers__value' />
|
||||
)}
|
||||
</div>
|
||||
<div className='timer-gap' />
|
||||
<div className='aux-timers'>
|
||||
<div className='aux-timers__label'>{getLocalizedString('common.stage_timer')}</div>
|
||||
<div className='aux-timers__value'>{stageTimer}</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AnimatePresence>
|
||||
{eventNext && (
|
||||
<MotionTitleCard
|
||||
className='event next'
|
||||
key='next'
|
||||
variants={titleVariants}
|
||||
initial='hidden'
|
||||
animate='visible'
|
||||
exit='exit'
|
||||
label='next'
|
||||
title={eventNext.title}
|
||||
secondary={secondaryTextNext}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
<ScheduleProvider events={filteredEvents} selectedEventId={selectedId} isBackstage>
|
||||
<ScheduleNav className='schedule-nav-container' />
|
||||
<Schedule isProduction className='schedule-container' />
|
||||
</ScheduleProvider>
|
||||
|
||||
<div className='info'>
|
||||
{general.backstageUrl && <QRCode value={general.backstageUrl} size={qrSize} level='L' className='qr' />}
|
||||
{general.backstageInfo && <div className='info__message'>{general.backstageInfo}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { CustomFields } from 'ontime-types';
|
||||
|
||||
import {
|
||||
getTimeOption,
|
||||
makeOptionsFromCustomFields,
|
||||
OptionTitle,
|
||||
} from '../../../common/components/view-params-editor/constants';
|
||||
import { ViewOption } from '../../../common/components/view-params-editor/types';
|
||||
|
||||
export const getBackstageOptions = (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: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
title: OptionTitle.Schedule,
|
||||
collapsible: true,
|
||||
options: [
|
||||
{
|
||||
id: 'eventsPerPage',
|
||||
title: 'Events per page',
|
||||
description: 'Sets the number of events on the page, can cause overflow',
|
||||
type: 'number',
|
||||
placeholder: '8 (default)',
|
||||
},
|
||||
{
|
||||
id: 'hidePast',
|
||||
title: 'Hide past events',
|
||||
description: 'Scheduler will only show upcoming events',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'stopCycle',
|
||||
title: 'Stop cycling through event pages',
|
||||
description: 'Schedule will not auto-cycle through events',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -3,9 +3,6 @@ import { useSearchParams } from 'react-router-dom';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { CustomFields, OntimeEvent, ProjectData, Settings } from 'ontime-types';
|
||||
|
||||
import Schedule from '../../../common/components/schedule/Schedule';
|
||||
import { ScheduleProvider } from '../../../common/components/schedule/ScheduleContext';
|
||||
import ScheduleNav from '../../../common/components/schedule/ScheduleNav';
|
||||
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';
|
||||
@@ -13,6 +10,9 @@ import { useWindowTitle } from '../../../common/hooks/useWindowTitle';
|
||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import Schedule from '../../../views/common/schedule/Schedule';
|
||||
import { ScheduleProvider } from '../../../views/common/schedule/ScheduleContext';
|
||||
import ScheduleNav from '../../../views/common/schedule/ScheduleNav';
|
||||
import { titleVariants } from '../common/animation';
|
||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||
import { getPropertyValue } from '../common/viewUtils';
|
||||
|
||||
Reference in New Issue
Block a user