refactor: deprecate presenter and subtitle (#795)

* refactor: deprecate presenter and subtitle

* style: reduce spacing between related sections

* refactor: optional secondary field

* refactor: remove secondary field

* refactor: dynamic data source
This commit is contained in:
Carlos Valente
2024-02-29 18:19:58 +01:00
committed by GitHub
parent d7392b93d2
commit 8df419d835
51 changed files with 735 additions and 731 deletions
@@ -31,6 +31,12 @@
text-overflow: ellipsis;
}
.entry-secondary {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&:not(:last-child) {
padding-bottom: 8px;
}
@@ -68,4 +74,3 @@
}
}
}
@@ -2,8 +2,8 @@ import { createContext, PropsWithChildren, useContext, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { OntimeEvent } from 'ontime-types';
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
import { useInterval } from '../../hooks/useInterval';
import { isStringBoolean } from '../../utils/viewUtils';
interface ScheduleContextState {
events: OntimeEvent[];
@@ -13,14 +13,13 @@ interface ScheduleItemProps {
timeStart: number;
timeEnd: number;
title: string;
presenter?: string;
backstageEvent: boolean;
colour: string;
skip: boolean;
}
export default function ScheduleItem(props: ScheduleItemProps) {
const { selected, timeStart, timeEnd, title, presenter, backstageEvent, colour, skip } = props;
const { selected, timeStart, timeEnd, title, backstageEvent, colour, skip } = props;
const start = formatTime(timeStart, formatOptions);
const end = formatTime(timeEnd, formatOptions);
@@ -39,7 +38,6 @@ export default function ScheduleItem(props: ScheduleItemProps) {
</div>
</div>
<div className='entry-title'>{title}</div>
{presenter && <div className='entry-presenter'>{presenter}</div>}
</li>
);
}
@@ -3,34 +3,38 @@
.title-card {
display: flex;
flex-direction: column;
gap: 8px;
gap: 0.5rem;
}
.inline {
display: flex;
}
.inline {
display: flex;
}
.title {
font-weight: 600;
font-size: clamp(32px, 3.5vw, 50px);
color: var(--color-override, $viewer-color);
line-height: 1.1em;
}
.title-card__title {
font-weight: 600;
font-size: clamp(32px, 3.5vw, 50px);
color: var(--color-override, $viewer-color);
line-height: 1.1em;
}
.subtitle, .presenter {
font-size: clamp(24px, 2vw, 35px);
color: var(--secondary-color-override, $viewer-secondary-color);
line-height: 1.1em;
}
.title-card__label {
font-size: clamp(1rem, 1.5vw, 1.5rem);
font-weight: 400;
color: var(--secondary-color-override, $viewer-secondary-color);
margin-left: auto;
text-transform: uppercase;
.label {
font-size: clamp(16px, 1.5vw, 24px);
font-weight: 400;
color: var(--secondary-color-override, $viewer-secondary-color);
margin-left: auto;
text-transform: uppercase;
&.accent {
color: var(--accent-color-override, $accent-color);
}
&--accent {
color: var(--accent-color-override, $accent-color);
}
}
.title-card__secondary {
font-size: clamp(1.5rem, 2vw, 2.25rem);
color: var(--secondary-color-override, $viewer-secondary-color);
line-height: 1.1em;
&::after {
content: '\200b';
}
}
@@ -4,13 +4,12 @@ import './TitleCard.scss';
interface TitleCardProps {
label: 'now' | 'next';
title: string | null;
subtitle: string | null;
presenter: string | null;
title: string;
secondary?: string;
}
export default function TitleCard(props: TitleCardProps) {
const { label, title, subtitle, presenter } = props;
const { label, title, secondary } = props;
const { getLocalizedString } = useTranslation();
const accent = label === 'now';
@@ -18,11 +17,12 @@ export default function TitleCard(props: TitleCardProps) {
return (
<div className='title-card'>
<div className='inline'>
<span className='presenter'>{presenter}</span>
<span className={accent ? 'label accent' : 'label'}>{getLocalizedString(`common.${label}`)}</span>
<span className='title-card__title'>{title}</span>
<span className={accent ? 'title-card__label title-card__label--accent' : 'title-card__label'}>
{getLocalizedString(`common.${label}`)}
</span>
</div>
<div className='title'>{title}</div>
<div className='subtitle'>{subtitle}</div>
<div className='title-card__secondary'>{secondary}</div>
</div>
);
}
@@ -1,7 +1,7 @@
import { useSearchParams } from 'react-router-dom';
import { Input, InputGroup, InputLeftElement, Select, Switch } from '@chakra-ui/react';
import { isStringBoolean } from '../../utils/viewUtils';
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
import { ParamField } from './types';
@@ -11,7 +11,7 @@
@extend .drawerContent;
display: flex;
justify-content: start;
gap: $element-spacing;
gap: $section-spacing;
button[type='reset'] {
padding: 0 2em;
@@ -30,9 +30,9 @@
.columnSection {
display: flex;
padding: $element-spacing;
padding: $section-spacing 0;
flex-direction: column;
gap: $element-inner-spacing;
gap: $element-spacing;
}
.title {
@@ -1,7 +1,16 @@
import { CustomFields } from 'ontime-types';
import { capitaliseFirstLetter } from '../../../features/viewers/common/viewUtils';
import { ParamField } from './types';
const makeOptionsFromCustomFields = (customFields: CustomFields, additionalOptions?: Record<string, string>) => {
const customFieldOptions = Object.keys(customFields).reduce((acc, key) => {
return { ...acc, [`custom-${key}`]: `Custom: ${capitaliseFirstLetter(key)}` };
}, additionalOptions ?? {});
return customFieldOptions;
};
const getTimeOption = (timeFormat: string): ParamField => {
const placeholder = `${timeFormat} (default)`;
return {
@@ -93,45 +102,56 @@ export const getClockOptions = (timeFormat: string): ParamField[] => [
},
];
export const getTimerOptions = (timeFormat: string): ParamField[] => [
getTimeOption(timeFormat),
hideTimerSeconds,
{
id: 'hideClock',
title: 'Hide Time Now',
description: 'Hides the Time Now field',
type: 'boolean',
defaultValue: false,
},
{
id: 'hideCards',
title: 'Hide Cards',
description: 'Hides the Now and Next cards',
type: 'boolean',
defaultValue: false,
},
{
id: 'hideProgress',
title: 'Hide progress bar',
description: 'Hides the progress bar',
type: 'boolean',
defaultValue: false,
},
{
id: 'hideMessage',
title: 'Hide Presenter Message',
description: 'Prevents the screen from displaying messages from the presenter',
type: 'boolean',
defaultValue: false,
},
{
id: 'hideExternal',
title: 'Hide External',
description: 'Prevents the screen from displaying the external field',
type: 'boolean',
defaultValue: false,
},
];
export const getTimerOptions = (timeFormat: string, customFields: CustomFields): ParamField[] => {
const secondaryOptions = makeOptionsFromCustomFields(customFields);
return [
getTimeOption(timeFormat),
hideTimerSeconds,
{
id: 'hideClock',
title: 'Hide Time Now',
description: 'Hides the Time Now field',
type: 'boolean',
defaultValue: false,
},
{
id: 'secondary-src',
title: 'Secondary text',
description: 'Select the data source for the secondary text',
type: 'option',
values: secondaryOptions,
defaultValue: '',
},
{
id: 'hideCards',
title: 'Hide Cards',
description: 'Hides the Now and Next cards',
type: 'boolean',
defaultValue: false,
},
{
id: 'hideProgress',
title: 'Hide progress bar',
description: 'Hides the progress bar',
type: 'boolean',
defaultValue: false,
},
{
id: 'hideMessage',
title: 'Hide Presenter Message',
description: 'Prevents the screen from displaying messages from the presenter',
type: 'boolean',
defaultValue: false,
},
{
id: 'hideExternal',
title: 'Hide External',
description: 'Prevents the screen from displaying the external field',
type: 'boolean',
defaultValue: false,
},
];
};
export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
hideTimerSeconds,
@@ -226,187 +246,216 @@ export const MINIMAL_TIMER_OPTIONS: ParamField[] = [
},
];
export const LOWER_THIRD_OPTIONS: ParamField[] = [
{
id: 'trigger',
title: 'Animation Trigger',
description: '',
type: 'option',
values: {
event: 'Event Load',
manual: 'Manual',
},
defaultValue: 'event',
},
{
id: 'top-src',
title: 'Top Text',
description: '',
type: 'option',
values: {
title: 'Title',
subtitle: 'Subtitle',
presenter: 'Presenter',
lowerMsg: 'Lower Third Message',
},
defaultValue: 'title',
},
{
id: 'bottom-src',
title: 'Bottom Text',
description: 'Select the text source for the bottom element',
type: 'option',
values: {
title: 'Title',
subtitle: 'Subtitle',
presenter: 'Presenter',
lowerMsg: 'Lower Third Message',
},
defaultValue: 'subtitle',
},
{
id: 'top-colour',
title: 'Top Text Colour',
description: 'Top text colour in hexadecimal',
prefix: '#',
type: 'string',
placeholder: '0000ff (default)',
},
{
id: 'bottom-colour',
title: 'Bottom Text Colour',
description: 'Bottom text colour in hexadecimal',
prefix: '#',
type: 'string',
placeholder: '0000ff (default)',
},
{
id: 'top-bg',
title: 'Top Background Colour',
description: 'Top text background colour in hexadecimal',
prefix: '#',
type: 'string',
placeholder: '00000000 (default)',
},
{
id: 'bottom-bg',
title: 'Bottom Background Colour',
description: 'Bottom text background colour in hexadecimal',
prefix: '#',
type: 'string',
placeholder: '00000000 (default)',
},
{
id: 'top-size',
title: 'Top Text Size',
description: 'Font size of the top text',
type: 'string',
placeholder: '65px',
},
{
id: 'bottom-size',
title: 'Bottom Text Size',
description: 'Font size of the bottom text',
type: 'string',
placeholder: '64px',
},
{
id: 'width',
title: 'Minimum Width',
description: 'Minimum Width of the element',
type: 'number',
prefix: '%',
placeholder: '45 (default)',
},
{
id: 'transition',
title: 'Transition',
description: 'Transition in time in seconds (default 3)',
type: 'number',
placeholder: '3 (default)',
},
{
id: 'delay',
title: 'Delay',
description: 'Delay between transition in and out in seconds (default 3)',
type: 'number',
placeholder: '3 (default)',
},
{
id: 'key',
title: 'Key Colour',
description: 'Colour of the background',
prefix: '#',
type: 'string',
placeholder: 'ffffffff (default)',
},
{
id: 'line-colour',
title: 'Line Colour',
description: 'Colour of the line',
prefix: '#',
type: 'string',
placeholder: 'ff0000ff (default)',
},
];
export const getLowerThirdOptions = (customFields: CustomFields): ParamField[] => {
const topSourceOptions = makeOptionsFromCustomFields(customFields, {
title: 'Title',
lowerMsg: 'Lower Third Message',
});
export const getBackstageOptions = (timeFormat: string): ParamField[] => [
getTimeOption(timeFormat),
{
id: 'hidePast',
title: 'Hide past events',
description: 'Scheduler will only show upcoming events',
type: 'boolean',
defaultValue: false,
},
{
id: 'stopCycle',
title: 'Stop cycling through event pages',
description: 'Schedule will not auto-cycle through events',
type: 'boolean',
defaultValue: false,
},
{
id: 'eventsPerPage',
title: 'Events per page',
description: 'Sets the number of events on the page, can cause overlow',
type: 'number',
placeholder: '7 (default)',
},
];
const bottomSourceOptions = makeOptionsFromCustomFields(customFields, {
title: 'Title',
lowerMsg: 'Lower Third Message',
});
return [
{
id: 'trigger',
title: 'Animation Trigger',
description: '',
type: 'option',
values: {
event: 'Event Load',
manual: 'Manual',
},
defaultValue: 'event',
},
{
id: 'top-src',
title: 'Top Text',
description: '',
type: 'option',
values: topSourceOptions,
defaultValue: 'title',
},
{
id: 'bottom-src',
title: 'Bottom Text',
description: 'Select the data source for the bottom element',
type: 'option',
values: bottomSourceOptions,
defaultValue: 'lowerMsg',
},
{
id: 'top-colour',
title: 'Top Text Colour',
description: 'Top text colour in hexadecimal',
prefix: '#',
type: 'string',
placeholder: '0000ff (default)',
},
{
id: 'bottom-colour',
title: 'Bottom Text Colour',
description: 'Bottom text colour in hexadecimal',
prefix: '#',
type: 'string',
placeholder: '0000ff (default)',
},
{
id: 'top-bg',
title: 'Top Background Colour',
description: 'Top text background colour in hexadecimal',
prefix: '#',
type: 'string',
placeholder: '00000000 (default)',
},
{
id: 'bottom-bg',
title: 'Bottom Background Colour',
description: 'Bottom text background colour in hexadecimal',
prefix: '#',
type: 'string',
placeholder: '00000000 (default)',
},
{
id: 'top-size',
title: 'Top Text Size',
description: 'Font size of the top text',
type: 'string',
placeholder: '65px',
},
{
id: 'bottom-size',
title: 'Bottom Text Size',
description: 'Font size of the bottom text',
type: 'string',
placeholder: '64px',
},
{
id: 'width',
title: 'Minimum Width',
description: 'Minimum Width of the element',
type: 'number',
prefix: '%',
placeholder: '45 (default)',
},
{
id: 'transition',
title: 'Transition',
description: 'Transition in time in seconds (default 3)',
type: 'number',
placeholder: '3 (default)',
},
{
id: 'delay',
title: 'Delay',
description: 'Delay between transition in and out in seconds (default 3)',
type: 'number',
placeholder: '3 (default)',
},
{
id: 'key',
title: 'Key Colour',
description: 'Colour of the background',
prefix: '#',
type: 'string',
placeholder: 'ffffffff (default)',
},
{
id: 'line-colour',
title: 'Line Colour',
description: 'Colour of the line',
prefix: '#',
type: 'string',
placeholder: 'ff0000ff (default)',
},
];
};
export const getBackstageOptions = (timeFormat: string, customFields: CustomFields): ParamField[] => {
const secondaryOptions = makeOptionsFromCustomFields(customFields, { note: 'Note' });
return [
getTimeOption(timeFormat),
{
id: 'hidePast',
title: 'Hide past events',
description: 'Scheduler will only show upcoming events',
type: 'boolean',
defaultValue: false,
},
{
id: 'stopCycle',
title: 'Stop cycling through event pages',
description: 'Schedule will not auto-cycle through events',
type: 'boolean',
defaultValue: false,
},
{
id: 'eventsPerPage',
title: 'Events per page',
description: 'Sets the number of events on the page, can cause overlow',
type: 'number',
placeholder: '7 (default)',
},
{
id: 'secondary-src',
title: 'Event secondary text',
description: 'Select the data source for auxiliary text shown in now and next cards',
type: 'option',
values: secondaryOptions,
defaultValue: '',
},
];
};
export const getPublicOptions = (timeFormat: string, customFields: CustomFields): ParamField[] => {
const secondaryOptions = makeOptionsFromCustomFields(customFields);
return [
getTimeOption(timeFormat),
{
id: 'hidePast',
title: 'Hide past events',
description: 'Scheduler will only show upcoming events',
type: 'boolean',
defaultValue: false,
},
{
id: 'stopCycle',
title: 'Stop cycling through event pages',
description: 'Schedule will not auto-cycle through events',
type: 'boolean',
defaultValue: false,
},
{
id: 'eventsPerPage',
title: 'Events per page',
description: 'Sets the number of events on the page, can cause overlow',
type: 'number',
placeholder: '7 (default)',
},
{
id: 'secondary-src',
title: 'Event secondary text',
description: 'Select the data source for auxiliary text shown in now and next cards',
type: 'option',
values: secondaryOptions,
defaultValue: '',
},
];
};
export const getPublicOptions = (timeFormat: string): ParamField[] => [
getTimeOption(timeFormat),
{
id: 'hidePast',
title: 'Hide past events',
description: 'Scheduler will only show upcoming events',
type: 'boolean',
defaultValue: false,
},
{
id: 'stopCycle',
title: 'Stop cycling through event pages',
description: 'Schedule will not auto-cycle through events',
type: 'boolean',
defaultValue: false,
},
{
id: 'eventsPerPage',
title: 'Events per page',
description: 'Sets the number of events on the page, can cause overlow',
type: 'number',
placeholder: '7 (default)',
},
];
export const getStudioClockOptions = (timeFormat: string): ParamField[] => [
getTimeOption(timeFormat),
hideTimerSeconds,
];
export const getOperatorOptions = (customFields: CustomFields, timeFormat: string): ParamField[] => {
const fieldOptions = makeOptionsFromCustomFields(customFields, { title: 'Title', note: 'Note' });
const customFieldSelect = Object.keys(customFields).reduce((acc, key) => {
return { ...acc, [key]: key };
return { ...acc, [key]: `Custom: ${capitaliseFirstLetter(key)}` };
}, {});
return [
@@ -423,22 +472,16 @@ export const getOperatorOptions = (customFields: CustomFields, timeFormat: strin
title: 'Main data field',
description: 'Field to be shown in the first line of text',
type: 'option',
values: {
title: 'Title',
subtitle: 'Subtitle',
presenter: 'Presenter',
},
values: fieldOptions,
defaultValue: 'title',
},
{
id: 'secondary',
title: 'Secondary data field',
description: 'Field to be shown in the second line of text',
type: 'option',
values: {
title: 'Title',
subtitle: 'Subtitle',
presenter: 'Presenter',
},
values: fieldOptions,
defaultValue: '',
},
{
id: 'subscribe',
@@ -446,6 +489,7 @@ export const getOperatorOptions = (customFields: CustomFields, timeFormat: strin
description: 'Choose a custom field to highlight',
type: 'option',
values: customFieldSelect,
defaultValue: '',
},
{
id: 'shouldEdit',
@@ -9,8 +9,6 @@ describe('cloneEvent()', () => {
type: SupportedEvent.Event,
title: 'title',
cue: 'cue',
subtitle: 'subtitle',
presenter: 'presenter',
note: 'note',
timeStart: 0,
duration: 10,
@@ -33,8 +31,6 @@ describe('cloneEvent()', () => {
// @ts-expect-error -- safeguarding this
expect(cloned?.id).toBe(undefined);
expect(cloned.title).toBe(original.title);
expect(cloned.subtitle).toBe(original.subtitle);
expect(cloned.presenter).toBe(original.presenter);
expect(cloned.note).toBe(original.note);
expect(cloned.endAction).toBe(original.endAction);
expect(cloned.timerType).toBe(original.timerType);
@@ -11,8 +11,6 @@ export const cloneEvent = (event: OntimeEvent, after?: string): ClonedEvent => {
return {
type: SupportedEvent.Event,
title: event.title,
subtitle: event.subtitle,
presenter: event.presenter,
note: event.note,
timeStart: event.timeStart,
duration: event.duration,
@@ -1,6 +0,0 @@
export function isStringBoolean(text: string | null) {
if (text === null) {
return false;
}
return text?.toLowerCase() === 'true' || text === '1';
}
@@ -11,8 +11,6 @@ describe('convertToImportMap', () => {
Duration: 'duration',
Cue: 'cue',
Title: 'title',
Presenter: 'presenter',
Subtitle: 'subtitle',
'Is Public': 'public',
Skip: 'skip',
Note: 'notes',
@@ -10,8 +10,6 @@ export const namedImportMap = {
Duration: 'duration',
Cue: 'cue',
Title: 'title',
Presenter: 'presenter',
Subtitle: 'subtitle',
'Is Public': 'public',
Skip: 'skip',
Note: 'notes',
@@ -38,8 +36,6 @@ export function convertToImportMap(namedImportMap: NamedImportMap): ImportMap {
duration: namedImportMap.Duration,
cue: namedImportMap.Cue,
title: namedImportMap.Title,
presenter: namedImportMap.Presenter,
subtitle: namedImportMap.Subtitle,
isPublic: namedImportMap['Is Public'],
skip: namedImportMap.Skip,
note: namedImportMap.Note,
@@ -33,8 +33,6 @@ export default function PreviewRundown(props: PreviewRundownProps) {
<th>Type</th>
<th>Cue</th>
<th>Title</th>
<th>Subtitle</th>
<th>Presenter</th>
<th>Time Start</th>
<th>Time End</th>
<th>Duration</th>
@@ -85,8 +83,6 @@ export default function PreviewRundown(props: PreviewRundownProps) {
</td>
<td className={style.nowrap}>{event.cue}</td>
<td>{event.title}</td>
<td>{event.subtitle}</td>
<td>{event.presenter}</td>
<td>{millisToString(event.timeStart)}</td>
<td>{millisToString(event.timeEnd)}</td>
<td>{millisToString(event.duration)}</td>
@@ -19,8 +19,6 @@ exports[`makeTable() > returns array of arrays with given fields 1`] = `
"Colour",
"Cue",
"Title",
"Subtitle",
"Presenter",
"Note",
"Is Public? (x)",
"Skip?",
@@ -35,8 +33,6 @@ exports[`makeTable() > returns array of arrays with given fields 1`] = `
"",
"test title 1",
"",
"",
"",
"x",
"",
"",
@@ -27,15 +27,13 @@ describe('parseField()', () => {
});
it('returns an empty string on undefined fields', () => {
expect(parseField('presenter')).toBe('');
expect(parseField('title')).toBe('');
});
describe('simply returns any other value in any other field', () => {
const testFields = [
{ field: 'nothing', value: '123' },
{ field: 'title', value: 'test' },
{ field: 'presenter', value: 'test' },
{ field: 'subtitle', value: 'test' },
{ field: 'note', value: 'test' },
{ field: 'colour', value: 'test' },
];
@@ -57,7 +55,6 @@ describe('makeTable()', () => {
const tableData = [
{
title: 'test title 1',
presenter: '',
timeStart: 0,
timeEnd: 0,
isPublic: 'x',
@@ -61,6 +61,7 @@ export function makeCuesheetColumns(customFields: CustomFields): ColumnDef<Ontim
header: customFields[key].label,
meta: { colour: customFields[key].colour },
cell: MakeCustomField,
size: 250,
}));
return [
@@ -104,24 +105,14 @@ export function makeCuesheetColumns(customFields: CustomFields): ColumnDef<Ontim
id: 'title',
header: 'Title',
cell: (row) => row.getValue(),
},
{
accessorKey: 'subtitle',
id: 'subtitle',
header: 'Subtitle',
cell: (row) => row.getValue(),
},
{
accessorKey: 'presenter',
id: 'presenter',
header: 'Presenter',
cell: (row) => row.getValue(),
size: 250,
},
{
accessorKey: 'note',
id: 'note',
header: 'Note',
cell: (row) => row.getValue(),
size: 250,
},
...dynamicCustomFields,
];
@@ -56,8 +56,6 @@ export const makeTable = (headerData: ProjectData, rundown: OntimeRundown, custo
'colour',
'cue',
'title',
'subtitle',
'presenter',
'note',
'isPublic',
'skip',
@@ -72,8 +70,6 @@ export const makeTable = (headerData: ProjectData, rundown: OntimeRundown, custo
'Colour',
'Cue',
'Title',
'Subtitle',
'Presenter',
'Note',
'Is Public? (x)',
'Skip?',
@@ -10,8 +10,6 @@ export const defaultColumnOrder: OntimeEntryCommonKeys[] = [
'timeEnd',
'duration',
'title',
'subtitle',
'presenter',
'note',
];
@@ -15,7 +15,7 @@ import useRundown from '../../common/hooks-query/useRundown';
import useSettings from '../../common/hooks-query/useSettings';
import { debounce } from '../../common/utils/debounce';
import { getDefaultFormat } from '../../common/utils/time';
import { isStringBoolean } from '../../common/utils/viewUtils';
import { getPropertyValue, isStringBoolean } from '../viewers/common/viewUtils';
import EditModal from './edit-modal/EditModal';
import FollowButton from './follow-button/FollowButton';
@@ -27,7 +27,7 @@ import style from './Operator.module.scss';
const selectedOffset = 50;
type TitleFields = Pick<OntimeEvent, 'title' | 'subtitle' | 'presenter'>;
type TitleFields = Pick<OntimeEvent, 'title'>;
export type EditEvent = Pick<OntimeEvent, 'id' | 'cue'> & { fieldLabel?: string; fieldValue: string };
export type PartialEdit = EditEvent & {
field: keyof CustomFields;
@@ -129,7 +129,7 @@ export default function Operator() {
const canEdit = shouldEdit && subscribe;
const main = searchParams.get('main') as keyof TitleFields | null;
const secondary = searchParams.get('secondary') as keyof TitleFields | null;
const secondary = searchParams.get('secondary');
const defaultFormat = getDefaultFormat(settings?.timeFormat);
const operatorOptions = getOperatorOptions(customFields, defaultFormat);
@@ -176,7 +176,7 @@ export default function Operator() {
}
const mainField = main ? entry?.[main] || entry.title : entry.title;
const secondaryField = secondary ? entry?.[secondary] || entry.subtitle : entry.subtitle;
const secondaryField = getPropertyValue(entry, secondary) ?? '';
const subscribedData = entry.custom[subscribe]?.value;
return (
@@ -102,7 +102,7 @@
}
.value {
color: $orange-500
color: $orange-500;
}
}
@@ -16,7 +16,7 @@ import style from './EventEditor.module.scss';
export type EventEditorSubmitActions = keyof OntimeEvent;
export type EditorUpdateFields = 'cue' | 'title' | 'presenter' | 'subtitle' | 'note' | 'colour' | CustomFieldLabel; // TODO: keyof customFields
export type EditorUpdateFields = 'cue' | 'title' | 'note' | 'colour' | CustomFieldLabel;
export default function EventEditor() {
const selectedEvents = useEventSelection((state) => state.selectedEvents);
@@ -90,8 +90,6 @@ export default function EventEditor() {
eventId={event.id}
cue={event.cue}
title={event.title}
presenter={event.presenter}
subtitle={event.subtitle}
note={event.note}
colour={event.colour}
handleSubmit={handleSubmit}
@@ -70,7 +70,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
: '';
return (
<>
<div className={style.column}>
<div>
<div className={style.inputLabel}>Event schedule</div>
<div className={style.inline}>
@@ -139,7 +139,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
{isPublic ? 'Public' : 'Private'}
</label>
</div>
</>
</div>
);
};
@@ -10,19 +10,17 @@ import EventTextInput from './EventTextInput';
import style from '../EventEditor.module.scss';
interface EventEditorLeftProps {
interface EventEditorTitlesProps {
eventId: string;
cue: string;
title: string;
presenter: string;
subtitle: string;
note: string;
colour: string;
handleSubmit: (field: EditorUpdateFields, value: string) => void;
}
const EventEditorTitles = (props: EventEditorLeftProps) => {
const { eventId, cue, title, presenter, subtitle, note, colour, handleSubmit } = props;
const EventEditorTitles = (props: EventEditorTitlesProps) => {
const { eventId, cue, title, note, colour, handleSubmit } = props;
const cueSubmitHandler = (_field: string, newValue: string) => {
handleSubmit('cue', sanitiseCue(newValue));
@@ -51,8 +49,6 @@ const EventEditorTitles = (props: EventEditorLeftProps) => {
<SwatchSelect name='colour' value={colour} handleChange={handleSubmit} />
</div>
<EventTextInput field='title' label='Title' initialValue={title} submitHandler={handleSubmit} />
<EventTextInput field='presenter' label='Presenter' initialValue={presenter} submitHandler={handleSubmit} />
<EventTextInput field='subtitle' label='Subtitle' initialValue={subtitle} submitHandler={handleSubmit} />
<EventTextArea field='note' label='Note' initialValue={note} submitHandler={handleSubmit} />
</div>
);
@@ -1,8 +1,18 @@
import { ComponentType, useMemo } from 'react';
import { ViewExtendedTimer } from 'common/models/TimeManager.type';
import { Message, OntimeEvent, ProjectData, Settings, SupportedEvent, TimerMessage, ViewSettings } from 'ontime-types';
import {
CustomFields,
Message,
OntimeEvent,
ProjectData,
Settings,
SupportedEvent,
TimerMessage,
ViewSettings,
} from 'ontime-types';
import { useStore } from 'zustand';
import useCustomFields from '../../common/hooks-query/useCustomFields';
import useProjectData from '../../common/hooks-query/useProjectData';
import { useFlatRundown } from '../../common/hooks-query/useRundown';
import useSettings from '../../common/hooks-query/useSettings';
@@ -11,25 +21,26 @@ import { runtimeStore } from '../../common/stores/runtime';
import { useViewOptionsStore } from '../../common/stores/viewOptions';
type WithDataProps = {
backstageEvents: OntimeEvent[];
customFields: CustomFields;
eventNext: OntimeEvent | null;
eventNow: OntimeEvent | null;
events: OntimeEvent[];
external: Message;
general: ProjectData;
isMirrored: boolean;
lower: Message;
nextId: string | null;
onAir: boolean;
pres: TimerMessage;
publ: Message;
lower: Message;
external: Message;
eventNow: OntimeEvent | null;
publicEventNow: OntimeEvent | null;
eventNext: OntimeEvent | null;
publicEventNext: OntimeEvent | null;
time: ViewExtendedTimer;
events: OntimeEvent[];
backstageEvents: OntimeEvent[];
selectedId: string | null;
publicEventNow: OntimeEvent | null;
publicSelectedId: string | null;
nextId: string | null;
general: ProjectData;
viewSettings: ViewSettings;
selectedId: string | null;
settings: Settings | undefined;
onAir: boolean;
time: ViewExtendedTimer;
viewSettings: ViewSettings;
};
function getDisplayName(Component: React.ComponentType<any>): string {
@@ -46,6 +57,7 @@ const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
const { data: project } = useProjectData();
const { data: viewSettings } = useViewSettings();
const { data: settings } = useSettings();
const { data: customFields } = useCustomFields();
const publicEvents = useMemo(() => {
if (Array.isArray(rundownData)) {
@@ -83,25 +95,26 @@ const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
return (
<Component
{...props}
backstageEvents={rundownData}
customFields={customFields}
eventNext={eventNext}
eventNow={eventNow}
events={publicEvents}
external={message.external}
general={project}
isMirrored={isMirrored}
lower={message.lower}
nextId={nextId}
onAir={onAir}
pres={message.timer}
publ={message.public}
lower={message.lower}
external={message.external}
eventNow={eventNow}
publicEventNow={publicEventNow}
eventNext={eventNext}
publicEventNext={publicEventNext}
time={TimeManagerType}
events={publicEvents}
backstageEvents={rundownData}
selectedId={selectedId}
publicEventNow={publicEventNow}
publicSelectedId={publicSelectedId}
viewSettings={viewSettings}
selectedId={selectedId}
settings={settings}
nextId={nextId}
general={project}
onAir={onAir}
time={TimeManagerType}
viewSettings={viewSettings}
/>
);
};
@@ -1,7 +1,8 @@
import { useEffect, useState } from 'react';
import QRCode from 'react-qr-code';
import { useSearchParams } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { Message, OntimeEvent, ProjectData, Settings, SupportedEvent, ViewSettings } from 'ontime-types';
import { CustomFields, Message, OntimeEvent, ProjectData, Settings, SupportedEvent, ViewSettings } from 'ontime-types';
import { millisToString, removeLeadingZero } from 'ontime-utils';
import { overrideStylesURL } from '../../../common/api/apiConstants';
@@ -19,10 +20,12 @@ import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
import { titleVariants } from '../common/animation';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import { getPropertyValue } from '../common/viewUtils';
import './Backstage.scss';
interface BackstageProps {
customFields: CustomFields;
isMirrored: boolean;
publ: Message;
eventNow: OntimeEvent | null;
@@ -36,12 +39,24 @@ interface BackstageProps {
}
export default function Backstage(props: BackstageProps) {
const { isMirrored, publ, eventNow, eventNext, time, backstageEvents, selectedId, general, viewSettings, settings } =
props;
const {
customFields,
isMirrored,
publ,
eventNow,
eventNext,
time,
backstageEvents,
selectedId,
general,
viewSettings,
settings,
} = props;
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const { getLocalizedString } = useTranslation();
const [blinkClass, setBlinkClass] = useState(false);
const [searchParams] = useSearchParams();
// Set window title
useEffect(() => {
@@ -74,12 +89,16 @@ export default function Backstage(props: BackstageProps) {
const showPublicMessage = publ.text && publ.visible;
const showProgress = time.playback !== 'stop';
const secondarySource = searchParams.get('secondary-src');
const secondaryTextNext = getPropertyValue(eventNext, secondarySource);
const secondaryTextNow = getPropertyValue(eventNow, secondarySource);
let stageTimer = millisToString(time.current, { fallback: '- - : - -' });
stageTimer = removeLeadingZero(stageTimer);
const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0);
const defaultFormat = getDefaultFormat(settings?.timeFormat);
const backstageOptions = getBackstageOptions(defaultFormat);
const backstageOptions = getBackstageOptions(defaultFormat, customFields);
return (
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
@@ -111,12 +130,7 @@ export default function Backstage(props: BackstageProps) {
animate='visible'
exit='exit'
>
<TitleCard
label='now'
title={eventNow.title}
subtitle={eventNow.subtitle}
presenter={eventNow.presenter}
/>
<TitleCard label='now' title={eventNow.title} secondary={secondaryTextNow} />
<div className='timer-group'>
<div className='aux-timers'>
<div className='aux-timers__label'>{getLocalizedString('common.started_at')}</div>
@@ -151,12 +165,7 @@ export default function Backstage(props: BackstageProps) {
animate='visible'
exit='exit'
>
<TitleCard
label='next'
title={eventNext.title}
subtitle={eventNext.subtitle}
presenter={eventNext.presenter}
/>
<TitleCard label='next' title={eventNext.title} secondary={secondaryTextNext} />
</motion.div>
)}
</AnimatePresence>
@@ -0,0 +1,56 @@
import { MaybeString, OntimeEvent, TimerType } from 'ontime-types';
import type { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
type TimerTypeParams = Pick<ViewExtendedTimer, 'timerType' | 'current' | 'elapsed' | 'clock'>;
export function getTimerByType(timerObject?: TimerTypeParams): number | null {
if (!timerObject) {
return null;
}
switch (timerObject.timerType) {
case TimerType.CountDown:
case TimerType.TimeToEnd:
return timerObject.current;
case TimerType.CountUp:
return Math.abs(timerObject.elapsed ?? 0);
case TimerType.Clock:
return timerObject.clock;
default: {
const exhaustiveCheck: never = timerObject.timerType;
return exhaustiveCheck;
}
}
}
export function isStringBoolean(text: string | null) {
if (text === null) {
return false;
}
return text?.toLowerCase() === 'true' || text === '1';
}
/**
* Retrieves a dynamic property from an event
* Considers custom fields
*/
export function getPropertyValue(event: OntimeEvent | null, property: MaybeString): string | undefined {
if (!event) {
return undefined;
}
if (typeof property !== 'string') {
return undefined;
}
if (property.startsWith('custom-')) {
const field = property.split('custom-')[1];
return event.custom?.[field]?.value;
}
return event[property as keyof OntimeEvent] as string;
}
export function capitaliseFirstLetter(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
@@ -1,25 +0,0 @@
import { TimerType } from 'ontime-types';
import type { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
type TimerTypeParams = Pick<ViewExtendedTimer, 'timerType' | 'current' | 'elapsed' | 'clock'>;
export function getTimerByType(timerObject?: TimerTypeParams): number | null {
if (!timerObject) {
return null;
}
switch (timerObject.timerType) {
case TimerType.CountDown:
case TimerType.TimeToEnd:
return timerObject.current;
case TimerType.CountUp:
return Math.abs(timerObject.elapsed ?? 0);
case TimerType.Clock:
return timerObject.clock;
default: {
const exhaustiveCheck: never = timerObject.timerType;
return exhaustiveCheck;
}
}
}
@@ -69,7 +69,7 @@
.data-bottom,
.data-top {
padding: 0 3vw;
font-family: var(--lowerThird-font-family-override, 'Lato');
font-family: var(--lowerThird-font-family-override), Lato, Arial, sans-serif;
text-align: var(--lowerThird-text-align-override, left);
white-space: nowrap;
}
@@ -1,22 +1,16 @@
import { useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Message, OntimeEvent, ViewSettings } from 'ontime-types';
import { CustomFields, Message, OntimeEvent, ViewSettings } from 'ontime-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import { LOWER_THIRD_OPTIONS } from '../../../common/components/view-params-editor/constants';
import { getLowerThirdOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { getPropertyValue } from '../common/viewUtils';
import './LowerThird.scss';
enum SrcKeys {
Title = 'title',
Subtitle = 'subtitle',
Presenter = 'presenter',
LowerMsg = 'lowerMsg',
}
enum TriggerType {
Event = 'event',
Manual = 'manual',
@@ -25,8 +19,8 @@ enum TriggerType {
type LowerOptions = {
width: number;
trigger: TriggerType;
topSrc: SrcKeys;
bottomSrc: SrcKeys;
topSrc: string;
bottomSrc: string;
topColour: string;
bottomColour: string;
topBg: string;
@@ -41,6 +35,7 @@ type LowerOptions = {
};
interface LowerProps {
customFields: CustomFields;
eventNow: OntimeEvent | null;
viewSettings: ViewSettings;
lower: Message;
@@ -49,8 +44,8 @@ interface LowerProps {
const defaultOptions: Readonly<LowerOptions> = {
width: 45,
trigger: TriggerType.Event,
topSrc: SrcKeys.Title,
bottomSrc: SrcKeys.Subtitle,
topSrc: 'title',
bottomSrc: 'lowerMsg',
topColour: '000000ff',
bottomColour: '000000ff',
topBg: '00000000',
@@ -65,7 +60,7 @@ const defaultOptions: Readonly<LowerOptions> = {
};
export default function LowerThird(props: LowerProps) {
const { eventNow, lower, viewSettings } = props;
const { customFields, eventNow, lower, viewSettings } = props;
const [searchParams] = useSearchParams();
const options = useMemo(() => {
@@ -81,12 +76,12 @@ export default function LowerThird(props: LowerProps) {
newOptions.trigger = trigger;
}
const topSrc = Object.values(SrcKeys).find((s) => s === searchParams.get('top-src'));
const topSrc = searchParams.get('top-src');
if (topSrc) {
newOptions.topSrc = topSrc;
}
const bottomSrc = Object.values(SrcKeys).find((s) => s === searchParams.get('bottom-src'));
const bottomSrc = searchParams.get('bottom-src');
if (bottomSrc) {
newOptions.bottomSrc = bottomSrc;
}
@@ -144,34 +139,14 @@ export default function LowerThird(props: LowerProps) {
return newOptions;
}, [searchParams]);
const [playState, setPlayState] = useState<'pre' | 'in' | 'out'>('pre');
useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const [playState, setPlayState] = useState<'pre' | 'in' | 'out'>('pre');
// set window title
useEffect(() => {
document.title = 'ontime - Lower Third';
}, []);
const topText = useMemo(() => {
if (options.topSrc === SrcKeys.LowerMsg) {
return lower.text;
} else if (eventNow) {
return eventNow[options.topSrc];
}
return '';
}, [eventNow, lower.text, options]);
const bottomText = useMemo(() => {
if (options.bottomSrc === SrcKeys.LowerMsg) {
return lower.text;
} else if (eventNow) {
return eventNow[options.bottomSrc];
}
return '';
}, [eventNow, lower.text, options]);
const transition = `${options.transition}s`;
const trigger = useMemo(() => {
if (options.trigger === TriggerType.Event) {
return eventNow?.id;
@@ -181,6 +156,7 @@ export default function LowerThird(props: LowerProps) {
return false;
}, [eventNow?.id, lower.visible, options.trigger]);
// coordinate load-unload of lower third
useEffect(() => {
if (options.trigger === TriggerType.Event && trigger) {
setPlayState('in');
@@ -197,10 +173,26 @@ export default function LowerThird(props: LowerProps) {
return () => null;
}, [options.delay, options.transition, options.trigger, trigger]);
const topText = useMemo(() => {
if (options.topSrc === 'lowerMsg') {
return lower.text;
}
return getPropertyValue(eventNow, options.topSrc) ?? '';
}, [eventNow, lower.text, options]);
const bottomText = useMemo(() => {
if (options.bottomSrc === 'lowerMsg') {
return lower.text;
}
return getPropertyValue(eventNow, options.bottomSrc) ?? '';
}, [eventNow, lower.text, options]);
const transition = `${options.transition}s`;
return (
<div className='lower-third' style={{ backgroundColor: `#${options.key}` }}>
<NavigationMenu />
<ViewParamsEditor paramFields={LOWER_THIRD_OPTIONS} />
<ViewParamsEditor paramFields={getLowerThirdOptions(customFields)} />
<div
className={`container container--${playState}`}
style={{ minWidth: `${options.width}vw`, animationDuration: transition }}
@@ -11,9 +11,8 @@ import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { OverridableOptions } from '../../../common/models/View.types';
import { timerPlaceholder } from '../../../common/utils/styleUtils';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
import { getTimerByType } from '../common/viewerUtils';
import { getTimerByType, isStringBoolean } from '../common/viewUtils';
import './MinimalTimer.scss';
@@ -1,7 +1,8 @@
import { useEffect } from 'react';
import QRCode from 'react-qr-code';
import { useSearchParams } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { Message, OntimeEvent, ProjectData, Settings, ViewSettings } from 'ontime-types';
import { CustomFields, Message, OntimeEvent, ProjectData, Settings, ViewSettings } from 'ontime-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
@@ -17,10 +18,12 @@ import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
import { titleVariants } from '../common/animation';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import { getPropertyValue } from '../common/viewUtils';
import './Public.scss';
interface BackstageProps {
customFields: CustomFields;
isMirrored: boolean;
publ: Message;
publicEventNow: OntimeEvent | null;
@@ -35,6 +38,7 @@ interface BackstageProps {
export default function Public(props: BackstageProps) {
const {
customFields,
isMirrored,
publ,
publicEventNow,
@@ -49,7 +53,9 @@ export default function Public(props: BackstageProps) {
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const { getLocalizedString } = useTranslation();
const [searchParams] = useSearchParams();
// set window title
useEffect(() => {
document.title = 'ontime - Public Screen';
}, []);
@@ -64,7 +70,11 @@ export default function Public(props: BackstageProps) {
const qrSize = Math.max(window.innerWidth / 15, 128);
const defaultFormat = getDefaultFormat(settings?.timeFormat);
const publicOptions = getPublicOptions(defaultFormat);
const publicOptions = getPublicOptions(defaultFormat, customFields);
const secondarySource = searchParams.get('secondary-src');
const secondaryTextNext = getPropertyValue(publicEventNext, secondarySource);
const secondaryTextNow = getPropertyValue(publicEventNow, secondarySource);
return (
<div className={`public-screen ${isMirrored ? 'mirror' : ''}`} data-testid='public-view'>
@@ -89,12 +99,7 @@ export default function Public(props: BackstageProps) {
animate='visible'
exit='exit'
>
<TitleCard
label='now'
title={publicEventNow.title}
subtitle={publicEventNow.subtitle}
presenter={publicEventNow.presenter}
/>
<TitleCard label='now' title={publicEventNow.title} secondary={secondaryTextNow} />
</motion.div>
)}
</AnimatePresence>
@@ -109,12 +114,7 @@ export default function Public(props: BackstageProps) {
animate='visible'
exit='exit'
>
<TitleCard
label='next'
title={publicEventNext.title}
subtitle={publicEventNext.subtitle}
presenter={publicEventNext.presenter}
/>
<TitleCard label='next' title={publicEventNext.title} secondary={secondaryTextNext} />
</motion.div>
)}
</AnimatePresence>
@@ -12,8 +12,8 @@ import useFitText from '../../../common/hooks/useFitText';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import { isStringBoolean } from '../common/viewUtils';
import { secondsInMillis, trimRundown } from './studioClock.utils';
@@ -1,7 +1,16 @@
import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { Message, OntimeEvent, Playback, Settings, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
import {
CustomFields,
Message,
OntimeEvent,
Playback,
Settings,
TimerMessage,
TimerType,
ViewSettings,
} from 'ontime-types';
import { millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils';
import { overrideStylesURL } from '../../../common/api/apiConstants';
@@ -14,10 +23,9 @@ import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { timerPlaceholder } from '../../../common/utils/styleUtils';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import { getTimerByType } from '../common/viewerUtils';
import { getPropertyValue, getTimerByType, isStringBoolean } from '../common/viewUtils';
import './Timer.scss';
@@ -38,18 +46,19 @@ const titleVariants = {
};
interface TimerProps {
customFields: CustomFields;
eventNext: OntimeEvent | null;
eventNow: OntimeEvent | null;
external: Message;
isMirrored: boolean;
pres: TimerMessage;
external: Message;
eventNow: OntimeEvent | null;
eventNext: OntimeEvent | null;
settings: Settings | undefined;
time: ViewExtendedTimer;
viewSettings: ViewSettings;
settings: Settings | undefined;
}
export default function Timer(props: TimerProps) {
const { isMirrored, pres, eventNow, eventNext, time, viewSettings, external, settings } = props;
const { customFields, isMirrored, pres, eventNow, eventNext, time, viewSettings, external, settings } = props;
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const { getLocalizedString } = useTranslation();
const [searchParams] = useSearchParams();
@@ -92,6 +101,10 @@ export default function Timer(props: TimerProps) {
const hideTimerSeconds = searchParams.get('hideTimerSeconds');
userOptions.hideTimerSeconds = isStringBoolean(hideTimerSeconds);
const secondarySource = searchParams.get('secondary-src');
const secondaryTextNow = getPropertyValue(eventNow, secondarySource);
const secondaryTextNext = getPropertyValue(eventNext, secondarySource);
const showOverlay = pres.text !== '' && pres.visible;
const isPlaying = time.playback !== Playback.Pause;
@@ -143,7 +156,7 @@ export default function Timer(props: TimerProps) {
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
const defaultFormat = getDefaultFormat(settings?.timeFormat);
const timerOptions = getTimerOptions(defaultFormat);
const timerOptions = getTimerOptions(defaultFormat, customFields);
return (
<div className={showFinished ? `${baseClasses} stage-timer--finished` : baseClasses} data-testid='timer-view'>
@@ -201,7 +214,7 @@ export default function Timer(props: TimerProps) {
{!userOptions.hideCards && (
<>
<AnimatePresence>
{eventNow && !finished && (
{eventNow && (
<motion.div
className='event now'
key='now'
@@ -210,12 +223,7 @@ export default function Timer(props: TimerProps) {
animate='visible'
exit='exit'
>
<TitleCard
label='now'
title={eventNow.title}
subtitle={eventNow.subtitle}
presenter={eventNow.presenter}
/>
<TitleCard label='now' title={eventNow.title} secondary={secondaryTextNow} />
</motion.div>
)}
</AnimatePresence>
@@ -230,12 +238,7 @@ export default function Timer(props: TimerProps) {
animate='visible'
exit='exit'
>
<TitleCard
label='next'
title={eventNext.title}
subtitle={eventNext.subtitle}
presenter={eventNext.presenter}
/>
<TitleCard label='next' title={eventNext.title} secondary={secondaryTextNext} />
</motion.div>
)}
</AnimatePresence>