refactor: improve handling of multi-fields

This commit is contained in:
Carlos Valente
2025-06-17 07:26:44 +02:00
committed by Carlos Valente
parent b2b115c329
commit 5c86914cda
23 changed files with 491 additions and 244 deletions
@@ -18,19 +18,21 @@ import {
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
import InlineColourPicker from './InlineColourPicker';
import { ParamField } from './types';
import { ParamField } from './viewParams.types';
interface EditFormInputProps {
interface ParamInputProps {
paramField: ParamField;
}
export default function ParamInput(props: EditFormInputProps) {
const { paramField } = props;
export default function ParamInput({ paramField }: ParamInputProps) {
const [searchParams] = useSearchParams();
const { id, type, defaultValue } = paramField;
if (type === 'persist') {
return null;
if (!paramField.values || !paramField.values.length) {
return null;
}
return <input hidden name={id} readOnly value={paramField.values.join(',')} />;
}
if (type === 'option') {
@@ -15,74 +15,12 @@ import {
import useViewSettings from '../../hooks-query/useViewSettings';
import Info from '../info/Info';
import { ViewOption } from './types';
import { ViewOption } from './viewParams.types';
import { getURLSearchParamsFromObj } from './viewParams.utils';
import ViewParamsSection from './ViewParamsSection';
import style from './ViewParamsEditor.module.scss';
type ViewParamsObj = { [key: string]: string | FormDataEntryValue };
/**
* Utility remove the # character from a hex string
*/
function sanitiseColour(colour: string) {
if (colour.startsWith('#')) {
return colour.substring(1);
}
return colour;
}
/**
* Makes a new URLSearchParams object from the given params object
*/
const getURLSearchParamsFromObj = (paramsObj: ViewParamsObj, paramFields: ViewOption[]) => {
const newSearchParams = new URLSearchParams();
// Convert paramFields to an object that contains default values
const defaultValues: Record<string, string> = {};
paramFields.forEach((section) => {
section.options.forEach((option) => {
defaultValues[option.id] = String(option.defaultValue);
// extract persisted values
if ('type' in option && option.type === 'persist') {
newSearchParams.set(option.id, option.value);
}
});
});
// compare which values are different from the default values
Object.entries(paramsObj).forEach(([id, value]) => {
if (typeof value === 'string' && value.length) {
// we dont know which values contain colours
// unfortunately this means we run all the strings through the sanitation
const valueWithoutHash = sanitiseColour(value);
if (defaultValues[id] !== valueWithoutHash) {
handleValueString(id, valueWithoutHash);
}
}
});
/** Utility function contains logic to add a value into the searchParams object */
function handleValueString(id: string, value: string) {
const maybeMultipleValues = value.split(',');
// we need to check if the value contains comma separated list, for the case of the multi-select data
if (Array.isArray(maybeMultipleValues) && maybeMultipleValues.length > 1) {
const added = new Set();
maybeMultipleValues.forEach((v) => {
if (!added.has(v)) {
added.add(v);
newSearchParams.append(id, v);
}
});
} else {
newSearchParams.set(id, value);
}
}
return newSearchParams;
};
interface EditFormDrawerProps {
viewOptions: ViewOption[];
}
@@ -94,6 +32,7 @@ export default function ViewParamsEditor({ viewOptions }: EditFormDrawerProps) {
const { isOpen, onClose, onOpen } = useDisclosure();
// handle opening the drawer
useEffect(() => {
const isEditing = searchParams.get('edit');
@@ -3,8 +3,9 @@ import { useLocalStorage } from '@mantine/hooks';
import { cx } from '../../utils/styleUtils';
import { OptionTitle } from './constants';
import ParamInput from './ParamInput';
import { type ParamField } from './types';
import { type ParamField } from './viewParams.types';
import style from './ViewParamsSection.module.scss';
@@ -14,9 +15,7 @@ interface ViewParamsSectionProps {
options: ParamField[];
}
export default function ViewParamsSection(props: ViewParamsSectionProps) {
const { title, collapsible, options } = props;
export default function ViewParamsSection({ title, collapsible, options }: ViewParamsSectionProps) {
const [collapsed, setCollapsed] = useLocalStorage({ key: `params-${title}`, defaultValue: false });
const handleCollapse = () => {
@@ -27,28 +26,52 @@ export default function ViewParamsSection(props: ViewParamsSectionProps) {
return (
<section className={style.section}>
<div className={cx([style.sectionHeader, collapsible && style.collapsible])} onClick={handleCollapse}>
{title}
{collapsible && <IoChevronDown className={cx([collapsed ? style.closed : style.open])} />}
</div>
{!collapsed && (
{title === OptionTitle.Hidden ? (
<HiddenContents options={options} />
) : (
<>
{options.map((option) => {
if (option.type === 'persist') {
return null;
}
return (
<label key={option.title} className={style.label}>
<span className={style.title}>{option.title}</span>
<span className={style.description}>{option.description}</span>
<ParamInput paramField={option} />
</label>
);
})}
<div className={cx([style.sectionHeader, collapsible && style.collapsible])} onClick={handleCollapse}>
{title}
{collapsible && <IoChevronDown className={cx([collapsed ? style.closed : style.open])} />}
</div>
<SectionContents options={options} collapsed={collapsed} />
</>
)}
</section>
);
}
interface SectionContentsProps {
options: ParamField[];
collapsed: boolean;
}
function SectionContents({ options, collapsed }: SectionContentsProps) {
if (collapsed) {
return null;
}
return (
<>
{options.map((option) => {
return (
<label key={option.title} className={style.label}>
<span className={style.title}>{option.title}</span>
<span className={style.description}>{option.description}</span>
<ParamInput paramField={option} />
</label>
);
})}
</>
);
}
function HiddenContents({ options }: { options: ParamField[] }) {
return (
<>
{options.map((option, index) => {
return <ParamInput key={option.title + index} paramField={option} />;
})}
</>
);
}
@@ -1,45 +0,0 @@
import { CustomFields } from 'ontime-types';
import { makeOptionsFromCustomFields } from '../constants';
describe('makeOptionsFromCustomFields', () => {
const testCustomFields: CustomFields = {
field1: { label: 'Field 1', colour: 'red', type: 'string' },
field2: { label: 'Field 2', colour: 'blue', type: 'string' },
};
it('creates a record of keys for the given custom fields', () => {
const result = makeOptionsFromCustomFields(testCustomFields);
expect(result).toStrictEqual({
'custom-field1': 'Custom: Field 1',
'custom-field2': 'Custom: Field 2',
});
});
it('appends additional data', () => {
const additionalData = {
test1: 'test1',
test2: 'test2',
};
const result = makeOptionsFromCustomFields(testCustomFields, additionalData);
expect(result).toStrictEqual({
'custom-field1': 'Custom: Field 1',
'custom-field2': 'Custom: Field 2',
test1: 'test1',
test2: 'test2',
});
});
it('filtersImageTypes', () => {
const customFieldsWIthImage: CustomFields = {
...testCustomFields,
field3: { label: 'Field 3', colour: 'green', type: 'image' },
};
const result = makeOptionsFromCustomFields(customFieldsWIthImage);
expect(result).toStrictEqual({
'custom-field1': 'Custom: Field 1',
'custom-field2': 'Custom: Field 2',
});
});
});
@@ -0,0 +1,204 @@
import { CustomFields } from 'ontime-types';
import { describe, expect, it } from 'vitest';
import { OptionTitle } from '../constants';
import type { ViewOption } from '../viewParams.types';
import { getURLSearchParamsFromObj, makeOptionsFromCustomFields } from '../viewParams.utils';
describe('makeOptionsFromCustomFields', () => {
const testCustomFields: CustomFields = {
field1: { label: 'Field 1', colour: 'red', type: 'string' },
field2: { label: 'Field 2', colour: 'blue', type: 'string' },
};
it('creates a record of keys for the given custom fields', () => {
const result = makeOptionsFromCustomFields(testCustomFields);
expect(result).toStrictEqual({
'custom-field1': 'Custom: Field 1',
'custom-field2': 'Custom: Field 2',
});
});
it('appends additional data', () => {
const additionalData = {
test1: 'test1',
test2: 'test2',
};
const result = makeOptionsFromCustomFields(testCustomFields, additionalData);
expect(result).toStrictEqual({
'custom-field1': 'Custom: Field 1',
'custom-field2': 'Custom: Field 2',
test1: 'test1',
test2: 'test2',
});
});
it('filtersImageTypes', () => {
const customFieldsWIthImage: CustomFields = {
...testCustomFields,
field3: { label: 'Field 3', colour: 'green', type: 'image' },
};
const result = makeOptionsFromCustomFields(customFieldsWIthImage);
expect(result).toStrictEqual({
'custom-field1': 'Custom: Field 1',
'custom-field2': 'Custom: Field 2',
});
});
});
describe('getURLSearchParamsFromObj', () => {
// Mock view options for testing
const mockViewOptions: ViewOption[] = [
{
title: OptionTitle.DataSources,
collapsible: true,
options: [
{
id: 'color',
title: 'Color',
description: 'The color value',
type: 'colour',
defaultValue: 'ff0000',
},
{
id: 'persist-field',
title: 'Persistent Field',
description: 'A field that persists',
type: 'persist',
values: ['persisted-value'],
},
{
id: 'multi-select',
title: 'Multi Select',
description: 'A multi-select field',
type: 'option',
values: { value1: 'Value 1', value2: 'Value 2' },
defaultValue: '',
},
],
},
];
it('should handle empty params object', () => {
const params = {};
const result = getURLSearchParamsFromObj(params, mockViewOptions);
// Should only include persisted values
expect(result.get('persist-field')).toBe('persisted-value');
expect(result.get('color')).toBeNull();
});
it('should not include values that match defaults', () => {
const params = {
color: 'ff0000', // same as the default
'other-param': 'value',
};
const result = getURLSearchParamsFromObj(params, mockViewOptions);
expect(result.get('color')).toBeNull();
expect(result.get('other-param')).toBe('value');
});
it('should sanitize color values with #', () => {
const params = {
color: '#00ff00', // different from default and includes #
};
const result = getURLSearchParamsFromObj(params, mockViewOptions);
expect(result.get('color')).toBe('00ff00');
});
it('should handle multi-select values', () => {
const params = {
'multi-select': 'value1,value2,value3',
};
const result = getURLSearchParamsFromObj(params, mockViewOptions);
// Should have multiple entries for the same key
const values = result.getAll('multi-select');
expect(values).toEqual(['value1', 'value2', 'value3']);
});
it('should not include empty string values', () => {
const params = {
'empty-param': '',
};
const result = getURLSearchParamsFromObj(params, mockViewOptions);
expect(result.get('empty-param')).toBeNull();
});
it('should allow multiple values for persisted fields', () => {
const mockOptionsWithMultiPersist: ViewOption[] = [
{
title: OptionTitle.DataSources,
collapsible: true,
options: [
{
id: 'sub',
title: 'Event subscription',
description: 'The events to follow',
values: [],
type: 'persist',
},
],
},
];
const params = {
sub: 'value1,value2,value3',
};
const result = getURLSearchParamsFromObj(params, mockOptionsWithMultiPersist);
const values = result.getAll('sub');
expect(values).toStrictEqual(['value1', 'value2', 'value3']);
});
it('should allow adding multiple values for any field', () => {
const params = {
'regular-field': 'value1,value2,value3',
};
const result = getURLSearchParamsFromObj(params, mockViewOptions);
const values = result.getAll('regular-field');
expect(values).toHaveLength(3);
expect(values).toEqual(['value1', 'value2', 'value3']);
});
it('should deduplicate repeated key-value pairs', () => {
const params = {
multiValue: 'value1,value1,value2,value2,value3',
repeatedField: 'same,same,same',
};
const result = getURLSearchParamsFromObj(params, mockViewOptions);
// Check multiValue field has unique values
expect(result.getAll('multiValue')).toEqual(['value1', 'value2', 'value3']);
// Check repeatedField has only one instance of the value
expect(result.getAll('repeatedField')).toEqual(['same']);
});
it('should deduplicate persisted values while maintaining order', () => {
const mockOptionsWithDuplicates: ViewOption[] = [
{
title: OptionTitle.StyleOverride,
options: [
{
id: 'sub',
title: 'Subscription',
description: 'Persisted subscription values',
type: 'persist',
values: ['value1', 'value1', 'value2', 'value2', 'value3', 'value1'],
},
],
},
];
const result = getURLSearchParamsFromObj({}, mockOptionsWithDuplicates);
// Should only include unique values while maintaining order
expect(result.getAll('sub')).toEqual(['value1', 'value2', 'value3']);
});
});
@@ -0,0 +1,28 @@
import type { ParamField } from './viewParams.types';
export const getTimeOption = (timeFormat: string): ParamField => {
const placeholder = `${timeFormat} (default)`;
return {
id: 'timeformat',
title: 'Time format string, taken from the Application Settings',
description: 'Format for auxiliar time fields (not the running), eg. HH:mm:ss or hh:mm:ss a, see docs for help',
type: 'string',
placeholder,
};
};
export const hideTimerSeconds: ParamField = {
id: 'hideTimerSeconds',
title: 'Hide seconds in timer',
description: 'Whether to hide seconds in the running timer',
type: 'boolean',
defaultValue: false,
};
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,
};
@@ -1,61 +1,6 @@
import { CustomFields } from 'ontime-types';
import type { MultiselectOptions, ParamField } from './types';
export const makeOptionsFromCustomFields = (
customFields: CustomFields,
additionalOptions: Readonly<Record<string, string>> = {},
filterImageType = true,
) => {
const options = { ...additionalOptions };
for (const [key, value] of Object.entries(customFields)) {
if (filterImageType && value.type === 'image') {
continue;
}
options[`custom-${key}`] = `Custom: ${value.label}`;
}
return options;
};
export function makeCustomFieldSelectOptions(customFields: CustomFields, filterImageType = true): MultiselectOptions {
const options: MultiselectOptions = {};
for (const [key, value] of Object.entries(customFields)) {
if (filterImageType && value.type === 'image') {
continue;
}
options[key] = { value: key, label: value.label, colour: value.colour };
}
return options;
}
export const getTimeOption = (timeFormat: string): ParamField => {
const placeholder = `${timeFormat} (default)`;
return {
id: 'timeformat',
title: 'Time format string, taken from the Application Settings',
description: 'Format for auxiliar time fields (not the running), eg. HH:mm:ss or hh:mm:ss a, see docs for help',
type: 'string',
placeholder,
};
};
export const hideTimerSeconds: ParamField = {
id: 'hideTimerSeconds',
title: 'Hide seconds in timer',
description: 'Whether to hide seconds in the running timer',
type: 'boolean',
defaultValue: false,
};
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,
};
/**
* Gathers possible titles for view options
*/
export enum OptionTitle {
ClockOptions = 'Clock Options',
TimerOptions = 'Timer Options',
@@ -65,4 +10,7 @@ export enum OptionTitle {
StyleOverride = 'View style override',
Animation = 'View animation',
Schedule = 'Schedule options',
/** rendered as hidden inputs */
Hidden = 'Hidden options',
}
@@ -24,7 +24,7 @@ type StringField = { type: 'string'; defaultValue?: string; prefix?: string; pla
type NumberField = { type: 'number'; defaultValue?: number; prefix?: string; placeholder?: string };
type BooleanField = { type: 'boolean'; defaultValue: boolean };
type ColourField = { type: 'colour'; defaultValue: string; placeholder?: string };
type PersistedField = { type: 'persist'; defaultValue?: string; value: string };
type PersistedField = { type: 'persist'; defaultValue?: string[]; values: string[] };
export type ParamField = BaseField &
(OptionsField | MultiOptionsField | StringField | NumberField | BooleanField | ColourField | PersistedField);
@@ -0,0 +1,144 @@
import type { CustomFields } from 'ontime-types';
import type { MultiselectOptions, ViewOption } from './viewParams.types';
/**
* Creates a list of custom fields for a select
* Filters out image type custom fields
*/
export function makeOptionsFromCustomFields(
customFields: CustomFields,
additionalOptions: Readonly<Record<string, string>> = {},
filterImageType = true,
): Record<string, string> {
const options = { ...additionalOptions };
for (const [key, value] of Object.entries(customFields)) {
if (filterImageType && value.type === 'image') {
continue;
}
options[`custom-${key}`] = `Custom: ${value.label}`;
}
return options;
}
/**
* Creates data for a multiselect component from custom fields
* Filters out image type custom fields
*/
export function makeCustomFieldSelectOptions(customFields: CustomFields, filterImageType = true): MultiselectOptions {
const options: MultiselectOptions = {};
for (const [key, value] of Object.entries(customFields)) {
if (filterImageType && value.type === 'image') {
continue;
}
options[key] = { value: key, label: value.label, colour: value.colour };
}
return options;
}
type ViewParamsObj = { [key: string]: string | FormDataEntryValue };
/**
* Utility remove the # character from a hex string
*/
function sanitiseColour(colour: string) {
if (colour.startsWith('#')) {
return colour.substring(1);
}
return colour;
}
type FieldMetadata = {
defaultValues: Record<string, string>;
colorFields: Set<string>;
isPersistedField: Set<string>;
persistedValues: Record<string, string[]>;
};
/**
* Utility collects metadata about fields from view options
*/
function collectFieldMetadata(paramFields: ViewOption[]): FieldMetadata {
const metadata: FieldMetadata = {
defaultValues: {},
colorFields: new Set(),
isPersistedField: new Set(),
persistedValues: {},
};
paramFields.forEach((section) => {
section.options.forEach((option) => {
if (option.type === 'persist') {
metadata.isPersistedField.add(option.id);
if (option.values) {
metadata.persistedValues[option.id] = option.values;
}
} else {
metadata.defaultValues[option.id] = String(option.defaultValue);
}
if (option.type === 'colour') {
metadata.colorFields.add(option.id);
}
});
});
return metadata;
}
/**
* Makes a new URLSearchParams object from the given params object
* @param paramsObj - The object containing parameters to be converted
* @param paramFields - The view options that define the parameters
* @returns A new URLSearchParams object with the parameters
*/
export function getURLSearchParamsFromObj(paramsObj: ViewParamsObj, paramFields: ViewOption[]) {
const newSearchParams = new URLSearchParams();
const addedPairs = new Set<string>();
const metadata = collectFieldMetadata(paramFields);
// Utility function to safely add params without duplicates
const addUniqueParam = (id: string, value: string) => {
const pair = `${id}:${value}`;
if (!addedPairs.has(pair)) {
addedPairs.add(pair);
newSearchParams.append(id, value);
}
};
// First add all persisted values
Object.entries(metadata.persistedValues).forEach(([id, values]) => {
values.forEach((value) => {
if (value) {
addUniqueParam(id, value);
}
});
});
// Then process user-provided values
Object.entries(paramsObj).forEach(([id, value]) => {
if (typeof value === 'string' && value.length) {
// For persisted fields, clear existing values before adding new ones
if (metadata.isPersistedField.has(id)) {
// Clear tracking of previous values for this field
Array.from(addedPairs).forEach((pair) => {
if (pair.startsWith(`${id}:`)) {
addedPairs.delete(pair);
}
});
newSearchParams.delete(id);
}
// Process and add new values
value.split(',').forEach((v) => {
const processedValue = metadata.colorFields.has(id) ? sanitiseColour(v) : v;
if (metadata.isPersistedField.has(id) || metadata.defaultValues[id] !== processedValue) {
addUniqueParam(id, processedValue);
}
});
}
});
return newSearchParams;
}
@@ -1,12 +1,12 @@
import { CustomFields } from 'ontime-types';
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
import { OptionTitle } from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
import {
getTimeOption,
makeCustomFieldSelectOptions,
makeOptionsFromCustomFields,
OptionTitle,
} from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/types';
} from '../../common/components/view-params-editor/viewParams.utils';
export const getOperatorOptions = (customFields: CustomFields, timeFormat: string): ViewOption[] => {
const fieldOptions = makeOptionsFromCustomFields(customFields, { title: 'Title', note: 'Note' });
@@ -1,5 +1,6 @@
import { getTimeOption, OptionTitle } from '../../../common/components/view-params-editor/constants';
import { ViewOption } from '../../../common/components/view-params-editor/types';
import { getTimeOption } from '../../../common/components/view-params-editor/common.options';
import { OptionTitle } from '../../../common/components/view-params-editor/constants';
import { ViewOption } from '../../../common/components/view-params-editor/viewParams.types';
export const getClockOptions = (timeFormat: string): ViewOption[] => [
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
@@ -2,8 +2,9 @@ import { useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { CustomFields } from 'ontime-types';
import { makeOptionsFromCustomFields, OptionTitle } from '../../../common/components/view-params-editor/constants';
import { ViewOption } from '../../../common/components/view-params-editor/types';
import { OptionTitle } from '../../../common/components/view-params-editor/constants';
import { ViewOption } from '../../../common/components/view-params-editor/viewParams.types';
import { makeOptionsFromCustomFields } from '../../../common/components/view-params-editor/viewParams.utils';
import safeParseNumber from '../../../common/utils/safeParseNumber';
export const getLowerThirdOptions = (customFields: CustomFields): ViewOption[] => {
@@ -1,9 +1,6 @@
import {
hideTimerSeconds,
OptionTitle,
showLeadingZeros,
} from '../../../common/components/view-params-editor/constants';
import { ViewOption } from '../../../common/components/view-params-editor/types';
import { hideTimerSeconds, showLeadingZeros } from '../../../common/components/view-params-editor/common.options';
import { OptionTitle } from '../../../common/components/view-params-editor/constants';
import { ViewOption } from '../../../common/components/view-params-editor/viewParams.types';
export const MINIMAL_TIMER_OPTIONS: ViewOption[] = [
{ title: OptionTitle.TimerOptions, collapsible: true, options: [hideTimerSeconds, showLeadingZeros] },
@@ -1,5 +1,6 @@
import { getTimeOption, hideTimerSeconds, OptionTitle } from '../../../common/components/view-params-editor/constants';
import type { ViewOption } from '../../../common/components/view-params-editor/types';
import { getTimeOption, hideTimerSeconds } from '../../../common/components/view-params-editor/common.options';
import { OptionTitle } from '../../../common/components/view-params-editor/constants';
import type { ViewOption } from '../../../common/components/view-params-editor/viewParams.types';
export const getStudioClockOptions = (timeFormat: string): ViewOption[] => [
{ title: OptionTitle.ClockOptions, collapsible: true, options: [getTimeOption(timeFormat)] },
@@ -2,12 +2,10 @@ import { useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { CustomFields, OntimeEvent } from 'ontime-types';
import {
getTimeOption,
makeOptionsFromCustomFields,
OptionTitle,
} from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/types';
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
import { OptionTitle } from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils';
import { scheduleOptions } from '../common/schedule/schedule.options';
export const getBackstageOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => {
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { OptionTitle } from '../../../common/components/view-params-editor/constants';
import { ViewOption } from '../../../common/components/view-params-editor/types';
import { ViewOption } from '../../../common/components/view-params-editor/viewParams.types';
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
export const scheduleOptions: ViewOption = {
@@ -63,7 +63,7 @@ export default function Countdown({
// gather option data
const defaultFormat = getDefaultFormat(settings?.timeFormat);
const countdownOptions = getCountdownOptions(defaultFormat, customFields);
const countdownOptions = getCountdownOptions(defaultFormat, customFields, subscriptions);
return (
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
@@ -2,15 +2,17 @@ import { useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { CustomFields, EntryId, OntimeEvent } from 'ontime-types';
import {
getTimeOption,
makeOptionsFromCustomFields,
OptionTitle,
} from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/types';
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
import { OptionTitle } from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils';
import { isStringBoolean } from '../../features/viewers/common/viewUtils';
export const getCountdownOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => {
export const getCountdownOptions = (
timeFormat: string,
customFields: CustomFields,
persistedSubscriptions: EntryId[],
): ViewOption[] => {
const secondaryOptions = makeOptionsFromCustomFields(customFields, { note: 'Note' });
return [
@@ -20,15 +22,6 @@ export const getCountdownOptions = (timeFormat: string, customFields: CustomFiel
collapsible: true,
options: [
{
id: 'sub',
title: 'Event subscription',
description: 'The events to follow',
value: '',
type: 'persist',
},
{
// TODO: adding a secondary source is removing the subscriptions
// this seems to be a bug with persist assuming that the property has a single entry
id: 'secondary-src',
title: 'Event secondary text',
description: 'Select the data source for auxiliary text shown in the card',
@@ -51,6 +44,18 @@ export const getCountdownOptions = (timeFormat: string, customFields: CustomFiel
},
],
},
{
title: OptionTitle.Hidden,
options: [
{
id: 'sub',
title: 'Event subscription',
description: 'The events to follow',
values: persistedSubscriptions,
type: 'persist',
},
],
},
];
};
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { OptionTitle } from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/types';
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
import { isStringBoolean } from '../../features/viewers/common/viewUtils';
/**
@@ -1,5 +1,5 @@
import { OptionTitle } from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/types';
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
export const projectInfoOptions: ViewOption[] = [
{
@@ -1,5 +1,6 @@
import { getTimeOption, OptionTitle } from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/types';
import { getTimeOption } from '../../common/components/view-params-editor/common.options';
import { OptionTitle } from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
export const getTimelineOptions = (timeFormat: string): ViewOption[] => {
return [
+4 -4
View File
@@ -6,11 +6,11 @@ import { validateTimerType } from 'ontime-utils';
import {
getTimeOption,
hideTimerSeconds,
makeOptionsFromCustomFields,
OptionTitle,
showLeadingZeros,
} from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/types';
} from '../../common/components/view-params-editor/common.options';
import { OptionTitle } from '../../common/components/view-params-editor/constants';
import { ViewOption } from '../../common/components/view-params-editor/viewParams.types';
import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils';
import { isStringBoolean } from '../../features/viewers/common/viewUtils';
// manually match the properties of TimerType excluding the None