Feat: unify view search params (#1947)

* feat: main src in backstage view

* feat: main src in timeline view

* feat: main src in countdown view

* feat: main src in studio view

* feat: hide past events in countdown view

* notes can not be set as main src

* feat: show group title as secondary src

* chore: spelling

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>

---------

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
This commit is contained in:
Alex Christoffer Rasmussen
2026-01-27 20:42:07 +01:00
committed by GitHub
parent ec1890c275
commit 026e24bf84
28 changed files with 224 additions and 64 deletions
+16 -4
View File
@@ -1,6 +1,6 @@
import { useMemo, useState } from 'react';
import { IoAdd } from 'react-icons/io5';
import { EntryId, isOntimeEvent, isPlayableEvent, OntimeEvent, OntimeView } from 'ontime-types';
import { EntryId, isOntimeEvent, isPlayableEvent, OntimeEvent, OntimeView, PlayableEvent } from 'ontime-types';
import Button from '../../common/components/buttons/Button';
import Empty from '../../common/components/state/Empty';
@@ -47,7 +47,9 @@ function Countdown({ customFields, rundownData, projectData, isMirrored, setting
const [editMode, setEditMode] = useState(false);
// gather rundown data
const playableEvents = rundownData.filter((entry) => isOntimeEvent(entry) && isPlayableEvent(entry));
const playableEvents = rundownData.filter((entry): entry is ExtendedEntry<PlayableEvent> => {
return isOntimeEvent(entry) && isPlayableEvent(entry);
});
// gather presentation data
const hasEvents = playableEvents.length > 0;
@@ -93,6 +95,7 @@ interface CountdownContentsProps {
function CountdownContents({ playableEvents, subscriptions, goToEditMode }: CountdownContentsProps) {
const { getLocalizedString } = useTranslation();
const { hidePast } = useCountdownOptions();
if (subscriptions.length === 0) {
return (
@@ -106,6 +109,7 @@ function CountdownContents({ playableEvents, subscriptions, goToEditMode }: Coun
}
const subscribedEvents = getOrderedSubscriptions(subscriptions, playableEvents);
const eventsToShow = !hidePast ? subscribedEvents : subscribedEvents.filter((event) => !event.isPast);
if (subscribedEvents.length === 0) {
return (
@@ -118,13 +122,21 @@ function CountdownContents({ playableEvents, subscriptions, goToEditMode }: Coun
);
}
if (subscribedEvents.length === 1) {
if (subscribedEvents.length === 1 && eventsToShow.length === 1) {
const event = subscribedEvents.at(0);
if (!event) return null;
return <SingleEventCountdown subscribedEvent={event} goToEditMode={goToEditMode} />;
}
return <CountdownSubscriptions subscribedEvents={subscribedEvents} goToEditMode={goToEditMode} />;
if (eventsToShow.length === 0) {
return (
<div className='empty-container'>
<Empty text={getLocalizedString('countdown.all_have_finished')} className='empty-container' />
</div>
);
}
return <CountdownSubscriptions subscribedEvents={eventsToShow} goToEditMode={goToEditMode} />;
}
function CountdownClock() {
@@ -36,7 +36,7 @@ interface CountdownSubscriptionsProps {
}
export default function CountdownSubscriptions({ subscribedEvents, goToEditMode }: CountdownSubscriptionsProps) {
const { secondarySource, showExpected } = useCountdownOptions();
const { mainSource, secondarySource, showExpected } = useCountdownOptions();
const { playback } = usePlayback();
const { selectedEventId } = useSelectedEventId();
const showFab = useFadeOutOnInactivity(true);
@@ -103,7 +103,7 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode
const isLive = getIsLive(event.id, selectedEventId, playback);
const isArmed = !isLive && event.id === selectedEventId;
const countdownEvent = extendEventData(event, currentDay, actualStart, plannedStart, offset, mode, reportData);
const title = event.title.length ? event.title : ' '; // insert utf-8 empty space to avoid the line collapsing
const displayTitle = getPropertyValue(event, mainSource ?? 'title');
return (
<div
key={event.id}
@@ -114,7 +114,7 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode
<div className='sub__binder' style={{ '--user-color': event.colour }} />
<ScheduleTime event={countdownEvent} showExpected={showExpected} />
<SubscriptionStatus event={countdownEvent} />
<div className={cx(['sub__title', !event.title && 'subdued'])}>{title}</div>
<div className={cx(['sub__title', !displayTitle && 'subdued'])}>{displayTitle}</div>
{secondaryData && <div className='sub__secondary'>{secondaryData}</div>}
</div>
);
@@ -23,7 +23,7 @@ interface SingleEventCountdownProps {
}
export default function SingleEventCountdown({ subscribedEvent, goToEditMode }: SingleEventCountdownProps) {
const { secondarySource, showExpected } = useCountdownOptions();
const { mainSource, secondarySource, showExpected } = useCountdownOptions();
const showFab = useFadeOutOnInactivity(true);
const { data: reportData } = useReport();
@@ -41,7 +41,8 @@ export default function SingleEventCountdown({ subscribedEvent, goToEditMode }:
const { endedAt } = reportData[subscribedEvent.id] ?? { endedAt: null };
const countdownEvent = { ...subscribedEvent, expectedStart, endedAt };
const title = subscribedEvent.title.length ? subscribedEvent.title : ' '; // insert utf-8 empty space to avoid the line collapsing
const titleTmp = getPropertyValue(subscribedEvent, mainSource ?? 'title');
const title = titleTmp?.length ? titleTmp : ' '; // insert utf-8 empty space to avoid the line collapsing;
const secondaryData = getPropertyValue(subscribedEvent, secondarySource);
return (
@@ -14,6 +14,7 @@ export const getCountdownOptions = (
customFields: CustomFields,
persistedSubscriptions: EntryId[],
): ViewOption[] => {
const mainOptions = makeOptionsFromCustomFields(customFields, [{ value: 'title', label: 'Title' }]);
const secondaryOptions = makeOptionsFromCustomFields(customFields, [
{ value: 'none', label: 'None' },
{ value: 'note', label: 'Note' },
@@ -25,6 +26,14 @@ export const getCountdownOptions = (
title: OptionTitle.DataSources,
collapsible: true,
options: [
{
id: 'main',
title: 'Main text',
description: 'Select the data source for the main text',
type: 'option',
values: mainOptions,
defaultValue: 'title',
},
{
id: 'secondary-src',
title: 'Event secondary text',
@@ -48,6 +57,19 @@ export const getCountdownOptions = (
},
],
},
{
title: OptionTitle.ElementVisibility,
collapsible: true,
options: [
{
id: 'hidePast',
title: 'Hide Past Events',
description: 'Whether to hide events that have passed',
type: 'boolean',
defaultValue: false,
},
],
},
{
title: OptionTitle.Hidden,
options: [
@@ -65,8 +87,10 @@ export const getCountdownOptions = (
type CountdownOptions = {
subscriptions: EntryId[];
mainSource: keyof OntimeEvent | null;
secondarySource: keyof OntimeEvent | null;
showExpected: boolean;
hidePast: boolean;
};
/**
@@ -87,8 +111,10 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL
return {
subscriptions: getArrayValues('sub'),
mainSource: getValue('main') as keyof OntimeEvent | null,
secondarySource: getValue('secondary-src') as keyof OntimeEvent | null,
showExpected: isStringBoolean(getValue('showExpected')),
hidePast: isStringBoolean(getValue('hidePast')),
};
}