diff --git a/apps/client/src/common/components/schedule/Schedule.tsx b/apps/client/src/common/components/schedule/Schedule.tsx index 9da050d18..a7e394a5f 100644 --- a/apps/client/src/common/components/schedule/Schedule.tsx +++ b/apps/client/src/common/components/schedule/Schedule.tsx @@ -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 ; + return null; } let selectedState: 'past' | 'now' | 'future' = 'past'; diff --git a/apps/client/src/common/components/schedule/ScheduleContext.tsx b/apps/client/src/common/components/schedule/ScheduleContext.tsx index a6c8fc583..0f4d84d2e 100644 --- a/apps/client/src/common/components/schedule/ScheduleContext.tsx +++ b/apps/client/src/common/components/schedule/ScheduleContext.tsx @@ -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) => { 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); } diff --git a/apps/client/src/common/components/view-params-editor/constants.ts b/apps/client/src/common/components/view-params-editor/constants.ts index 636739439..da9546ace 100644 --- a/apps/client/src/common/components/view-params-editor/constants.ts +++ b/apps/client/src/common/components/view-params-editor/constants.ts @@ -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, { diff --git a/apps/client/src/features/viewers/ViewWrapper.tsx b/apps/client/src/features/viewers/ViewWrapper.tsx index 94d39d4fa..01bbe9b51 100644 --- a/apps/client/src/features/viewers/ViewWrapper.tsx +++ b/apps/client/src/features/viewers/ViewWrapper.tsx @@ -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 =

(Component: ComponentType

) => { 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]); diff --git a/apps/client/src/features/viewers/backstage/Backstage.scss b/apps/client/src/features/viewers/backstage/Backstage.scss index ffda8ab1a..4198a4337 100644 --- a/apps/client/src/features/viewers/backstage/Backstage.scss +++ b/apps/client/src/features/viewers/backstage/Backstage.scss @@ -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 { diff --git a/apps/client/src/features/viewers/backstage/Backstage.tsx b/apps/client/src/features/viewers/backstage/Backstage.tsx index 72360b924..94fb848b1 100644 --- a/apps/client/src/features/viewers/backstage/Backstage.tsx +++ b/apps/client/src/features/viewers/backstage/Backstage.tsx @@ -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 (

- +
{general.title}
@@ -130,10 +130,12 @@ export default function Backstage(props: BackstageProps) {
{getLocalizedString('common.started_at')}
{startedAt}
+
{getLocalizedString('common.expected_finish')}
{expectedFinish}
+
{getLocalizedString('common.stage_timer')}
{stageTimer}
diff --git a/apps/client/src/features/viewers/public/Public.tsx b/apps/client/src/features/viewers/public/Public.tsx index eb2012523..78d97a4f4 100644 --- a/apps/client/src/features/viewers/public/Public.tsx +++ b/apps/client/src/features/viewers/public/Public.tsx @@ -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 (
- +
{general.title}
diff --git a/apps/server/src/classes/event-loader/EventLoader.ts b/apps/server/src/classes/event-loader/EventLoader.ts index 09c7fea2d..a371f3ff5 100644 --- a/apps/server/src/classes/event-loader/EventLoader.ts +++ b/apps/server/src/classes/event-loader/EventLoader.ts @@ -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;