Files
ontime/apps/client/src/common/components/view-params-editor/ParamInput.tsx
T
Carlos Valente ae15f3cdc5 Alpha 5 (#1741)
* refactor: align header columns

* refactor: improve scrollbar visibility

* refactor: center align table elements

* refactor: make param elements stateful

* fix: issue with collapsed elements not loosing value

* fix: prevent search params containing multiple alias references

* fix: the issue where a file disappears if it is both migrated and recovered in the same load operation (#1744)

* refactor: disable group action for elements in groups

* refactor: move context menu items into the event element (#1747)

* feat: sheet import new features for v4 (#1730)

* import milestone

* fixup! import milestone

* test: milestone import

* add entries to group

stop on new group or on group-end type

* fixup! add entries to group

* cleanup

* add event target duration

* link start if undefined

* add skip import type

* extract some to the excel paresing functions

* tweaks to presentation

* move file

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* fix: notify runtimeStore of events bieng groupd

* fix: improve authentication and stage detection in demo

* chore: ship logo with project

* fix: client is referenced by name

* fix: prevent reflow in event editor

* fix: stale render on selected event due to ref mismatch

* Create/Load/Delete multiple rundowns (#1696)

* refactor: restore last loaded rundown

* refactor: initialise rundown in ProjectService

* feat: allow switching rundowns

ensure on coordination between the db object and the working object

server provide list of rundowns

implement switch in the UI

implement delete

implement new rundown button

* fix: render order for floating button

* refactor: appropriate names to service

* refactor: rundown endpoints

* refactor: save last loaded rundown ID

* refactor: rundown management UI

* refactor: emit refetch all on project load

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* feat: recover single event subscription

* fixup! feat: sheet import new features for v4 (#1730)

* fix: prevent dropping a group inside another

* fixup! refactor: move context menu items into the event element (#1747)

* fix: prevent stale references to custom fields

* fix: propagate updates to all rundowns

* refactor: client rundown metadata (#1728)

* generate metadata in the hook

* move test

* ensure there is always a last element

* use for-loop

* update metadata in useEfect

* fully extract metadata generation

* use direct assignment

* cleanup

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* refactor: small imporvement and tests for coerce functions (#1752)

* refactor: small imporvement and tests for coerce functions

add test `coerceString`

add test `coerceBoolean`

add test `coerceColour`

* remove old todo

* fix: consistent quick add behaviour

* refactor: create flat rundown with metadata

* fix: show add buttons on top

* feat: allow editing milestones

* refactor: style tweaks to rundown elements

refactor: milestones are full width

refactor: cuesheet header alignment

fix: editor styling in cuesheet

* refactor: virtualise table

* refactor: improve overscan (#1758)

* bump version to 4.0.0-alpha.5

---------

Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk>
2025-09-03 15:50:20 +02:00

184 lines
5.2 KiB
TypeScript

import { ComponentProps, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router';
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
import Checkbox from '../checkbox/Checkbox';
import Input from '../input/input/Input';
import Select, { SelectOption } from '../select/Select';
import Switch from '../switch/Switch';
import InlineColourPicker from './InlineColourPicker';
import { ParamField } from './viewParams.types';
import style from './ParamInput.module.scss';
interface ParamInputProps {
paramField: ParamField;
}
export default function ParamInput({ paramField }: ParamInputProps) {
const [searchParams] = useSearchParams();
const { id, type, defaultValue } = paramField;
if (type === 'persist') {
if (!paramField.values || !paramField.values.length) {
return null;
}
return <input hidden name={id} readOnly value={paramField.values.join(',')} />;
}
if (type === 'option') {
const optionFromParams = searchParams.get(id);
const defaultOptionValue = optionFromParams || defaultValue;
if (paramField.values.length === 0) {
return <span className={style.empty}>No options available</span>;
}
return <ControlledSelect id={id} initialValue={defaultOptionValue} options={paramField.values} />;
}
if (type === 'multi-option') {
return <MultiOption paramField={paramField} />;
}
if (type === 'boolean') {
return <ControlledSwitch id={id} initialValue={isStringBoolean(searchParams.get(id)) ?? defaultValue} />;
}
if (type === 'number') {
const { placeholder } = paramField;
const defaultNumberValue = searchParams.get(id) ?? defaultValue;
return (
<Input
height='large'
type='number'
step='any'
name={id}
defaultValue={defaultNumberValue}
placeholder={placeholder}
/>
);
}
if (type === 'colour') {
return <InlineColourPicker name={id} value={searchParams.get(id) ?? defaultValue} />;
}
const defaultStringValue = searchParams.get(id) ?? defaultValue ?? '';
const { placeholder } = paramField;
return <ControlledInput id={id} initialValue={defaultStringValue} placeholder={placeholder} />;
}
interface EditFormMultiOptionProps {
paramField: ParamField & { type: 'multi-option' };
}
function MultiOption({ paramField }: EditFormMultiOptionProps) {
const [searchParams] = useSearchParams();
const { id, values, defaultValue = [''] } = paramField;
const optionFromParams = searchParams.getAll(id);
const [paramState, setParamState] = useState<string[]>(optionFromParams.length ? optionFromParams : defaultValue);
useEffect(() => {
const params = searchParams.getAll(id);
setParamState(params.length ? params : defaultValue);
}, [searchParams, id, defaultValue]);
const toggleValue = (value: string, checked: boolean) => {
if (checked) {
setParamState((prev) => [...prev, value]);
} else {
setParamState((prev) => prev.filter((v) => v !== value));
}
};
if (values.length === 0) {
return <span className={style.empty}>No options available</span>;
}
return (
<>
<input name={id} hidden readOnly value={paramState.join(',')} />
<div className={style.inline}>
{values.map((option) => {
return (
<label
key={option.value}
className={style.toggleSelect}
style={{
'--user-bg': option.colour,
}}
>
<Checkbox
checked={paramState.includes(option.value)}
onCheckedChange={(checked) => toggleValue(option.value, checked as boolean)}
/>
{option.label}
</label>
);
})}
</div>
</>
);
}
interface ControlledSwitchProps {
id: string;
initialValue: boolean;
}
function ControlledSwitch({ id, initialValue }: ControlledSwitchProps) {
const [checked, setChecked] = useState(initialValue);
// synchronise checked state
useEffect(() => {
setChecked(initialValue);
}, [initialValue]);
return <Switch size='large' name={id} checked={checked} onCheckedChange={setChecked} />;
}
interface ControlledSelectProps {
id: string;
initialValue?: string;
options: SelectOption[];
}
function ControlledSelect({ id, initialValue, options }: ControlledSelectProps) {
const [selected, setSelected] = useState(initialValue);
// synchronise selected state
useEffect(() => {
setSelected(initialValue);
}, [initialValue]);
return (
<Select size='large' name={id} options={options} value={selected} onValueChange={(value) => setSelected(value)} />
);
}
interface ControlledInputProps<T extends number | string> extends ComponentProps<typeof Input> {
id: string;
initialValue: T;
}
function ControlledInput<T extends number | string>({ id, initialValue, ...inputProps }: ControlledInputProps<T>) {
const [value, setValue] = useState(initialValue);
// synchronise selected state
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return (
<Input
height='large'
name={id}
value={value}
onChange={(event) => setValue(event.target.value as T)}
{...inputProps}
/>
);
}