feat: option to select all events for countdown

This commit is contained in:
arc-alex
2025-09-16 16:59:29 +02:00
parent 9288566a7f
commit 6397d33eaa
4 changed files with 49 additions and 17 deletions
@@ -1,6 +1,6 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { IoAdd } from 'react-icons/io5'; import { IoAdd } from 'react-icons/io5';
import { EntryId, isOntimeEvent, isPlayableEvent, OntimeEvent, OntimeView } from 'ontime-types'; import { isOntimeEvent, isPlayableEvent, OntimeEvent, OntimeView } from 'ontime-types';
import Button from '../../common/components/buttons/Button'; import Button from '../../common/components/buttons/Button';
import Empty from '../../common/components/state/Empty'; import Empty from '../../common/components/state/Empty';
@@ -16,7 +16,7 @@ import { useTranslation } from '../../translation/TranslationProvider';
import Loader from '../common/loader/Loader'; import Loader from '../common/loader/Loader';
import { getCountdownOptions, useCountdownOptions } from './countdown.options'; import { getCountdownOptions, useCountdownOptions } from './countdown.options';
import { getOrderedSubscriptions } from './countdown.utils'; import { CountdownSubscription, getOrderedSubscriptions } from './countdown.utils';
import CountdownSelect from './CountdownSelect'; import CountdownSelect from './CountdownSelect';
import CountdownSubscriptions from './CountdownSubscriptions'; import CountdownSubscriptions from './CountdownSubscriptions';
import SingleEventCountdown from './SingleEventCountdown'; import SingleEventCountdown from './SingleEventCountdown';
@@ -59,6 +59,8 @@ function Countdown({ customFields, rundownData, projectData, isMirrored, setting
[defaultFormat, customFields, subscriptions], [defaultFormat, customFields, subscriptions],
); );
console.log(subscriptions)
return ( return (
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'> <div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
<ViewParamsEditor target={OntimeView.Countdown} viewOptions={countdownOptions} /> <ViewParamsEditor target={OntimeView.Countdown} viewOptions={countdownOptions} />
@@ -87,7 +89,7 @@ function Countdown({ customFields, rundownData, projectData, isMirrored, setting
interface CountdownContentsProps { interface CountdownContentsProps {
playableEvents: ExtendedEntry<OntimeEvent>[]; playableEvents: ExtendedEntry<OntimeEvent>[];
subscriptions: EntryId[]; subscriptions: CountdownSubscription;
goToEditMode: () => void; goToEditMode: () => void;
} }
@@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { IoArrowBack, IoClose, IoSaveOutline } from 'react-icons/io5'; import { IoArrowBack, IoClose, IoSaveOutline, IoAlbumsOutline } from 'react-icons/io5';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { EntryId, OntimeEvent } from 'ontime-types'; import { EntryId, OntimeEvent } from 'ontime-types';
@@ -7,18 +7,19 @@ import Button from '../../common/components/buttons/Button';
import { cx } from '../../common/utils/styleUtils'; import { cx } from '../../common/utils/styleUtils';
import ClockTime from '../../features/viewers/common/clock-time/ClockTime'; import ClockTime from '../../features/viewers/common/clock-time/ClockTime';
import { makeSubscriptionsUrl } from './countdown.utils'; import { CountdownSubscription, makeSubscriptionsUrl } from './countdown.utils';
import './Countdown.scss'; import './Countdown.scss';
interface CountdownSelectProps { interface CountdownSelectProps {
events: OntimeEvent[]; events: OntimeEvent[];
subscriptions: EntryId[]; subscriptions: CountdownSubscription;
disableEdit: () => void; disableEdit: () => void;
} }
export default function CountdownSelect({ events, subscriptions, disableEdit }: CountdownSelectProps) { export default function CountdownSelect({ events, subscriptions, disableEdit }: CountdownSelectProps) {
const [selected, setSelected] = useState<EntryId[]>(subscriptions); const maybeAllSubscriptions: EntryId[] = subscriptions === 'all' ? events.map((event) => event.id) : subscriptions;
const [selected, setSelected] = useState<EntryId[]>(maybeAllSubscriptions);
const navigate = useNavigate(); const navigate = useNavigate();
/** /**
@@ -47,6 +48,18 @@ export default function CountdownSelect({ events, subscriptions, disableEdit }:
navigate(url.search.toString()); navigate(url.search.toString());
}; };
/**
* Creates a URL with all
* and navigates to it
*/
const applyAll = () => {
// we remove events that no longer exist to avoid stale subscriptions
const url = makeSubscriptionsUrl(window.location.href, 'all');
disableEdit();
setSelected([]);
navigate(url.search.toString());
};
// make a copy of the selected array for quick lookup // make a copy of the selected array for quick lookup
const selectedIds = new Set(selected); const selectedIds = new Set(selected);
@@ -86,6 +99,10 @@ export default function CountdownSelect({ events, subscriptions, disableEdit }:
<Button variant='subtle' size='xlarge' onClick={disableEdit}> <Button variant='subtle' size='xlarge' onClick={disableEdit}>
<IoArrowBack /> Go back <IoArrowBack /> Go back
</Button> </Button>
<Button variant='subtle' size='xlarge' onClick={applyAll}>
{/* TODO: icon ??? */}
<IoAlbumsOutline /> Use All
</Button>
<Button variant='subtle' size='xlarge' onClick={() => setSelected([])} disabled={selected.length === 0}> <Button variant='subtle' size='xlarge' onClick={() => setSelected([])} disabled={selected.length === 0}>
<IoClose /> Clear <IoClose /> Clear
</Button> </Button>
@@ -8,11 +8,12 @@ import { ViewOption } from '../../common/components/view-params-editor/viewParam
import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils'; import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils';
import { PresetContext } from '../../common/context/PresetContext'; import { PresetContext } from '../../common/context/PresetContext';
import { isStringBoolean } from '../../features/viewers/common/viewUtils'; import { isStringBoolean } from '../../features/viewers/common/viewUtils';
import { CountdownSubscription } from './countdown.utils';
export const getCountdownOptions = ( export const getCountdownOptions = (
timeFormat: string, timeFormat: string,
customFields: CustomFields, customFields: CustomFields,
persistedSubscriptions: EntryId[], persistedSubscriptions: CountdownSubscription,
): ViewOption[] => { ): ViewOption[] => {
const secondaryOptions = makeOptionsFromCustomFields(customFields, [ const secondaryOptions = makeOptionsFromCustomFields(customFields, [
{ value: 'none', label: 'None' }, { value: 'none', label: 'None' },
@@ -55,7 +56,7 @@ export const getCountdownOptions = (
id: 'sub', id: 'sub',
title: 'Event subscription', title: 'Event subscription',
description: 'The events to follow', description: 'The events to follow',
values: persistedSubscriptions, values: persistedSubscriptions === 'all' ? ['all'] : persistedSubscriptions,
type: 'persist', type: 'persist',
}, },
], ],
@@ -64,7 +65,7 @@ export const getCountdownOptions = (
}; };
type CountdownOptions = { type CountdownOptions = {
subscriptions: EntryId[]; subscriptions: CountdownSubscription;
secondarySource: keyof OntimeEvent | null; secondarySource: keyof OntimeEvent | null;
showExpected: boolean; showExpected: boolean;
}; };
@@ -85,8 +86,10 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL
return searchParams.getAll(key) as EntryId[]; return searchParams.getAll(key) as EntryId[];
}; };
const subscriptions = getArrayValues('sub');
return { return {
subscriptions: getArrayValues('sub'), subscriptions: subscriptions.at(0) === 'all' ? 'all' : subscriptions,
secondarySource: getValue('secondary-src') as keyof OntimeEvent | null, secondarySource: getValue('secondary-src') as keyof OntimeEvent | null,
showExpected: isStringBoolean(getValue('showExpected')), showExpected: isStringBoolean(getValue('showExpected')),
}; };
@@ -14,6 +14,8 @@ export function sanitiseTitle(title: string | null) {
return title ?? '{no title}'; return title ?? '{no title}';
} }
export type CountdownSubscription = EntryId[] | 'all';
export const preferredFormat12 = 'h:mm a'; export const preferredFormat12 = 'h:mm a';
export const preferredFormat24 = 'HH:mm'; export const preferredFormat24 = 'HH:mm';
@@ -120,7 +122,7 @@ export function useSubscriptionDisplayData(
/** /**
* Adds a set of subscriptions to the URL parameters * Adds a set of subscriptions to the URL parameters
*/ */
export function makeSubscriptionsUrl(urlRef: string, subscriptions: EntryId[]) { export function makeSubscriptionsUrl(urlRef: string, subscriptions: CountdownSubscription) {
const url = new URL(urlRef); const url = new URL(urlRef);
const newParams = new URLSearchParams(); const newParams = new URLSearchParams();
@@ -131,10 +133,14 @@ export function makeSubscriptionsUrl(urlRef: string, subscriptions: EntryId[]) {
} }
} }
// add new subscriptions if (subscriptions === 'all') {
subscriptions.forEach((id) => { newParams.append('sub', 'all');
newParams.append('sub', id); } else {
}); // add new subscriptions
subscriptions.forEach((id) => {
newParams.append('sub', id);
});
}
url.search = newParams.toString(); url.search = newParams.toString();
@@ -146,7 +152,11 @@ export function makeSubscriptionsUrl(urlRef: string, subscriptions: EntryId[]) {
* Since the original array is already ordered, we simply filter out the events * Since the original array is already ordered, we simply filter out the events
* which are not in the subscriptions list. * which are not in the subscriptions list.
*/ */
export function getOrderedSubscriptions<T extends OntimeEntry>(subscriptions: EntryId[], playableEvents: T[]): T[] { export function getOrderedSubscriptions<T extends OntimeEntry>(
subscriptions: CountdownSubscription,
playableEvents: T[],
): T[] {
if (subscriptions === 'all') return playableEvents;
return playableEvents.filter((event) => subscriptions.includes(event.id)); return playableEvents.filter((event) => subscriptions.includes(event.id));
} }