diff --git a/apps/client/src/common/components/view-params-editor/ParamInput.tsx b/apps/client/src/common/components/view-params-editor/ParamInput.tsx index 56380e85bf..cec09b0f9f 100644 --- a/apps/client/src/common/components/view-params-editor/ParamInput.tsx +++ b/apps/client/src/common/components/view-params-editor/ParamInput.tsx @@ -27,6 +27,10 @@ export default function ParamInput(props: EditFormInputProps) { const { paramField } = props; const { id, type, defaultValue } = paramField; + if (type === 'persist') { + return null; + } + if (type === 'option') { const optionFromParams = searchParams.get(id); const defaultOptionValue = optionFromParams || defaultValue; diff --git a/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss b/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss index 53de3badc5..efe885c525 100644 --- a/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss +++ b/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss @@ -13,14 +13,24 @@ color: $label-gray; display: flex; flex-direction: column; - gap: 0.25rem + gap: 0.25rem; } -.columnSection { +.section { + color: $ui-white; + font-size: 1rem; + + &:not(:first-child) { + margin-top: 2rem; + } +} + +.fieldSet { display: flex; padding: $section-spacing 0; flex-direction: column; gap: $element-spacing; + margin-left: 0.5rem; } .title { diff --git a/apps/client/src/common/components/view-params-editor/ViewParamsEditor.tsx b/apps/client/src/common/components/view-params-editor/ViewParamsEditor.tsx index c2e6291970..d3ebd13103 100644 --- a/apps/client/src/common/components/view-params-editor/ViewParamsEditor.tsx +++ b/apps/client/src/common/components/view-params-editor/ViewParamsEditor.tsx @@ -13,7 +13,7 @@ import { } from '@chakra-ui/react'; import ParamInput from './ParamInput'; -import { ParamField } from './types'; +import { isSection, ViewOption } from './types'; import style from './ViewParamsEditor.module.scss'; @@ -22,25 +22,36 @@ type ViewParamsObj = { [key: string]: string | FormDataEntryValue }; /** * Makes a new URLSearchParams object from the given params object */ -const getURLSearchParamsFromObj = (paramsObj: ViewParamsObj, paramFields: ParamField[]) => { - const defaultValues = paramFields.reduce>((acc, { id, defaultValue }) => { - acc[id] = String(defaultValue); - return acc; - }, {}); +const getURLSearchParamsFromObj = (paramsObj: ViewParamsObj, paramFields: ViewOption[]) => { + const newSearchParams = new URLSearchParams(); + + // Convert paramFields to an object that contains default values + const defaultValues: Record = {}; + paramFields.forEach((option) => { + if (!isSection(option)) { + defaultValues[option.id] = String(option.defaultValue); + } + + // extract persisted values + if ('type' in option && option.type === 'persist') { + newSearchParams.set(option.id, option.value); + } + }); - return Object.entries(paramsObj).reduce((newSearchParams, [id, value]) => { + // compare which values are different from the default values + Object.entries(paramsObj).forEach(([id, value]) => { if (typeof value === 'string' && value.length && defaultValues[id] !== value) { newSearchParams.set(id, value); } - return newSearchParams; - }, new URLSearchParams()); + }); + return newSearchParams; }; interface EditFormDrawerProps { - paramFields: ParamField[]; + viewOptions: ViewOption[]; } -export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) { +export default function ViewParamsEditor({ viewOptions }: EditFormDrawerProps) { const [searchParams, setSearchParams] = useSearchParams(); const { isOpen, onClose, onOpen } = useDisclosure(); @@ -68,7 +79,7 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) { formEvent.preventDefault(); const newParamsObject = Object.fromEntries(new FormData(formEvent.currentTarget)); - const newSearchParams = getURLSearchParamsFromObj(newParamsObject, paramFields); + const newSearchParams = getURLSearchParamsFromObj(newParamsObject, viewOptions); setSearchParams(newSearchParams); onClose(); @@ -85,15 +96,29 @@ export default function ViewParamsEditor({ paramFields }: EditFormDrawerProps) {
- {paramFields.map((field) => ( -
- -
- ))} + {viewOptions.map((option) => { + if (isSection(option)) { + return ( +
+ {option.section} +
+ ); + } + + if (option.type === 'persist') { + return null; + } + + return ( +
+ +
+ ); + })}
diff --git a/apps/client/src/common/components/view-params-editor/constants.ts b/apps/client/src/common/components/view-params-editor/constants.ts index a5023ccaa6..be4a2cc70b 100644 --- a/apps/client/src/common/components/view-params-editor/constants.ts +++ b/apps/client/src/common/components/view-params-editor/constants.ts @@ -1,15 +1,15 @@ import { CustomFields } from 'ontime-types'; -import { type ParamField } from './types'; +import type { ParamField } from './types'; -const makeOptionsFromCustomFields = (customFields: CustomFields, additionalOptions?: Record) => { +export const makeOptionsFromCustomFields = (customFields: CustomFields, additionalOptions?: Record) => { const customFieldOptions = Object.entries(customFields).reduce((acc, [key, value]) => { return { ...acc, [`custom-${key}`]: `Custom: ${value.label}` }; }, additionalOptions ?? {}); return customFieldOptions; }; -const getTimeOption = (timeFormat: string): ParamField => { +export const getTimeOption = (timeFormat: string): ParamField => { const placeholder = `${timeFormat} (default)`; return { id: 'timeformat', @@ -20,7 +20,7 @@ const getTimeOption = (timeFormat: string): ParamField => { }; }; -const hideTimerSeconds: ParamField = { +export const hideTimerSeconds: ParamField = { id: 'hideTimerSeconds', title: 'Hide seconds in timer', description: 'Whether to hide seconds in the running timer', @@ -28,494 +28,10 @@ const hideTimerSeconds: ParamField = { defaultValue: false, }; -const showLeadingZeros: ParamField = { +export const showLeadingZeros: ParamField = { id: 'showLeadingZeros', title: 'Show leading zeros in timer', description: 'Whether to show leading zeros in the running timer', type: 'boolean', defaultValue: false, }; - -export const getClockOptions = (timeFormat: string): ParamField[] => [ - getTimeOption(timeFormat), - { - id: 'key', - title: 'Key Colour', - description: 'Background colour in hexadecimal', - prefix: '#', - type: 'string', - placeholder: '00000000 (default)', - }, - { - id: 'text', - title: 'Text Colour', - description: 'Text colour in hexadecimal', - prefix: '#', - type: 'string', - placeholder: 'fffff (default)', - }, - { - id: 'textbg', - title: 'Text Background', - description: 'Colour of text background in hexadecimal', - prefix: '#', - type: 'string', - placeholder: '00000000 (default)', - }, - { - id: 'font', - title: 'Font', - description: 'Font family, will use the fonts available in the system', - type: 'string', - placeholder: 'Arial Black (default)', - }, - { - id: 'size', - title: 'Text Size', - description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)', - type: 'number', - placeholder: '1 (default)', - }, - { - id: 'alignx', - title: 'Align Horizontal', - description: 'Moves the horizontally in page to start = left | center | end = right', - type: 'option', - values: { start: 'Start', center: 'Center', end: 'End' }, - defaultValue: 'center', - }, - { - id: 'offsetx', - title: 'Offset Horizontal', - description: 'Offsets the timer horizontal position by a given amount in pixels', - type: 'number', - placeholder: '0 (default)', - }, - { - id: 'aligny', - title: 'Align Vertical', - description: 'Moves the vertically in page to start = left | center | end = right', - type: 'option', - values: { start: 'Start', center: 'Center', end: 'End' }, - defaultValue: 'center', - }, - { - id: 'offsety', - title: 'Offset Vertical', - description: 'Offsets the timer vertical position by a given amount in pixels', - type: 'number', - placeholder: '0 (default)', - }, -]; - -export const getTimerOptions = (timeFormat: string, customFields: CustomFields): ParamField[] => { - const mainOptions = makeOptionsFromCustomFields(customFields, { title: 'Title' }); - const secondaryOptions = makeOptionsFromCustomFields(customFields, { note: 'Note' }); - return [ - getTimeOption(timeFormat), - hideTimerSeconds, - showLeadingZeros, - { - id: 'hideClock', - title: 'Hide Time Now', - description: 'Hides the Time Now field', - type: 'boolean', - defaultValue: false, - }, - { - id: 'main', - title: 'Main text', - description: 'Select the data source for the main text', - type: 'option', - values: mainOptions, - defaultValue: 'Title', - }, - { - 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, - { - id: 'key', - title: 'Key Colour', - description: 'Background colour in hexadecimal', - prefix: '#', - type: 'string', - placeholder: '00000000 (default)', - }, - { - id: 'text', - title: 'Text Colour', - description: 'Text colour in hexadecimal', - prefix: '#', - type: 'string', - placeholder: 'fffff (default)', - }, - { - id: 'textbg', - title: 'Text Background', - description: 'Colour of text background in hexadecimal', - prefix: '#', - type: 'string', - placeholder: '00000000 (default)', - }, - { - id: 'font', - title: 'Font', - description: 'Font family, will use the fonts available in the system', - type: 'string', - placeholder: 'Arial Black (default)', - }, - { - id: 'size', - title: 'Text Size', - description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)', - type: 'number', - placeholder: '1 (default)', - }, - { - id: 'alignx', - title: 'Align Horizontal', - description: 'Moves the horizontally in page to start = left | center | end = right', - type: 'option', - values: { start: 'Start', center: 'Center', end: 'End' }, - defaultValue: 'center', - }, - { - id: 'offsetx', - title: 'Offset Horizontal', - description: 'Offsets the timer horizontal position by a given amount in pixels', - type: 'number', - placeholder: '0 (default)', - }, - { - id: 'aligny', - title: 'Align Vertical', - description: 'Moves the vertically in page to start = left | center | end = right', - type: 'option', - values: { start: 'Start', center: 'Center', end: 'End' }, - defaultValue: 'center', - }, - { - id: 'offsety', - title: 'Offset Vertical', - description: 'Offsets the timer vertical position by a given amount in pixels', - type: 'number', - placeholder: '0 (default)', - }, - { - id: 'hideovertime', - title: 'Hide Overtime', - description: 'Whether to suppress overtime styles (red borders and red text)', - type: 'boolean', - defaultValue: false, - }, - { - id: 'hideendmessage', - title: 'Hide End Message', - description: 'Whether to hide end message and continue showing the clock if timer is in overtime', - type: 'boolean', - defaultValue: false, - }, -]; - -export const getLowerThirdOptions = (customFields: CustomFields): ParamField[] => { - const topSourceOptions = makeOptionsFromCustomFields(customFields, { - title: 'Title', - }); - - const bottomSourceOptions = makeOptionsFromCustomFields(customFields, { - title: 'Title', - none: 'None', - }); - - return [ - { - id: 'trigger', - title: 'Animation Trigger', - description: '', - type: 'option', - values: { - event: 'Event Load', - manual: 'Manual', - }, - defaultValue: 'manual', - }, - { - 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: 'none', - }, - { - 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 overflow', - type: 'number', - placeholder: '8 (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 overflow', - type: 'number', - placeholder: '8 (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 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.entries(customFields).reduce((acc, [key, field]) => { - return { ...acc, [key]: { value: key, label: field.label, colour: field.colour } }; - }, {}); - - return [ - getTimeOption(timeFormat), - { - id: 'hidepast', - title: 'Hide Past Events', - description: 'Whether to events that have passed', - type: 'boolean', - defaultValue: false, - }, - { - id: 'main', - title: 'Main data field', - description: 'Field to be shown in the first line of text', - type: 'option', - values: fieldOptions, - defaultValue: 'title', - }, - { - id: 'secondary', - title: 'Secondary data field', - description: 'Field to be shown in the second line of text', - type: 'option', - values: fieldOptions, - defaultValue: '', - }, - { - id: 'subscribe', - title: 'Highlight Field', - description: 'Choose a custom field to highlight', - type: 'multi-option', - values: customFieldSelect, - }, - { - id: 'shouldEdit', - title: 'Edit custom field', - description: 'Allows editing an events selected custom field by long pressing.', - type: 'boolean', - defaultValue: false, - }, - ]; -}; - -export const getCountdownOptions = (timeFormat: string): ParamField[] => [ - getTimeOption(timeFormat), - hideTimerSeconds, - { - id: 'showProjected', - title: 'Show projected time', - description: 'Whether to show the projected delay of an event (taken from runtime offset).', - type: 'boolean', - defaultValue: false, - }, -]; diff --git a/apps/client/src/common/components/view-params-editor/types.ts b/apps/client/src/common/components/view-params-editor/types.ts index ab80f3e1fc..530e233f92 100644 --- a/apps/client/src/common/components/view-params-editor/types.ts +++ b/apps/client/src/common/components/view-params-editor/types.ts @@ -1,3 +1,7 @@ +type ParamSection = { + section: string; +}; + type BaseField = { id: string; title: string; @@ -19,5 +23,15 @@ type MultiOptionsField = { type StringField = { type: 'string'; defaultValue?: string; prefix?: string; placeholder?: string }; type NumberField = { type: 'number'; defaultValue?: number; prefix?: string; placeholder?: string }; type BooleanField = { type: 'boolean'; defaultValue: boolean }; +type PersistedField = { type: 'persist'; defaultValue?: string; value: string }; + +export type ParamField = BaseField & + (StringField | BooleanField | NumberField | OptionsField | MultiOptionsField | PersistedField); +export type ViewOption = ParamSection | ParamField; -export type ParamField = BaseField & (StringField | BooleanField | NumberField | OptionsField | MultiOptionsField); +/** + * Type assertion utility checks whether an entry is a section separator + */ +export function isSection(entry: ViewOption): entry is ParamSection { + return 'section' in entry; +} diff --git a/apps/client/src/features/operator/Operator.tsx b/apps/client/src/features/operator/Operator.tsx index b95cc488a8..a63ce674c7 100644 --- a/apps/client/src/features/operator/Operator.tsx +++ b/apps/client/src/features/operator/Operator.tsx @@ -4,7 +4,6 @@ import { isOntimeEvent, OntimeEvent, SupportedEvent } from 'ontime-types'; import { getFirstEventNormal, getLastEventNormal } from 'ontime-utils'; import Empty from '../../common/components/state/Empty'; -import { getOperatorOptions } from '../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor'; import useFollowComponent from '../../common/hooks/useFollowComponent'; import { useOperator } from '../../common/hooks/useSocket'; @@ -22,6 +21,7 @@ import FollowButton from './follow-button/FollowButton'; import OperatorBlock from './operator-block/OperatorBlock'; import OperatorEvent from './operator-event/OperatorEvent'; import StatusBar from './status-bar/StatusBar'; +import { getOperatorOptions } from './operator.options'; import style from './Operator.module.scss'; @@ -134,7 +134,7 @@ export default function Operator() { return (
- + {editEvent && setEditEvent(null)} />} { + const fieldOptions = makeOptionsFromCustomFields(customFields, { title: 'Title', note: 'Note' }); + + const customFieldSelect = Object.entries(customFields).reduce((acc, [key, field]) => { + return { ...acc, [key]: { value: key, label: field.label, colour: field.colour } }; + }, {}); + + return [ + { section: 'Clock Options' }, + getTimeOption(timeFormat), + { section: 'Data sources' }, + + { + id: 'main', + title: 'Main data field', + description: 'Field to be shown in the first line of text', + type: 'option', + values: fieldOptions, + defaultValue: 'title', + }, + { + id: 'secondary', + title: 'Secondary data field', + description: 'Field to be shown in the second line of text', + type: 'option', + values: fieldOptions, + defaultValue: '', + }, + { + id: 'subscribe', + title: 'Highlight Field', + description: 'Choose a custom field to highlight', + type: 'multi-option', + values: customFieldSelect, + }, + { section: 'Element visibility' }, + { + id: 'hidepast', + title: 'Hide Past Events', + description: 'Whether to events that have passed', + type: 'boolean', + defaultValue: false, + }, + { + id: 'shouldEdit', + title: 'Edit custom field', + description: 'Allows editing an events selected custom field by long pressing.', + type: 'boolean', + defaultValue: false, + }, + ]; +}; diff --git a/apps/client/src/features/viewers/backstage/Backstage.tsx b/apps/client/src/features/viewers/backstage/Backstage.tsx index afad7e9c5f..dca3a69334 100644 --- a/apps/client/src/features/viewers/backstage/Backstage.tsx +++ b/apps/client/src/features/viewers/backstage/Backstage.tsx @@ -11,7 +11,6 @@ import Schedule from '../../../common/components/schedule/Schedule'; import { ScheduleProvider } from '../../../common/components/schedule/ScheduleContext'; import ScheduleNav from '../../../common/components/schedule/ScheduleNav'; import TitleCard from '../../../common/components/title-card/TitleCard'; -import { getBackstageOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; @@ -23,6 +22,8 @@ import { titleVariants } from '../common/animation'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import { getPropertyValue } from '../common/viewUtils'; +import { getBackstageOptions } from './backstage.options'; + import './Backstage.scss'; export const MotionTitleCard = motion(TitleCard); @@ -99,7 +100,7 @@ export default function Backstage(props: BackstageProps) { return (
- +
{general.title}
diff --git a/apps/client/src/features/viewers/backstage/backstage.options.ts b/apps/client/src/features/viewers/backstage/backstage.options.ts new file mode 100644 index 0000000000..77859bdfdd --- /dev/null +++ b/apps/client/src/features/viewers/backstage/backstage.options.ts @@ -0,0 +1,44 @@ +import { CustomFields } from 'ontime-types'; + +import { getTimeOption, makeOptionsFromCustomFields } from '../../../common/components/view-params-editor/constants'; +import { ViewOption } from '../../../common/components/view-params-editor/types'; + +export const getBackstageOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => { + const secondaryOptions = makeOptionsFromCustomFields(customFields, { note: 'Note' }); + + return [ + { section: 'Clock Options' }, + getTimeOption(timeFormat), + { section: 'Data sources' }, + { + 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: '', + }, + { section: 'Schedule options' }, + { + id: 'eventsPerPage', + title: 'Events per page', + description: 'Sets the number of events on the page, can cause overflow', + type: 'number', + placeholder: '8 (default)', + }, + { + 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, + }, + ]; +}; diff --git a/apps/client/src/features/viewers/clock/Clock.tsx b/apps/client/src/features/viewers/clock/Clock.tsx index ec6c7ccbe4..b95f46d803 100644 --- a/apps/client/src/features/viewers/clock/Clock.tsx +++ b/apps/client/src/features/viewers/clock/Clock.tsx @@ -2,7 +2,6 @@ import { useSearchParams } from 'react-router-dom'; import { Settings, ViewSettings } from 'ontime-types'; import { overrideStylesURL } from '../../../common/api/constants'; -import { getClockOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; @@ -11,6 +10,8 @@ import { OverridableOptions } from '../../../common/models/View.types'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; +import { getClockOptions } from './clock.options'; + import './Clock.scss'; interface ClockProps { @@ -130,7 +131,7 @@ export default function Clock(props: ClockProps) { }} data-testid='clock-view' > - + [ + { section: 'Clock Options' }, + getTimeOption(timeFormat), + { section: 'View style override' }, + { + id: 'key', + title: 'Key Colour', + description: 'Background colour in hexadecimal', + prefix: '#', + type: 'string', + placeholder: '00000000 (default)', + }, + { + id: 'text', + title: 'Text Colour', + description: 'Text colour in hexadecimal', + prefix: '#', + type: 'string', + placeholder: 'fffff (default)', + }, + { + id: 'textbg', + title: 'Text Background', + description: 'Colour of text background in hexadecimal', + prefix: '#', + type: 'string', + placeholder: '00000000 (default)', + }, + { + id: 'font', + title: 'Font', + description: 'Font family, will use the fonts available in the system', + type: 'string', + placeholder: 'Arial Black (default)', + }, + { + id: 'size', + title: 'Text Size', + description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)', + type: 'number', + placeholder: '1 (default)', + }, + { + id: 'alignx', + title: 'Align Horizontal', + description: 'Moves the horizontally in page to start = left | center | end = right', + type: 'option', + values: { start: 'Start', center: 'Center', end: 'End' }, + defaultValue: 'center', + }, + { + id: 'offsetx', + title: 'Offset Horizontal', + description: 'Offsets the timer horizontal position by a given amount in pixels', + type: 'number', + placeholder: '0 (default)', + }, + { + id: 'aligny', + title: 'Align Vertical', + description: 'Moves the vertically in page to start = left | center | end = right', + type: 'option', + values: { start: 'Start', center: 'Center', end: 'End' }, + defaultValue: 'center', + }, + { + id: 'offsety', + title: 'Offset Vertical', + description: 'Offsets the timer vertical position by a given amount in pixels', + type: 'number', + placeholder: '0 (default)', + }, +]; diff --git a/apps/client/src/features/viewers/countdown/Countdown.tsx b/apps/client/src/features/viewers/countdown/Countdown.tsx index 53040217c6..40ae2f3d39 100644 --- a/apps/client/src/features/viewers/countdown/Countdown.tsx +++ b/apps/client/src/features/viewers/countdown/Countdown.tsx @@ -12,7 +12,6 @@ import { } from 'ontime-types'; import { overrideStylesURL } from '../../../common/api/constants'; -import { getCountdownOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; @@ -23,6 +22,7 @@ import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import { getFormattedTimer, isStringBoolean } from '../common/viewUtils'; import { fetchTimerData, getTimerItems, TimerMessage } from './countdown.helpers'; +import { getCountdownOptions } from './countdown.options'; import CountdownSelect from './CountdownSelect'; import './Countdown.scss'; @@ -106,12 +106,24 @@ export default function Countdown(props: CountdownProps) { removeLeadingZero: false, }); + const persistParam = () => { + const eventId = searchParams.get('eventid'); + if (eventId !== null) { + return { id: 'eventid', value: eventId }; + } + const eventIndex = searchParams.get('event'); + if (eventIndex !== null) { + return { id: 'eventindex', value: eventIndex }; + } + return undefined; + }; + const defaultFormat = getDefaultFormat(settings?.timeFormat); - const timeOption = getCountdownOptions(defaultFormat); + const viewOptions = getCountdownOptions(defaultFormat, persistParam()); return (
- + {follow === null ? ( ) : ( diff --git a/apps/client/src/features/viewers/countdown/countdown.options.ts b/apps/client/src/features/viewers/countdown/countdown.options.ts new file mode 100644 index 0000000000..3afe965895 --- /dev/null +++ b/apps/client/src/features/viewers/countdown/countdown.options.ts @@ -0,0 +1,29 @@ +import { getTimeOption, hideTimerSeconds } from '../../../common/components/view-params-editor/constants'; +import { ViewOption } from '../../../common/components/view-params-editor/types'; + +const makePersistedField = (id: string, value: string): ViewOption => { + return { + id, + title: 'Used to keep the selection on submit', + description: 'Used to keep the selection on submit', + type: 'persist', + value, + }; +}; + +type Persisted = { id: string; value: string }; +export const getCountdownOptions = (timeFormat: string, persisted?: Persisted): ViewOption[] => [ + { section: 'Clock Options' }, + getTimeOption(timeFormat), + { section: 'Timer Options' }, + hideTimerSeconds, + { section: 'View behaviour' }, + { + id: 'showProjected', + title: 'Show projected time', + description: 'Show projected times for the event, as well as apply the runtime offset to the timer.', + type: 'boolean', + defaultValue: false, + }, + ...(persisted ? [makePersistedField(persisted.id, persisted.value)] : []), +]; diff --git a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx index 34dcf582d1..040e10e168 100644 --- a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx +++ b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx @@ -3,12 +3,13 @@ import { useSearchParams } from 'react-router-dom'; import { CustomFields, OntimeEvent, ViewSettings } from 'ontime-types'; import { overrideStylesURL } from '../../../common/api/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 { useWindowTitle } from '../../../common/hooks/useWindowTitle'; import { getPropertyValue } from '../common/viewUtils'; +import { getLowerThirdOptions } from './lowerThird.options'; + import './LowerThird.scss'; type LowerOptions = { @@ -182,7 +183,7 @@ export default function LowerThird(props: LowerProps) { return (
- +
{ + const topSourceOptions = makeOptionsFromCustomFields(customFields, { + title: 'Title', + }); + + const bottomSourceOptions = makeOptionsFromCustomFields(customFields, { + title: 'Title', + none: 'None', + }); + + return [ + { section: 'View behaviour' }, + { + id: 'trigger', + title: 'Animation Trigger', + description: '', + type: 'option', + values: { + event: 'Event Load', + manual: 'Manual', + }, + defaultValue: 'manual', + }, + { section: 'Data sources' }, + { + 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: 'none', + }, + { section: 'View style override' }, + { + 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)', + }, + ]; +}; diff --git a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx index 673dca07ca..2a6f23e316 100644 --- a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx +++ b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx @@ -2,7 +2,6 @@ import { useSearchParams } from 'react-router-dom'; import { Playback, TimerPhase, TimerType, ViewSettings } from 'ontime-types'; import { overrideStylesURL } from '../../../common/api/constants'; -import { MINIMAL_TIMER_OPTIONS } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; @@ -11,6 +10,8 @@ import { OverridableOptions } from '../../../common/models/View.types'; import { useTranslation } from '../../../translation/TranslationProvider'; import { getFormattedTimer, getTimerByType, isStringBoolean } from '../common/viewUtils'; +import { MINIMAL_TIMER_OPTIONS } from './minimalTimer.options'; + import './MinimalTimer.scss'; interface MinimalTimerProps { @@ -161,7 +162,7 @@ export default function MinimalTimer(props: MinimalTimerProps) { }} data-testid='minimal-timer' > - + {showEndMessage ? (
{viewSettings.endMessage}
) : ( diff --git a/apps/client/src/features/viewers/minimal-timer/minimalTimer.options.ts b/apps/client/src/features/viewers/minimal-timer/minimalTimer.options.ts new file mode 100644 index 0000000000..224bf33646 --- /dev/null +++ b/apps/client/src/features/viewers/minimal-timer/minimalTimer.options.ts @@ -0,0 +1,91 @@ +import { hideTimerSeconds } from '../../../common/components/view-params-editor/constants'; +import { ViewOption } from '../../../common/components/view-params-editor/types'; + +export const MINIMAL_TIMER_OPTIONS: ViewOption[] = [ + { section: 'Timer Options' }, + hideTimerSeconds, + { section: 'Element visibility' }, + { + id: 'hideovertime', + title: 'Hide Overtime', + description: 'Whether to suppress overtime styles (red borders and red text)', + type: 'boolean', + defaultValue: false, + }, + { + id: 'hideendmessage', + title: 'Hide End Message', + description: 'Whether to hide end message and continue showing the clock if timer is in overtime', + type: 'boolean', + defaultValue: false, + }, + { section: 'View style override' }, + { + id: 'key', + title: 'Key Colour', + description: 'Background colour in hexadecimal', + prefix: '#', + type: 'string', + placeholder: '00000000 (default)', + }, + { + id: 'text', + title: 'Text Colour', + description: 'Text colour in hexadecimal', + prefix: '#', + type: 'string', + placeholder: 'fffff (default)', + }, + { + id: 'textbg', + title: 'Text Background', + description: 'Colour of text background in hexadecimal', + prefix: '#', + type: 'string', + placeholder: '00000000 (default)', + }, + { + id: 'font', + title: 'Font', + description: 'Font family, will use the fonts available in the system', + type: 'string', + placeholder: 'Arial Black (default)', + }, + { + id: 'size', + title: 'Text Size', + description: 'Scales the current style (0.5 = 50% 1 = 100% 2 = 200%)', + type: 'number', + placeholder: '1 (default)', + }, + { + id: 'alignx', + title: 'Align Horizontal', + description: 'Moves the horizontally in page to start = left | center | end = right', + type: 'option', + values: { start: 'Start', center: 'Center', end: 'End' }, + defaultValue: 'center', + }, + { + id: 'offsetx', + title: 'Offset Horizontal', + description: 'Offsets the timer horizontal position by a given amount in pixels', + type: 'number', + placeholder: '0 (default)', + }, + { + id: 'aligny', + title: 'Align Vertical', + description: 'Moves the vertically in page to start = left | center | end = right', + type: 'option', + values: { start: 'Start', center: 'Center', end: 'End' }, + defaultValue: 'center', + }, + { + id: 'offsety', + title: 'Offset Vertical', + description: 'Offsets the timer vertical position by a given amount in pixels', + type: 'number', + placeholder: '0 (default)', + }, +]; diff --git a/apps/client/src/features/viewers/public/Public.tsx b/apps/client/src/features/viewers/public/Public.tsx index 4d3a2a6273..d88c811ce9 100644 --- a/apps/client/src/features/viewers/public/Public.tsx +++ b/apps/client/src/features/viewers/public/Public.tsx @@ -8,7 +8,6 @@ import Schedule from '../../../common/components/schedule/Schedule'; import { ScheduleProvider } from '../../../common/components/schedule/ScheduleContext'; import ScheduleNav from '../../../common/components/schedule/ScheduleNav'; import TitleCard from '../../../common/components/title-card/TitleCard'; -import { getPublicOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; @@ -19,6 +18,8 @@ import { titleVariants } from '../common/animation'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import { getPropertyValue } from '../common/viewUtils'; +import { getPublicOptions } from './public.options'; + import './Public.scss'; export const MotionTitleCard = motion(TitleCard); @@ -73,7 +74,7 @@ export default function Public(props: BackstageProps) { return (
- +
{general.title}
diff --git a/apps/client/src/features/viewers/public/public.options.ts b/apps/client/src/features/viewers/public/public.options.ts new file mode 100644 index 0000000000..02f1da9bec --- /dev/null +++ b/apps/client/src/features/viewers/public/public.options.ts @@ -0,0 +1,44 @@ +import { CustomFields } from 'ontime-types'; + +import { getTimeOption, makeOptionsFromCustomFields } from '../../../common/components/view-params-editor/constants'; +import { ViewOption } from '../../../common/components/view-params-editor/types'; + +export const getPublicOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => { + const secondaryOptions = makeOptionsFromCustomFields(customFields); + + return [ + { section: 'Clock Options' }, + getTimeOption(timeFormat), + { section: 'Data sources' }, + { + 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: '', + }, + { section: 'Schedule options' }, + { + id: 'eventsPerPage', + title: 'Events per page', + description: 'Sets the number of events on the page, can cause overflow', + type: 'number', + placeholder: '8 (default)', + }, + { + 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, + }, + ]; +}; diff --git a/apps/client/src/features/viewers/studio/StudioClock.tsx b/apps/client/src/features/viewers/studio/StudioClock.tsx index 17bce990b6..b3be821dcc 100644 --- a/apps/client/src/features/viewers/studio/StudioClock.tsx +++ b/apps/client/src/features/viewers/studio/StudioClock.tsx @@ -4,7 +4,6 @@ import { isOntimeEvent, Playback } from 'ontime-types'; import { millisToString, removeSeconds } from 'ontime-utils'; import { overrideStylesURL } from '../../../common/api/constants'; -import { getStudioClockOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import useFitText from '../../../common/hooks/useFitText'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; @@ -14,6 +13,7 @@ import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import { isStringBoolean } from '../common/viewUtils'; +import { getStudioClockOptions } from './studioClock.options'; import { secondsInMillis, trimRundown } from './studioClock.utils'; import './StudioClock.scss'; @@ -75,7 +75,7 @@ export default function StudioClock(props: StudioClockProps) { return (
- +
{hasAmPm &&
{hasAmPm}
}
{clock}
diff --git a/apps/client/src/features/viewers/studio/studioClock.options.ts b/apps/client/src/features/viewers/studio/studioClock.options.ts new file mode 100644 index 0000000000..23ac6c0acb --- /dev/null +++ b/apps/client/src/features/viewers/studio/studioClock.options.ts @@ -0,0 +1,9 @@ +import { getTimeOption, hideTimerSeconds } from '../../../common/components/view-params-editor/constants'; +import type { ViewOption } from '../../../common/components/view-params-editor/types'; + +export const getStudioClockOptions = (timeFormat: string): ViewOption[] => [ + { section: 'Clock Options' }, + getTimeOption(timeFormat), + { section: 'Timer Options' }, + hideTimerSeconds, +]; diff --git a/apps/client/src/features/viewers/timer/Timer.tsx b/apps/client/src/features/viewers/timer/Timer.tsx index dc7e767b50..6edba99d96 100644 --- a/apps/client/src/features/viewers/timer/Timer.tsx +++ b/apps/client/src/features/viewers/timer/Timer.tsx @@ -15,7 +15,6 @@ import { import { overrideStylesURL } from '../../../common/api/constants'; import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar'; import TitleCard from '../../../common/components/title-card/TitleCard'; -import { getTimerOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { useWindowTitle } from '../../../common/hooks/useWindowTitle'; @@ -25,6 +24,8 @@ import { useTranslation } from '../../../translation/TranslationProvider'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import { getFormattedTimer, getPropertyValue, getTimerByType, isStringBoolean } from '../common/viewUtils'; +import { getTimerOptions } from './timer.options'; + import './Timer.scss'; // motion @@ -158,7 +159,7 @@ export default function Timer(props: TimerProps) { return (
- +
{!userOptions.hideMessage && (
diff --git a/apps/client/src/features/viewers/timer/timer.options.ts b/apps/client/src/features/viewers/timer/timer.options.ts new file mode 100644 index 0000000000..9a0cc39eab --- /dev/null +++ b/apps/client/src/features/viewers/timer/timer.options.ts @@ -0,0 +1,74 @@ +import { CustomFields } from 'ontime-types'; + +import { + getTimeOption, + hideTimerSeconds, + makeOptionsFromCustomFields, + showLeadingZeros, +} from '../../../common/components/view-params-editor/constants'; +import { ViewOption } from '../../../common/components/view-params-editor/types'; + +export const getTimerOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => { + const mainOptions = makeOptionsFromCustomFields(customFields, { title: 'Title' }); + const secondaryOptions = makeOptionsFromCustomFields(customFields, { note: 'Note' }); + return [ + { section: 'Clock Options' }, + getTimeOption(timeFormat), + { section: 'Timer Options' }, + hideTimerSeconds, + showLeadingZeros, + { section: 'Data sources' }, + { + id: 'main', + title: 'Main text', + description: 'Select the data source for the main text', + type: 'option', + values: mainOptions, + defaultValue: 'Title', + }, + { + id: 'secondary-src', + title: 'Secondary text', + description: 'Select the data source for the secondary text', + type: 'option', + values: secondaryOptions, + defaultValue: '', + }, + { section: 'Element visibility' }, + { + 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, + }, + ]; +};