feat: expand user options for views with schedule (#565)

* feat: expand user options for views with schedule

* fix: selected public id on load

* fix: load events on roll

* style: remove empty container
This commit is contained in:
Carlos Valente
2023-11-03 13:47:38 +01:00
committed by GitHub
parent 1e90ce4c1d
commit 0555d0f400
8 changed files with 70 additions and 13 deletions
@@ -1,5 +1,3 @@
import Empty from '../state/Empty';
import { useSchedule } from './ScheduleContext';
import ScheduleItem from './ScheduleItem';
@@ -13,8 +11,9 @@ interface ScheduleProps {
export default function Schedule({ isProduction, className }: ScheduleProps) {
const { paginatedEvents, selectedEventId, isBackstage, scheduleType } = useSchedule();
// TODO: design a nice placeholder for empty schedules
if (paginatedEvents?.length < 1) {
return <Empty text='No events to show' />;
return null;
}
let selectedState: 'past' | 'now' | 'future' = 'past';
@@ -1,7 +1,9 @@
import { createContext, PropsWithChildren, useContext, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { OntimeEvent } from 'ontime-types';
import { useInterval } from '../../hooks/useInterval';
import { isStringBoolean } from '../../utils/viewUtils';
interface ScheduleContextState {
events: OntimeEvent[];
@@ -32,12 +34,25 @@ export const ScheduleProvider = ({
time = 10,
}: PropsWithChildren<ScheduleProviderProps>) => {
const [visiblePage, setVisiblePage] = useState(0);
const [searchParams] = useSearchParams();
const numPages = Math.ceil(events.length / eventsPerPage);
// look for overrides from views
const hidePast = isStringBoolean(searchParams.get('hidePast'));
const stopCycle = isStringBoolean(searchParams.get('stopCycle'));
let selectedEventIndex = events.findIndex((event) => event.id === selectedEventId);
const viewEvents = [...events];
if (hidePast) {
// we want to show the event after the next
viewEvents.splice(0, selectedEventIndex + 2);
selectedEventIndex = 0;
}
const numPages = Math.ceil(viewEvents.length / eventsPerPage);
const eventStart = eventsPerPage * visiblePage;
const eventEnd = eventsPerPage * (visiblePage + 1);
const paginatedEvents = events.slice(eventStart, eventEnd);
const selectedEventIndex = events.findIndex((event) => event.id === selectedEventId);
const paginatedEvents = viewEvents.slice(eventStart, eventEnd);
const resolveScheduleType = () => {
if (selectedEventIndex >= eventStart && selectedEventIndex < eventEnd) {
@@ -52,7 +67,9 @@ export const ScheduleProvider = ({
// every SCROLL_TIME go to the next array
useInterval(() => {
if (events.length > eventsPerPage) {
if (stopCycle) {
setVisiblePage(0);
} else if (events.length > eventsPerPage) {
const next = (visiblePage + 1) % numPages;
setVisiblePage(next);
}
@@ -194,6 +194,37 @@ export const LOWER_THIRDS_OPTIONS: ParamField[] = [
},
];
export const BACKSTAGE_OPTIONS: ParamField[] = [
TIME_FORMAT_OPTION,
{
id: 'hidePast',
title: 'Hide past events',
description: 'Scheduler will only show upcoming events',
type: 'boolean',
},
{
id: 'stopCycle',
title: 'Stop cycling through event pages',
description: 'Schedule will not auto-cycle through events',
type: 'boolean',
},
];
export const PUBLIC_OPTIONS: ParamField[] = [
TIME_FORMAT_OPTION,
{
id: 'hidePast',
title: 'Hide past events',
description: 'Scheduler will only show upcoming events',
type: 'boolean',
},
{
id: 'stopCycle',
title: 'Stop cycling through event pages',
description: 'Schedule will not auto-cycle through events',
type: 'boolean',
},
];
export const STUDIO_CLOCK_OPTIONS: ParamField[] = [
TIME_FORMAT_OPTION,
{
@@ -1,5 +1,6 @@
/* eslint-disable react/display-name */
import { ComponentType, useMemo } from 'react';
import { SupportedEvent } from 'ontime-types';
import { useStore } from 'zustand';
import useProjectData from '../../common/hooks-query/useProjectData';
@@ -20,7 +21,7 @@ const withData = <P extends object>(Component: ComponentType<P>) => {
const publicEvents = useMemo(() => {
if (Array.isArray(rundownData)) {
return rundownData.filter((e) => e.type === 'event' && e.title && e.isPublic);
return rundownData.filter((e) => e.type === SupportedEvent.Event && e.title && e.isPublic);
}
return [];
}, [rundownData]);
@@ -99,7 +99,11 @@
margin-top: 2em;
padding-top: 1em;
display: flex;
gap: 7.5em;
}
.timer-gap {
flex: 1;
max-width: 7.5em;
}
.aux-timers {
@@ -11,7 +11,7 @@ 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 { TIME_FORMAT_OPTION } from '../../../common/components/view-params-editor/constants';
import { BACKSTAGE_OPTIONS } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
@@ -92,7 +92,7 @@ export default function Backstage(props: BackstageProps) {
return (
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
<NavigationMenu />
<ViewParamsEditor paramFields={[TIME_FORMAT_OPTION]} />
<ViewParamsEditor paramFields={BACKSTAGE_OPTIONS} />
<div className='project-header'>
{general.title}
<div className='clock-container'>
@@ -130,10 +130,12 @@ export default function Backstage(props: BackstageProps) {
<div className='aux-timers__label'>{getLocalizedString('common.started_at')}</div>
<div className='aux-timers__value'>{startedAt}</div>
</div>
<div className='timer-gap' />
<div className='aux-timers'>
<div className='aux-timers__label'>{getLocalizedString('common.expected_finish')}</div>
<div className='aux-timers__value'>{expectedFinish}</div>
</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>
@@ -9,7 +9,7 @@ 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 { TIME_FORMAT_OPTION } from '../../../common/components/view-params-editor/constants';
import { PUBLIC_OPTIONS } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
@@ -58,7 +58,7 @@ export default function Public(props: BackstageProps) {
return (
<div className={`public-screen ${isMirrored ? 'mirror' : ''}`} data-testid='public-view'>
<NavigationMenu />
<ViewParamsEditor paramFields={[TIME_FORMAT_OPTION]} />
<ViewParamsEditor paramFields={PUBLIC_OPTIONS} />
<div className='project-header'>
{general.title}
<div className='clock-container'>
@@ -183,6 +183,8 @@ export class EventLoader {
this.loaded.nextEventId = nextEvent?.id || null;
this.loaded.nextPublicEventId = nextPublicEvent?.id || null;
this._loadEvent();
return { currentEvent, nextEvent, timeToNext };
}
@@ -281,6 +283,7 @@ export class EventLoader {
// check if current is also public
if (event.isPublic) {
this.publicEventNow = event;
this.loaded.selectedPublicEventId = event.id;
} else {
// assume there is no public event
this.publicEventNow = null;