mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-24 00:19:21 +00:00
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:
committed by
GitHub
parent
ec1890c275
commit
026e24bf84
@@ -16,6 +16,7 @@
|
|||||||
.title-card__title {
|
.title-card__title {
|
||||||
color: var(--color-override, $viewer-color);
|
color: var(--color-override, $viewer-color);
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
|
min-height: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-card__placeholder {
|
.title-card__placeholder {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const langDe: TranslationObject = {
|
|||||||
'countdown.to_start': 'Zeit bis zum Start',
|
'countdown.to_start': 'Zeit bis zum Start',
|
||||||
'countdown.waiting': 'Warten auf den Veranstaltungsbeginn',
|
'countdown.waiting': 'Warten auf den Veranstaltungsbeginn',
|
||||||
'countdown.overtime': 'überfällig',
|
'countdown.overtime': 'überfällig',
|
||||||
|
'countdown.all_have_finished': 'Alle ausgewählten Veranstaltungen sind beendet',
|
||||||
'timeline.live': 'live',
|
'timeline.live': 'live',
|
||||||
'timeline.done': 'Beendet',
|
'timeline.done': 'Beendet',
|
||||||
'timeline.due': 'fällig',
|
'timeline.due': 'fällig',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const langEs: TranslationObject = {
|
|||||||
'countdown.to_start': 'Tiempo para comenzar',
|
'countdown.to_start': 'Tiempo para comenzar',
|
||||||
'countdown.waiting': 'Esperando el inicio del evento',
|
'countdown.waiting': 'Esperando el inicio del evento',
|
||||||
'countdown.overtime': 'en tiempo extra',
|
'countdown.overtime': 'en tiempo extra',
|
||||||
|
'countdown.all_have_finished': 'Todos los eventos seleccionados han terminado',
|
||||||
'timeline.live': 'live',
|
'timeline.live': 'live',
|
||||||
'timeline.done': 'Terminado',
|
'timeline.done': 'Terminado',
|
||||||
'timeline.due': 'pendiente',
|
'timeline.due': 'pendiente',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const langFr: TranslationObject = {
|
|||||||
'countdown.to_start': 'Évènement commence dans',
|
'countdown.to_start': 'Évènement commence dans',
|
||||||
'countdown.waiting': 'En attente du début de l’évènement',
|
'countdown.waiting': 'En attente du début de l’évènement',
|
||||||
'countdown.overtime': 'en dépassement',
|
'countdown.overtime': 'en dépassement',
|
||||||
|
'countdown.all_have_finished': 'Tous les évènements sélectionnés ont terminé',
|
||||||
'timeline.live': 'live',
|
'timeline.live': 'live',
|
||||||
'timeline.done': 'Terminé',
|
'timeline.done': 'Terminé',
|
||||||
'timeline.due': 'dû',
|
'timeline.due': 'dû',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const langIt: TranslationObject = {
|
|||||||
'countdown.to_start': 'Tempo alla partenza',
|
'countdown.to_start': 'Tempo alla partenza',
|
||||||
'countdown.waiting': "In attesa dell'inizio dell'evento",
|
'countdown.waiting': "In attesa dell'inizio dell'evento",
|
||||||
'countdown.overtime': 'in ritardo',
|
'countdown.overtime': 'in ritardo',
|
||||||
|
'countdown.all_have_finished': 'Tutti gli eventi selezionati sono finiti',
|
||||||
'timeline.live': 'live',
|
'timeline.live': 'live',
|
||||||
'timeline.done': 'Terminato',
|
'timeline.done': 'Terminato',
|
||||||
'timeline.due': 'previsto',
|
'timeline.due': 'previsto',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const langPt: TranslationObject = {
|
|||||||
'countdown.to_start': 'Tempo para iniciar',
|
'countdown.to_start': 'Tempo para iniciar',
|
||||||
'countdown.waiting': 'Aguardando o início do evento',
|
'countdown.waiting': 'Aguardando o início do evento',
|
||||||
'countdown.overtime': 'em tempo extra',
|
'countdown.overtime': 'em tempo extra',
|
||||||
|
'countdown.all_have_finished': 'Todos os eventos selecionados terminaram',
|
||||||
'timeline.live': 'live',
|
'timeline.live': 'live',
|
||||||
'timeline.done': 'Concluído',
|
'timeline.done': 'Concluído',
|
||||||
'timeline.due': 'Pendente',
|
'timeline.due': 'Pendente',
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export default function BackstageLoader() {
|
|||||||
|
|
||||||
function Backstage({ events, customFields, projectData, isMirrored, settings }: BackstageData) {
|
function Backstage({ events, customFields, projectData, isMirrored, settings }: BackstageData) {
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const { secondarySource, extraInfo } = useBackstageOptions();
|
const { mainSource, secondarySource, extraInfo } = useBackstageOptions();
|
||||||
const { eventNext, eventNow, rundown, selectedEventId, time } = useBackstageSocket();
|
const { eventNext, eventNow, rundown, selectedEventId, time } = useBackstageSocket();
|
||||||
const [blinkClass, setBlinkClass] = useState(false);
|
const [blinkClass, setBlinkClass] = useState(false);
|
||||||
const { height: screenHeight } = useViewportSize();
|
const { height: screenHeight } = useViewportSize();
|
||||||
@@ -64,7 +64,7 @@ function Backstage({ events, customFields, projectData, isMirrored, settings }:
|
|||||||
const { showNow, nowMain, nowSecondary, showNext, nextMain, nextSecondary } = getCardData(
|
const { showNow, nowMain, nowSecondary, showNext, nextMain, nextSecondary } = getCardData(
|
||||||
eventNow,
|
eventNow,
|
||||||
eventNext,
|
eventNext,
|
||||||
'title',
|
mainSource,
|
||||||
secondarySource,
|
secondarySource,
|
||||||
time.playback,
|
time.playback,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const getBackstageOptions = (
|
|||||||
{ value: 'note', label: 'Note' },
|
{ value: 'note', label: 'Note' },
|
||||||
]);
|
]);
|
||||||
const projectDataOptions = makeProjectDataOptions(projectData, [{ value: 'none', label: 'None' }]);
|
const projectDataOptions = makeProjectDataOptions(projectData, [{ value: 'none', label: 'None' }]);
|
||||||
|
const mainOptions = makeOptionsFromCustomFields(customFields, [{ value: 'title', label: 'Title' }]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
|
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
|
||||||
@@ -30,6 +31,14 @@ export const getBackstageOptions = (
|
|||||||
title: OptionTitle.DataSources,
|
title: OptionTitle.DataSources,
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
options: [
|
options: [
|
||||||
|
{
|
||||||
|
id: 'main',
|
||||||
|
title: 'Main text',
|
||||||
|
description: 'Select the data source for the main text',
|
||||||
|
type: 'option',
|
||||||
|
values: mainOptions,
|
||||||
|
defaultValue: 'title',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'secondary-src',
|
id: 'secondary-src',
|
||||||
title: 'Event secondary text',
|
title: 'Event secondary text',
|
||||||
@@ -59,6 +68,7 @@ export const getBackstageOptions = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
type BackstageOptions = {
|
type BackstageOptions = {
|
||||||
|
mainSource: keyof OntimeEvent | null;
|
||||||
secondarySource: keyof OntimeEvent | null;
|
secondarySource: keyof OntimeEvent | null;
|
||||||
extraInfo: string | null;
|
extraInfo: string | null;
|
||||||
};
|
};
|
||||||
@@ -72,6 +82,7 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL
|
|||||||
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
mainSource: getValue('main') as keyof OntimeEvent | null,
|
||||||
secondarySource: getValue('secondary-src') as keyof OntimeEvent | null,
|
secondarySource: getValue('secondary-src') as keyof OntimeEvent | null,
|
||||||
extraInfo: getValue('extra-info'),
|
extraInfo: getValue('extra-info'),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { MaybeNumber, MaybeString, OntimeEvent, TimerState, TimerType } from 'ontime-types';
|
import {
|
||||||
|
MaybeNumber,
|
||||||
|
MaybeString,
|
||||||
|
OntimeEvent,
|
||||||
|
OntimeGroup,
|
||||||
|
RundownEntries,
|
||||||
|
TimerState,
|
||||||
|
TimerType,
|
||||||
|
} from 'ontime-types';
|
||||||
import { MILLIS_PER_MINUTE, MILLIS_PER_SECOND, millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils';
|
import { MILLIS_PER_MINUTE, MILLIS_PER_SECOND, millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils';
|
||||||
|
|
||||||
import { timerPlaceholder, timerPlaceholderMin } from '../../common/utils/styleUtils';
|
import { timerPlaceholder, timerPlaceholderMin } from '../../common/utils/styleUtils';
|
||||||
@@ -66,12 +74,22 @@ export function makeColourString(hex: string | null): string | undefined {
|
|||||||
/**
|
/**
|
||||||
* Retrieves a dynamic property from an event
|
* Retrieves a dynamic property from an event
|
||||||
* Considers custom fields
|
* Considers custom fields
|
||||||
|
* if rundown entries are provided it can also find the parent title
|
||||||
*/
|
*/
|
||||||
export function getPropertyValue(event: OntimeEvent | null, property: MaybeString): string | undefined {
|
export function getPropertyValue(
|
||||||
|
event: OntimeEvent | null,
|
||||||
|
property: MaybeString,
|
||||||
|
entries?: RundownEntries,
|
||||||
|
): string | undefined {
|
||||||
if (!event || typeof property !== 'string' || property === 'none') {
|
if (!event || typeof property !== 'string' || property === 'none') {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (property === 'parent') {
|
||||||
|
if (!entries || !event.parent) return undefined;
|
||||||
|
return (entries[event.parent] as OntimeGroup)?.title;
|
||||||
|
}
|
||||||
|
|
||||||
if (property.startsWith('custom-')) {
|
if (property.startsWith('custom-')) {
|
||||||
const field = property.split('custom-')[1];
|
const field = property.split('custom-')[1];
|
||||||
return event.custom?.[field];
|
return event.custom?.[field];
|
||||||
|
|||||||
@@ -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 { EntryId, isOntimeEvent, isPlayableEvent, OntimeEvent, OntimeView, PlayableEvent } 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';
|
||||||
@@ -47,7 +47,9 @@ function Countdown({ customFields, rundownData, projectData, isMirrored, setting
|
|||||||
const [editMode, setEditMode] = useState(false);
|
const [editMode, setEditMode] = useState(false);
|
||||||
|
|
||||||
// gather rundown data
|
// 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
|
// gather presentation data
|
||||||
const hasEvents = playableEvents.length > 0;
|
const hasEvents = playableEvents.length > 0;
|
||||||
@@ -93,6 +95,7 @@ interface CountdownContentsProps {
|
|||||||
|
|
||||||
function CountdownContents({ playableEvents, subscriptions, goToEditMode }: CountdownContentsProps) {
|
function CountdownContents({ playableEvents, subscriptions, goToEditMode }: CountdownContentsProps) {
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
|
const { hidePast } = useCountdownOptions();
|
||||||
|
|
||||||
if (subscriptions.length === 0) {
|
if (subscriptions.length === 0) {
|
||||||
return (
|
return (
|
||||||
@@ -106,6 +109,7 @@ function CountdownContents({ playableEvents, subscriptions, goToEditMode }: Coun
|
|||||||
}
|
}
|
||||||
|
|
||||||
const subscribedEvents = getOrderedSubscriptions(subscriptions, playableEvents);
|
const subscribedEvents = getOrderedSubscriptions(subscriptions, playableEvents);
|
||||||
|
const eventsToShow = !hidePast ? subscribedEvents : subscribedEvents.filter((event) => !event.isPast);
|
||||||
|
|
||||||
if (subscribedEvents.length === 0) {
|
if (subscribedEvents.length === 0) {
|
||||||
return (
|
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);
|
const event = subscribedEvents.at(0);
|
||||||
if (!event) return null;
|
if (!event) return null;
|
||||||
return <SingleEventCountdown subscribedEvent={event} goToEditMode={goToEditMode} />;
|
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() {
|
function CountdownClock() {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ interface CountdownSubscriptionsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CountdownSubscriptions({ subscribedEvents, goToEditMode }: CountdownSubscriptionsProps) {
|
export default function CountdownSubscriptions({ subscribedEvents, goToEditMode }: CountdownSubscriptionsProps) {
|
||||||
const { secondarySource, showExpected } = useCountdownOptions();
|
const { mainSource, secondarySource, showExpected } = useCountdownOptions();
|
||||||
const { playback } = usePlayback();
|
const { playback } = usePlayback();
|
||||||
const { selectedEventId } = useSelectedEventId();
|
const { selectedEventId } = useSelectedEventId();
|
||||||
const showFab = useFadeOutOnInactivity(true);
|
const showFab = useFadeOutOnInactivity(true);
|
||||||
@@ -103,7 +103,7 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode
|
|||||||
const isLive = getIsLive(event.id, selectedEventId, playback);
|
const isLive = getIsLive(event.id, selectedEventId, playback);
|
||||||
const isArmed = !isLive && event.id === selectedEventId;
|
const isArmed = !isLive && event.id === selectedEventId;
|
||||||
const countdownEvent = extendEventData(event, currentDay, actualStart, plannedStart, offset, mode, reportData);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
key={event.id}
|
key={event.id}
|
||||||
@@ -114,7 +114,7 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode
|
|||||||
<div className='sub__binder' style={{ '--user-color': event.colour }} />
|
<div className='sub__binder' style={{ '--user-color': event.colour }} />
|
||||||
<ScheduleTime event={countdownEvent} showExpected={showExpected} />
|
<ScheduleTime event={countdownEvent} showExpected={showExpected} />
|
||||||
<SubscriptionStatus event={countdownEvent} />
|
<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>}
|
{secondaryData && <div className='sub__secondary'>{secondaryData}</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ interface SingleEventCountdownProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function SingleEventCountdown({ subscribedEvent, goToEditMode }: SingleEventCountdownProps) {
|
export default function SingleEventCountdown({ subscribedEvent, goToEditMode }: SingleEventCountdownProps) {
|
||||||
const { secondarySource, showExpected } = useCountdownOptions();
|
const { mainSource, secondarySource, showExpected } = useCountdownOptions();
|
||||||
const showFab = useFadeOutOnInactivity(true);
|
const showFab = useFadeOutOnInactivity(true);
|
||||||
const { data: reportData } = useReport();
|
const { data: reportData } = useReport();
|
||||||
|
|
||||||
@@ -41,7 +41,8 @@ export default function SingleEventCountdown({ subscribedEvent, goToEditMode }:
|
|||||||
|
|
||||||
const { endedAt } = reportData[subscribedEvent.id] ?? { endedAt: null };
|
const { endedAt } = reportData[subscribedEvent.id] ?? { endedAt: null };
|
||||||
const countdownEvent = { ...subscribedEvent, expectedStart, endedAt };
|
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);
|
const secondaryData = getPropertyValue(subscribedEvent, secondarySource);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const getCountdownOptions = (
|
|||||||
customFields: CustomFields,
|
customFields: CustomFields,
|
||||||
persistedSubscriptions: EntryId[],
|
persistedSubscriptions: EntryId[],
|
||||||
): ViewOption[] => {
|
): ViewOption[] => {
|
||||||
|
const mainOptions = makeOptionsFromCustomFields(customFields, [{ value: 'title', label: 'Title' }]);
|
||||||
const secondaryOptions = makeOptionsFromCustomFields(customFields, [
|
const secondaryOptions = makeOptionsFromCustomFields(customFields, [
|
||||||
{ value: 'none', label: 'None' },
|
{ value: 'none', label: 'None' },
|
||||||
{ value: 'note', label: 'Note' },
|
{ value: 'note', label: 'Note' },
|
||||||
@@ -25,6 +26,14 @@ export const getCountdownOptions = (
|
|||||||
title: OptionTitle.DataSources,
|
title: OptionTitle.DataSources,
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
options: [
|
options: [
|
||||||
|
{
|
||||||
|
id: 'main',
|
||||||
|
title: 'Main text',
|
||||||
|
description: 'Select the data source for the main text',
|
||||||
|
type: 'option',
|
||||||
|
values: mainOptions,
|
||||||
|
defaultValue: 'title',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'secondary-src',
|
id: 'secondary-src',
|
||||||
title: 'Event secondary text',
|
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,
|
title: OptionTitle.Hidden,
|
||||||
options: [
|
options: [
|
||||||
@@ -65,8 +87,10 @@ export const getCountdownOptions = (
|
|||||||
|
|
||||||
type CountdownOptions = {
|
type CountdownOptions = {
|
||||||
subscriptions: EntryId[];
|
subscriptions: EntryId[];
|
||||||
|
mainSource: keyof OntimeEvent | null;
|
||||||
secondarySource: keyof OntimeEvent | null;
|
secondarySource: keyof OntimeEvent | null;
|
||||||
showExpected: boolean;
|
showExpected: boolean;
|
||||||
|
hidePast: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,8 +111,10 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
subscriptions: getArrayValues('sub'),
|
subscriptions: getArrayValues('sub'),
|
||||||
|
mainSource: getValue('main') as keyof OntimeEvent | null,
|
||||||
secondarySource: getValue('secondary-src') as keyof OntimeEvent | null,
|
secondarySource: getValue('secondary-src') as keyof OntimeEvent | null,
|
||||||
showExpected: isStringBoolean(getValue('showExpected')),
|
showExpected: isStringBoolean(getValue('showExpected')),
|
||||||
|
hidePast: isStringBoolean(getValue('hidePast')),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ export default function StudioLoader() {
|
|||||||
return <Studio {...data} />;
|
return <Studio {...data} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Studio({ projectData, isMirrored, settings, viewSettings }: StudioData) {
|
function Studio({ customFields, projectData, isMirrored, settings, viewSettings }: StudioData) {
|
||||||
const { hideCards } = useStudioOptions();
|
const { hideCards } = useStudioOptions();
|
||||||
|
|
||||||
// gather option data
|
// gather option data
|
||||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||||
const studioOptions = useMemo(() => getStudioOptions(defaultFormat), [defaultFormat]);
|
const studioOptions = useMemo(() => getStudioOptions(defaultFormat, customFields), [defaultFormat, customFields]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(['studio', isMirrored && 'mirror'])} data-testid='studio-view'>
|
<div className={cx(['studio', isMirrored && 'mirror'])} data-testid='studio-view'>
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { useAuxTimersTime, useStudioTimersSocket } from '../../common/hooks/useS
|
|||||||
import { getOffsetState } from '../../common/utils/offset';
|
import { getOffsetState } from '../../common/utils/offset';
|
||||||
import { cx } from '../../common/utils/styleUtils';
|
import { cx } from '../../common/utils/styleUtils';
|
||||||
import { useTranslation } from '../../translation/TranslationProvider';
|
import { useTranslation } from '../../translation/TranslationProvider';
|
||||||
|
import { getPropertyValue } from '../common/viewUtils';
|
||||||
import { getTimerColour } from '../utils/presentation.utils';
|
import { getTimerColour } from '../utils/presentation.utils';
|
||||||
|
|
||||||
|
import { useStudioOptions } from './studio.options';
|
||||||
import { getFormattedEventData, getFormattedScheduleTimes } from './studioTimers.utils';
|
import { getFormattedEventData, getFormattedScheduleTimes } from './studioTimers.utils';
|
||||||
|
|
||||||
import './StudioTimers.scss';
|
import './StudioTimers.scss';
|
||||||
@@ -17,6 +19,7 @@ interface StudioTimersProps {
|
|||||||
|
|
||||||
export default function StudioTimers({ viewSettings }: StudioTimersProps) {
|
export default function StudioTimers({ viewSettings }: StudioTimersProps) {
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
|
const { mainSource } = useStudioOptions();
|
||||||
const { eventNow, eventNext, message, time, offset, rundown, expectedRundownEnd } = useStudioTimersSocket();
|
const { eventNow, eventNext, message, time, offset, rundown, expectedRundownEnd } = useStudioTimersSocket();
|
||||||
|
|
||||||
const schedule = getFormattedScheduleTimes({
|
const schedule = getFormattedScheduleTimes({
|
||||||
@@ -24,8 +27,8 @@ export default function StudioTimers({ viewSettings }: StudioTimersProps) {
|
|||||||
actualStart: rundown.actualStart,
|
actualStart: rundown.actualStart,
|
||||||
expectedEnd: expectedRundownEnd,
|
expectedEnd: expectedRundownEnd,
|
||||||
});
|
});
|
||||||
const event = getFormattedEventData(eventNow, time);
|
const event = getFormattedEventData(eventNow, time, mainSource);
|
||||||
const eventNextTitle = eventNext?.title || '-';
|
const eventNextTitle = getPropertyValue(eventNext, mainSource ?? 'title') || '-';
|
||||||
const formattedTimerMessage = (message.timer.visible && message.timer.text) || '-';
|
const formattedTimerMessage = (message.timer.visible && message.timer.text) || '-';
|
||||||
const formattedSecondaryMessage = message.timer.secondarySource === 'secondary' ? message.secondary || '-' : '-';
|
const formattedSecondaryMessage = message.timer.secondarySource === 'secondary' ? message.secondary || '-' : '-';
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,51 @@
|
|||||||
import { use, useMemo } from 'react';
|
import { use, useMemo } from 'react';
|
||||||
import { useSearchParams } from 'react-router';
|
import { useSearchParams } from 'react-router';
|
||||||
|
import { CustomFields, OntimeEvent } from 'ontime-types';
|
||||||
|
|
||||||
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
|
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
|
||||||
import { OptionTitle } from '../../common/components/view-params-editor/constants';
|
import { OptionTitle } from '../../common/components/view-params-editor/constants';
|
||||||
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
|
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
|
||||||
|
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 '../common/viewUtils';
|
import { isStringBoolean } from '../common/viewUtils';
|
||||||
|
|
||||||
export const getStudioOptions = (timeFormat: string): ViewOption[] => [
|
export const getStudioOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => {
|
||||||
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
|
const mainOptions = makeOptionsFromCustomFields(customFields, [{ value: 'title', label: 'Title' }]);
|
||||||
{
|
|
||||||
title: OptionTitle.ElementVisibility,
|
return [
|
||||||
collapsible: true,
|
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
|
||||||
options: [
|
{
|
||||||
{
|
title: OptionTitle.DataSources,
|
||||||
id: 'hideCards',
|
collapsible: true,
|
||||||
title: 'Hide cards section',
|
options: [
|
||||||
description: 'Hides the card section with the timers',
|
{
|
||||||
type: 'boolean',
|
id: 'main',
|
||||||
defaultValue: false,
|
title: 'Main text',
|
||||||
},
|
description: 'Select the data source for the main text',
|
||||||
],
|
type: 'option',
|
||||||
},
|
values: mainOptions,
|
||||||
];
|
defaultValue: 'title',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: OptionTitle.ElementVisibility,
|
||||||
|
collapsible: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
id: 'hideCards',
|
||||||
|
title: 'Hide cards section',
|
||||||
|
description: 'Hides the card section with the timers',
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
type StudioOptions = {
|
type StudioOptions = {
|
||||||
|
mainSource: keyof OntimeEvent | null;
|
||||||
hideCards: boolean;
|
hideCards: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,6 +58,7 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL
|
|||||||
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
mainSource: getValue('main') as keyof OntimeEvent | null,
|
||||||
hideCards: isStringBoolean(getValue('hideCards')),
|
hideCards: isStringBoolean(getValue('hideCards')),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,13 @@ export function getFormattedScheduleTimes(data: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFormattedEventData(eventNow: OntimeEvent | null, timer: TimerState) {
|
export function getFormattedEventData(
|
||||||
|
eventNow: OntimeEvent | null,
|
||||||
|
timer: TimerState,
|
||||||
|
mainSource: keyof OntimeEvent | null,
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
title: eventNow?.title || '-',
|
title: (eventNow?.[mainSource ?? 'title'] as string) || '-',
|
||||||
startedAt: formatTime(timer.startedAt, timeFormat),
|
startedAt: formatTime(timer.startedAt, timeFormat),
|
||||||
expectedEnd: formatTime(timer.expectedFinish, timeFormat),
|
expectedEnd: formatTime(timer.expectedFinish, timeFormat),
|
||||||
timer: millisToString(timer.current),
|
timer: millisToString(timer.current),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ProjectData, Settings, ViewSettings } from 'ontime-types';
|
import { CustomFields, ProjectData, Settings, ViewSettings } from 'ontime-types';
|
||||||
|
|
||||||
|
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
||||||
import useProjectData from '../../common/hooks-query/useProjectData';
|
import useProjectData from '../../common/hooks-query/useProjectData';
|
||||||
import useSettings from '../../common/hooks-query/useSettings';
|
import useSettings from '../../common/hooks-query/useSettings';
|
||||||
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
||||||
@@ -7,6 +8,7 @@ import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
|||||||
import { aggregateQueryStatus, ViewData } from '../utils/viewLoader.utils';
|
import { aggregateQueryStatus, ViewData } from '../utils/viewLoader.utils';
|
||||||
|
|
||||||
export interface StudioData {
|
export interface StudioData {
|
||||||
|
customFields: CustomFields;
|
||||||
projectData: ProjectData;
|
projectData: ProjectData;
|
||||||
isMirrored: boolean;
|
isMirrored: boolean;
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
@@ -21,14 +23,16 @@ export function useStudioData(): ViewData<StudioData> {
|
|||||||
const { data: projectData, status: projectDataStatus } = useProjectData();
|
const { data: projectData, status: projectDataStatus } = useProjectData();
|
||||||
const { data: viewSettings, status: viewSettingsStatus } = useViewSettings();
|
const { data: viewSettings, status: viewSettingsStatus } = useViewSettings();
|
||||||
const { data: settings, status: settingsStatus } = useSettings();
|
const { data: settings, status: settingsStatus } = useSettings();
|
||||||
|
const { data: customFields, status: customFieldsStatus } = useCustomFields();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
|
customFields,
|
||||||
projectData,
|
projectData,
|
||||||
isMirrored,
|
isMirrored,
|
||||||
settings,
|
settings,
|
||||||
viewSettings,
|
viewSettings,
|
||||||
},
|
},
|
||||||
status: aggregateQueryStatus([projectDataStatus, viewSettingsStatus, settingsStatus]),
|
status: aggregateQueryStatus([projectDataStatus, viewSettingsStatus, settingsStatus, customFieldsStatus]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { dayInMs, getLastEvent, MILLIS_PER_HOUR } from 'ontime-utils';
|
|||||||
import useHorizontalFollowComponent from '../../common/hooks/useHorizontalFollowComponent';
|
import useHorizontalFollowComponent from '../../common/hooks/useHorizontalFollowComponent';
|
||||||
import { ExtendedEntry } from '../../common/utils/rundownMetadata';
|
import { ExtendedEntry } from '../../common/utils/rundownMetadata';
|
||||||
import { cx } from '../../common/utils/styleUtils';
|
import { cx } from '../../common/utils/styleUtils';
|
||||||
|
import { getPropertyValue } from '../common/viewUtils';
|
||||||
|
|
||||||
import TimelineMarkers from './timeline-markers/TimelineMarkers';
|
import TimelineMarkers from './timeline-markers/TimelineMarkers';
|
||||||
import { useTimelineOptions } from './timeline.options';
|
import { useTimelineOptions } from './timeline.options';
|
||||||
@@ -24,7 +25,7 @@ interface TimelineProps {
|
|||||||
export default memo(Timeline);
|
export default memo(Timeline);
|
||||||
function Timeline({ firstStart, rundown, selectedEventId, totalDuration }: TimelineProps) {
|
function Timeline({ firstStart, rundown, selectedEventId, totalDuration }: TimelineProps) {
|
||||||
const { width: screenWidth } = useViewportSize();
|
const { width: screenWidth } = useViewportSize();
|
||||||
const { hidePast, fixedSize } = useTimelineOptions();
|
const { mainSource, hidePast, fixedSize } = useTimelineOptions();
|
||||||
const selectedRef = useRef<HTMLDivElement>(null);
|
const selectedRef = useRef<HTMLDivElement>(null);
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -86,6 +87,8 @@ function Timeline({ firstStart, rundown, selectedEventId, totalDuration }: Timel
|
|||||||
const position = positions[index];
|
const position = positions[index];
|
||||||
if (!position) return null;
|
if (!position) return null;
|
||||||
|
|
||||||
|
const displayTitle = getPropertyValue(event, mainSource ?? 'title') || event.title;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimelineEntry
|
<TimelineEntry
|
||||||
key={event.id}
|
key={event.id}
|
||||||
@@ -100,7 +103,7 @@ function Timeline({ firstStart, rundown, selectedEventId, totalDuration }: Timel
|
|||||||
totalGap={event.totalGap}
|
totalGap={event.totalGap}
|
||||||
isLinkedToLoaded={event.isLinkedToLoaded}
|
isLinkedToLoaded={event.isLinkedToLoaded}
|
||||||
dayOffset={event.dayOffset}
|
dayOffset={event.dayOffset}
|
||||||
title={event.title}
|
title={displayTitle}
|
||||||
cue={event.cue}
|
cue={event.cue}
|
||||||
width={position.width}
|
width={position.width}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import Loader from '../common/loader/Loader';
|
|||||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||||
|
|
||||||
import Timeline from './Timeline';
|
import Timeline from './Timeline';
|
||||||
import { getTimelineOptions } from './timeline.options';
|
import { getTimelineOptions, useTimelineOptions } from './timeline.options';
|
||||||
import { getUpcomingEvents, useScopedRundown } from './timeline.utils';
|
import { getUpcomingEvents, useScopedRundown } from './timeline.utils';
|
||||||
import TimelineSections from './TimelineSections';
|
import TimelineSections from './TimelineSections';
|
||||||
import { TimelineData, useTimelineData } from './useTimelineData';
|
import { TimelineData, useTimelineData } from './useTimelineData';
|
||||||
@@ -35,8 +35,9 @@ export default function TimelinePageLoader() {
|
|||||||
return <TimelinePage {...data} />;
|
return <TimelinePage {...data} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TimelinePage({ events, projectData, settings }: TimelineData) {
|
function TimelinePage({ events, customFields, projectData, settings }: TimelineData) {
|
||||||
const { selectedEventId } = useSelectedEventId();
|
const { selectedEventId } = useSelectedEventId();
|
||||||
|
const { mainSource } = useTimelineOptions();
|
||||||
// holds copy of the rundown with only relevant events
|
// holds copy of the rundown with only relevant events
|
||||||
const { scopedRundown, firstStart, totalDuration } = useScopedRundown(events, selectedEventId);
|
const { scopedRundown, firstStart, totalDuration } = useScopedRundown(events, selectedEventId);
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ function TimelinePage({ events, projectData, settings }: TimelineData) {
|
|||||||
|
|
||||||
// populate options
|
// populate options
|
||||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||||
const progressOptions = useMemo(() => getTimelineOptions(defaultFormat), [defaultFormat]);
|
const progressOptions = useMemo(() => getTimelineOptions(defaultFormat, customFields), [defaultFormat, customFields]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='timeline' data-testid='timeline-view'>
|
<div className='timeline' data-testid='timeline-view'>
|
||||||
@@ -58,7 +59,7 @@ function TimelinePage({ events, projectData, settings }: TimelineData) {
|
|||||||
<TimelineClock />
|
<TimelineClock />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TimelineSections now={now} next={next} followedBy={followedBy} />
|
<TimelineSections now={now} next={next} followedBy={followedBy} mainSource={mainSource} />
|
||||||
|
|
||||||
<Timeline
|
<Timeline
|
||||||
firstStart={firstStart}
|
firstStart={firstStart}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useExpectedStartData } from '../../common/hooks/useSocket';
|
|||||||
import { ExtendedEntry } from '../../common/utils/rundownMetadata';
|
import { ExtendedEntry } from '../../common/utils/rundownMetadata';
|
||||||
import { formatDuration, getExpectedTimesFromExtendedEvent } from '../../common/utils/time';
|
import { formatDuration, getExpectedTimesFromExtendedEvent } from '../../common/utils/time';
|
||||||
import { useTranslation } from '../../translation/TranslationProvider';
|
import { useTranslation } from '../../translation/TranslationProvider';
|
||||||
|
import { getPropertyValue } from '../common/viewUtils';
|
||||||
|
|
||||||
import TimelineSection from './timeline-section/TimelineSection';
|
import TimelineSection from './timeline-section/TimelineSection';
|
||||||
|
|
||||||
@@ -12,17 +13,18 @@ interface TimelineSectionsProps {
|
|||||||
now: ExtendedEntry<OntimeEvent> | null;
|
now: ExtendedEntry<OntimeEvent> | null;
|
||||||
next: ExtendedEntry<OntimeEvent> | null;
|
next: ExtendedEntry<OntimeEvent> | null;
|
||||||
followedBy: ExtendedEntry<OntimeEvent> | null;
|
followedBy: ExtendedEntry<OntimeEvent> | null;
|
||||||
|
mainSource: keyof OntimeEvent | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TimelineSections({ now, next, followedBy }: TimelineSectionsProps) {
|
export default function TimelineSections({ now, next, followedBy, mainSource }: TimelineSectionsProps) {
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const state = useExpectedStartData();
|
const state = useExpectedStartData();
|
||||||
|
|
||||||
// gather card data
|
// gather card data
|
||||||
const titleNow = now?.title ?? '-';
|
const titleNow = getPropertyValue(now, mainSource ?? 'title') ?? '-';
|
||||||
const dueText = getLocalizedString('timeline.due').toUpperCase();
|
const dueText = getLocalizedString('timeline.due').toUpperCase();
|
||||||
const nextText = next !== null ? next.title : '-';
|
const nextText = getPropertyValue(next, mainSource ?? 'title') ?? '-';
|
||||||
const followedByText = followedBy !== null ? followedBy.title : '-';
|
const followedByText = getPropertyValue(followedBy, mainSource ?? 'title') ?? '-';
|
||||||
let nextStatus: string | undefined;
|
let nextStatus: string | undefined;
|
||||||
let followedByStatus: string | undefined;
|
let followedByStatus: string | undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,33 @@
|
|||||||
import { use, useMemo } from 'react';
|
import { use, useMemo } from 'react';
|
||||||
import { useSearchParams } from 'react-router';
|
import { useSearchParams } from 'react-router';
|
||||||
|
import { CustomFields, OntimeEvent } from 'ontime-types';
|
||||||
|
|
||||||
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
|
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
|
||||||
import { OptionTitle } from '../../common/components/view-params-editor/constants';
|
import { OptionTitle } from '../../common/components/view-params-editor/constants';
|
||||||
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
|
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
|
||||||
|
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 '../common/viewUtils';
|
import { isStringBoolean } from '../common/viewUtils';
|
||||||
|
|
||||||
export const getTimelineOptions = (timeFormat: string): ViewOption[] => {
|
export const getTimelineOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => {
|
||||||
|
const mainOptions = makeOptionsFromCustomFields(customFields, [{ value: 'title', label: 'Title' }]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
|
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: OptionTitle.ElementVisibility,
|
title: OptionTitle.ElementVisibility,
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
@@ -34,6 +52,7 @@ export const getTimelineOptions = (timeFormat: string): ViewOption[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TimelineOptions = {
|
type TimelineOptions = {
|
||||||
|
mainSource: keyof OntimeEvent | null;
|
||||||
hidePast: boolean;
|
hidePast: boolean;
|
||||||
fixedSize: boolean;
|
fixedSize: boolean;
|
||||||
};
|
};
|
||||||
@@ -47,6 +66,7 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL
|
|||||||
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
const getValue = (key: string) => defaultValues?.get(key) ?? searchParams.get(key);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
mainSource: getValue('main') as keyof OntimeEvent | null,
|
||||||
hidePast: isStringBoolean(getValue('hidePast')),
|
hidePast: isStringBoolean(getValue('hidePast')),
|
||||||
fixedSize: isStringBoolean(getValue('fixedSize')),
|
fixedSize: isStringBoolean(getValue('fixedSize')),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { OntimeEntry, ProjectData, Settings } from 'ontime-types';
|
import { CustomFields, OntimeEntry, ProjectData, Settings } from 'ontime-types';
|
||||||
|
|
||||||
|
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
||||||
import useProjectData from '../../common/hooks-query/useProjectData';
|
import useProjectData from '../../common/hooks-query/useProjectData';
|
||||||
import { useFlatRundownWithMetadata } from '../../common/hooks-query/useRundown';
|
import { useFlatRundownWithMetadata } from '../../common/hooks-query/useRundown';
|
||||||
import useSettings from '../../common/hooks-query/useSettings';
|
import useSettings from '../../common/hooks-query/useSettings';
|
||||||
@@ -8,6 +9,7 @@ import { aggregateQueryStatus, ViewData } from '../utils/viewLoader.utils';
|
|||||||
|
|
||||||
export interface TimelineData {
|
export interface TimelineData {
|
||||||
events: ExtendedEntry<OntimeEntry>[];
|
events: ExtendedEntry<OntimeEntry>[];
|
||||||
|
customFields: CustomFields;
|
||||||
projectData: ProjectData;
|
projectData: ProjectData;
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
}
|
}
|
||||||
@@ -17,13 +19,15 @@ export function useTimelineData(): ViewData<TimelineData> {
|
|||||||
const { data: rundownData, status: rundownStatus } = useFlatRundownWithMetadata();
|
const { data: rundownData, status: rundownStatus } = useFlatRundownWithMetadata();
|
||||||
const { data: projectData, status: projectDataStatus } = useProjectData();
|
const { data: projectData, status: projectDataStatus } = useProjectData();
|
||||||
const { data: settings, status: settingsStatus } = useSettings();
|
const { data: settings, status: settingsStatus } = useSettings();
|
||||||
|
const { data: customFields, status: customFieldsStatus } = useCustomFields();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
events: rundownData,
|
events: rundownData,
|
||||||
|
customFields,
|
||||||
projectData,
|
projectData,
|
||||||
settings,
|
settings,
|
||||||
},
|
},
|
||||||
status: aggregateQueryStatus([rundownStatus, projectDataStatus, settingsStatus]),
|
status: aggregateQueryStatus([rundownStatus, projectDataStatus, settingsStatus, customFieldsStatus]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export default function TimerLoader() {
|
|||||||
return <Timer {...data} />;
|
return <Timer {...data} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Timer({ customFields, projectData, isMirrored, settings, viewSettings }: TimerData) {
|
function Timer({ customFields, projectData, isMirrored, settings, viewSettings, entries }: TimerData) {
|
||||||
const { eventNext, eventNow, message, time, clock, timerTypeNow, countToEndNow, auxTimer } = useTimerSocket();
|
const { eventNext, eventNow, message, time, clock, timerTypeNow, countToEndNow, auxTimer } = useTimerSocket();
|
||||||
const {
|
const {
|
||||||
hideClock,
|
hideClock,
|
||||||
@@ -97,6 +97,7 @@ function Timer({ customFields, projectData, isMirrored, settings, viewSettings }
|
|||||||
secondarySource,
|
secondarySource,
|
||||||
time.playback,
|
time.playback,
|
||||||
time.phase,
|
time.phase,
|
||||||
|
entries,
|
||||||
);
|
);
|
||||||
|
|
||||||
// gather timer data
|
// gather timer data
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ export const getTimerOptions = (timeFormat: string, customFields: CustomFields):
|
|||||||
const mainOptions = makeOptionsFromCustomFields(customFields, [
|
const mainOptions = makeOptionsFromCustomFields(customFields, [
|
||||||
{ value: 'none', label: 'None' },
|
{ value: 'none', label: 'None' },
|
||||||
{ value: 'title', label: 'Title' },
|
{ value: 'title', label: 'Title' },
|
||||||
{ value: 'note', label: 'Note' },
|
|
||||||
]);
|
]);
|
||||||
const secondaryOptions = makeOptionsFromCustomFields(customFields, [
|
const secondaryOptions = makeOptionsFromCustomFields(customFields, [
|
||||||
{ value: 'none', label: 'None' },
|
{ value: 'none', label: 'None' },
|
||||||
{ value: 'title', label: 'Title' },
|
{ value: 'title', label: 'Title' },
|
||||||
{ value: 'note', label: 'Note' },
|
{ value: 'note', label: 'Note' },
|
||||||
|
{ value: 'parent', label: 'Group Title' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -185,8 +185,8 @@ type TimerOptions = {
|
|||||||
hideLogo: boolean;
|
hideLogo: boolean;
|
||||||
hideTimerSeconds: boolean;
|
hideTimerSeconds: boolean;
|
||||||
removeLeadingZeros: boolean;
|
removeLeadingZeros: boolean;
|
||||||
mainSource: keyof OntimeEvent | null;
|
mainSource: keyof OntimeEvent | null | 'none';
|
||||||
secondarySource: keyof OntimeEvent | null;
|
secondarySource: keyof OntimeEvent | null | 'none';
|
||||||
timerType?: TimerType;
|
timerType?: TimerType;
|
||||||
freezeOvertime: boolean;
|
freezeOvertime: boolean;
|
||||||
freezeMessage: string;
|
freezeMessage: string;
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
import { MaybeNumber, MessageState, OntimeEvent, Playback, TimerMessage, TimerPhase, TimerType } from 'ontime-types';
|
import {
|
||||||
|
MaybeNumber,
|
||||||
|
MessageState,
|
||||||
|
OntimeEvent,
|
||||||
|
Playback,
|
||||||
|
RundownEntries,
|
||||||
|
TimerMessage,
|
||||||
|
TimerPhase,
|
||||||
|
TimerType,
|
||||||
|
} from 'ontime-types';
|
||||||
import { isPlaybackActive } from 'ontime-utils';
|
import { isPlaybackActive } from 'ontime-utils';
|
||||||
|
|
||||||
import { getFormattedTimer, getPropertyValue } from '../common/viewUtils';
|
import { getFormattedTimer, getPropertyValue } from '../common/viewUtils';
|
||||||
@@ -141,10 +150,11 @@ export function getSecondaryDisplay(
|
|||||||
export function getCardData(
|
export function getCardData(
|
||||||
eventNow: OntimeEvent | null,
|
eventNow: OntimeEvent | null,
|
||||||
eventNext: OntimeEvent | null,
|
eventNext: OntimeEvent | null,
|
||||||
mainSource: keyof OntimeEvent | null,
|
mainSource: keyof OntimeEvent | null | 'none',
|
||||||
secondarySource: keyof OntimeEvent | null,
|
secondarySource: keyof OntimeEvent | null | 'none',
|
||||||
playback: Playback,
|
playback: Playback,
|
||||||
phase: TimerPhase,
|
phase: TimerPhase,
|
||||||
|
entries: RundownEntries,
|
||||||
) {
|
) {
|
||||||
if (playback === Playback.Stop) {
|
if (playback === Playback.Stop) {
|
||||||
return {
|
return {
|
||||||
@@ -162,19 +172,19 @@ export function getCardData(
|
|||||||
|
|
||||||
// if we are loaded, we show the upcoming event as next
|
// if we are loaded, we show the upcoming event as next
|
||||||
const nowMain = hasActiveTimer ? getPropertyValue(eventNow, mainSource ?? 'title') : undefined;
|
const nowMain = hasActiveTimer ? getPropertyValue(eventNow, mainSource ?? 'title') : undefined;
|
||||||
const nowSecondary = hasActiveTimer ? getPropertyValue(eventNow, secondarySource) : undefined;
|
const nowSecondary = hasActiveTimer ? getPropertyValue(eventNow, secondarySource, entries) : undefined;
|
||||||
const nextMain = hasActiveTimer
|
const nextMain = hasActiveTimer
|
||||||
? getPropertyValue(eventNext, mainSource ?? 'title')
|
? getPropertyValue(eventNext, mainSource ?? 'title')
|
||||||
: getPropertyValue(eventNow, mainSource ?? 'title');
|
: getPropertyValue(eventNow, mainSource ?? 'title');
|
||||||
const nextSecondary = hasActiveTimer
|
const nextSecondary = hasActiveTimer
|
||||||
? getPropertyValue(eventNext, secondarySource)
|
? getPropertyValue(eventNext, secondarySource, entries)
|
||||||
: getPropertyValue(eventNow, secondarySource);
|
: getPropertyValue(eventNow, secondarySource, entries);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showNow: Boolean(nowMain) || Boolean(nowSecondary),
|
showNow: mainSource !== 'none' || Boolean(nowSecondary),
|
||||||
nowMain,
|
nowMain,
|
||||||
nowSecondary,
|
nowSecondary,
|
||||||
showNext: Boolean(nextMain) || Boolean(nextSecondary),
|
showNext: mainSource !== 'none' || Boolean(nextSecondary),
|
||||||
nextMain,
|
nextMain,
|
||||||
nextSecondary,
|
nextSecondary,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { CustomFields, ProjectData, Settings, ViewSettings } from 'ontime-types';
|
import { CustomFields, ProjectData, RundownEntries, Settings, ViewSettings } from 'ontime-types';
|
||||||
|
|
||||||
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
||||||
import useProjectData from '../../common/hooks-query/useProjectData';
|
import useProjectData from '../../common/hooks-query/useProjectData';
|
||||||
|
import useRundown from '../../common/hooks-query/useRundown';
|
||||||
import useSettings from '../../common/hooks-query/useSettings';
|
import useSettings from '../../common/hooks-query/useSettings';
|
||||||
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
import useViewSettings from '../../common/hooks-query/useViewSettings';
|
||||||
import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
||||||
@@ -13,6 +14,7 @@ export interface TimerData {
|
|||||||
isMirrored: boolean;
|
isMirrored: boolean;
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
viewSettings: ViewSettings;
|
viewSettings: ViewSettings;
|
||||||
|
entries: RundownEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTimerData(): ViewData<TimerData> {
|
export function useTimerData(): ViewData<TimerData> {
|
||||||
@@ -24,6 +26,8 @@ export function useTimerData(): ViewData<TimerData> {
|
|||||||
const { data: viewSettings, status: viewSettingsStatus } = useViewSettings();
|
const { data: viewSettings, status: viewSettingsStatus } = useViewSettings();
|
||||||
const { data: settings, status: settingsStatus } = useSettings();
|
const { data: settings, status: settingsStatus } = useSettings();
|
||||||
const { data: customFields, status: customFieldsStatus } = useCustomFields();
|
const { data: customFields, status: customFieldsStatus } = useCustomFields();
|
||||||
|
const { data: rundown, status: rundownStatus } = useRundown();
|
||||||
|
const { entries } = rundown;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
@@ -32,7 +36,14 @@ export function useTimerData(): ViewData<TimerData> {
|
|||||||
isMirrored,
|
isMirrored,
|
||||||
settings,
|
settings,
|
||||||
viewSettings,
|
viewSettings,
|
||||||
|
entries,
|
||||||
},
|
},
|
||||||
status: aggregateQueryStatus([projectDataStatus, viewSettingsStatus, settingsStatus, customFieldsStatus]),
|
status: aggregateQueryStatus([
|
||||||
|
projectDataStatus,
|
||||||
|
viewSettingsStatus,
|
||||||
|
settingsStatus,
|
||||||
|
customFieldsStatus,
|
||||||
|
rundownStatus,
|
||||||
|
]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const langEn = {
|
|||||||
'countdown.to_start': 'Time to start',
|
'countdown.to_start': 'Time to start',
|
||||||
'countdown.waiting': 'Waiting for event start',
|
'countdown.waiting': 'Waiting for event start',
|
||||||
'countdown.overtime': 'in overtime',
|
'countdown.overtime': 'in overtime',
|
||||||
|
'countdown.all_have_finished': 'All selected events have finished',
|
||||||
'timeline.live': 'live',
|
'timeline.live': 'live',
|
||||||
'timeline.done': 'done',
|
'timeline.done': 'done',
|
||||||
'timeline.due': 'due',
|
'timeline.due': 'due',
|
||||||
|
|||||||
Reference in New Issue
Block a user