mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-15 04:13:47 +00:00
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>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import SwatchPicker from '../input/colour-input/SwatchPicker';
|
||||
|
||||
@@ -16,10 +16,13 @@ const ensureHex = (value: string) => {
|
||||
return value;
|
||||
};
|
||||
|
||||
export default function InlineColourPicker(props: InlineColourPickerProps) {
|
||||
const { name, value } = props;
|
||||
export default function InlineColourPicker({ name, value }: InlineColourPickerProps) {
|
||||
const [colour, setColour] = useState(() => ensureHex(value));
|
||||
|
||||
useEffect(() => {
|
||||
setColour(ensureHex(value));
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className={style.inline}>
|
||||
<SwatchPicker color={colour} onChange={setColour} alwaysDisplayColor />
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
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 from '../select/Select';
|
||||
import Select, { SelectOption } from '../select/Select';
|
||||
import Switch from '../switch/Switch';
|
||||
|
||||
import InlineColourPicker from './InlineColourPicker';
|
||||
@@ -35,7 +35,7 @@ export default function ParamInput({ paramField }: ParamInputProps) {
|
||||
return <span className={style.empty}>No options available</span>;
|
||||
}
|
||||
|
||||
return <Select size='large' name={id} defaultValue={defaultOptionValue} options={paramField.values} />;
|
||||
return <ControlledSelect id={id} initialValue={defaultOptionValue} options={paramField.values} />;
|
||||
}
|
||||
|
||||
if (type === 'multi-option') {
|
||||
@@ -43,7 +43,7 @@ export default function ParamInput({ paramField }: ParamInputProps) {
|
||||
}
|
||||
|
||||
if (type === 'boolean') {
|
||||
return <ControlledSwitch id={id} initialValue={isStringBoolean(searchParams.get(id)) || defaultValue} />;
|
||||
return <ControlledSwitch id={id} initialValue={isStringBoolean(searchParams.get(id)) ?? defaultValue} />;
|
||||
}
|
||||
|
||||
if (type === 'number') {
|
||||
@@ -63,15 +63,13 @@ export default function ParamInput({ paramField }: ParamInputProps) {
|
||||
}
|
||||
|
||||
if (type === 'colour') {
|
||||
const currentvalue = `#${searchParams.get(id) ?? defaultValue}`;
|
||||
|
||||
return <InlineColourPicker name={id} value={currentvalue} />;
|
||||
return <InlineColourPicker name={id} value={searchParams.get(id) ?? defaultValue} />;
|
||||
}
|
||||
|
||||
const defaultStringValue = searchParams.get(id) ?? defaultValue;
|
||||
const defaultStringValue = searchParams.get(id) ?? defaultValue ?? '';
|
||||
const { placeholder } = paramField;
|
||||
|
||||
return <Input height='large' name={id} defaultValue={defaultStringValue} placeholder={placeholder} />;
|
||||
return <ControlledInput id={id} initialValue={defaultStringValue} placeholder={placeholder} />;
|
||||
}
|
||||
|
||||
interface EditFormMultiOptionProps {
|
||||
@@ -83,7 +81,12 @@ function MultiOption({ paramField }: EditFormMultiOptionProps) {
|
||||
const { id, values, defaultValue = [''] } = paramField;
|
||||
|
||||
const optionFromParams = searchParams.getAll(id);
|
||||
const [paramState, setParamState] = useState<string[]>(optionFromParams || defaultValue);
|
||||
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) {
|
||||
@@ -129,5 +132,52 @@ interface ControlledSwitchProps {
|
||||
}
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FormEvent, memo, useReducer } from 'react';
|
||||
import { FormEvent, memo } from 'react';
|
||||
import { IoClose } from 'react-icons/io5';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import { Dialog } from '@base-ui-components/react/dialog';
|
||||
@@ -12,7 +12,7 @@ import Info from '../info/Info';
|
||||
import { ViewOption } from './viewParams.types';
|
||||
import { getURLSearchParamsFromObj } from './viewParams.utils';
|
||||
import { useViewParamsEditorStore } from './viewParamsEditor.store';
|
||||
import { ViewParamsShare } from './ViewParamShare';
|
||||
import { ViewParamsPresets } from './ViewParamsPresets';
|
||||
import ViewParamsSection from './ViewParamsSection';
|
||||
|
||||
import style from './ViewParamsEditor.module.scss';
|
||||
@@ -24,12 +24,9 @@ interface EditFormDrawerProps {
|
||||
|
||||
export default memo(ViewParamsEditor);
|
||||
function ViewParamsEditor({ target, viewOptions }: EditFormDrawerProps) {
|
||||
// TODO: can we ensure that the options update when the user loads an alias?
|
||||
const [_, setSearchParams] = useSearchParams();
|
||||
const { data: viewSettings } = useViewSettings();
|
||||
const { isOpen, close } = useViewParamsEditorStore();
|
||||
// TODO: we dont want this as a permanent option
|
||||
const forceRender = useReducer((x) => x + 1, 0)[1];
|
||||
|
||||
const handleClose = () => {
|
||||
close();
|
||||
@@ -37,7 +34,6 @@ function ViewParamsEditor({ target, viewOptions }: EditFormDrawerProps) {
|
||||
|
||||
const resetParams = () => {
|
||||
setSearchParams();
|
||||
forceRender();
|
||||
};
|
||||
|
||||
const onParamsFormSubmit = (formEvent: FormEvent<HTMLFormElement>) => {
|
||||
@@ -46,7 +42,6 @@ function ViewParamsEditor({ target, viewOptions }: EditFormDrawerProps) {
|
||||
const newParamsObject = Object.fromEntries(new FormData(formEvent.currentTarget));
|
||||
const newSearchParams = getURLSearchParamsFromObj(newParamsObject, viewOptions);
|
||||
setSearchParams(newSearchParams);
|
||||
forceRender();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -71,7 +66,7 @@ function ViewParamsEditor({ target, viewOptions }: EditFormDrawerProps) {
|
||||
{viewSettings.overrideStyles && (
|
||||
<Info className={style.info}>This view style is being modified by a custom CSS file.</Info>
|
||||
)}
|
||||
<ViewParamsShare target={target} />
|
||||
<ViewParamsPresets target={target} />
|
||||
<form id='edit-params-form' onSubmit={onParamsFormSubmit} className={style.sectionList}>
|
||||
{viewOptions.map((section) => (
|
||||
<ViewParamsSection
|
||||
|
||||
+3
@@ -5,6 +5,9 @@
|
||||
gap: 0.25rem;
|
||||
padding: 1rem 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
max-height: 10rem;
|
||||
overflow-y: auto;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.preset {
|
||||
+8
-6
@@ -5,17 +5,19 @@ import { useViewUrlPresets } from '../../hooks-query/useUrlPresets';
|
||||
import { cx } from '../../utils/styleUtils';
|
||||
import Button from '../buttons/Button';
|
||||
|
||||
import style from './ViewParamsShare.module.scss';
|
||||
import style from './ViewParamsPresets.module.scss';
|
||||
|
||||
/**
|
||||
* Shows a list of presets for the current view
|
||||
*/
|
||||
export function ViewParamsShare({ target }: { target: OntimeView }) {
|
||||
export function ViewParamsPresets({ target }: { target: OntimeView }) {
|
||||
const { viewPresets } = useViewUrlPresets(target);
|
||||
const [_, setSearchParams] = useSearchParams();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const handleRecall = (preset: URLPreset) => {
|
||||
setSearchParams(`${preset.search}&alias=${preset.alias}`);
|
||||
const newSearch = new URLSearchParams(preset.search);
|
||||
newSearch.set('alias', preset.alias);
|
||||
setSearchParams(newSearch);
|
||||
};
|
||||
|
||||
if (viewPresets.length === 0) {
|
||||
@@ -25,12 +27,12 @@ export function ViewParamsShare({ target }: { target: OntimeView }) {
|
||||
return (
|
||||
<div className={style.presetSection}>
|
||||
{viewPresets.map((preset) => {
|
||||
const active = window.location.search.includes(`alias=${preset.alias}`);
|
||||
const active = searchParams.get('alias') === preset.alias;
|
||||
return (
|
||||
<div key={preset.alias} className={cx([style.preset, active && style.active])}>
|
||||
<div>{preset.alias}</div>
|
||||
<Button
|
||||
variant='subtle-white'
|
||||
variant={active ? 'ghosted' : 'subtle-white'}
|
||||
onClick={() => handleRecall(preset)}
|
||||
disabled={active}
|
||||
className={style.presetActions}
|
||||
@@ -44,3 +44,7 @@
|
||||
transition: rotate 300ms cubic-bezier(0.45, 1.005, 0, 1.005);
|
||||
rotate: 180deg;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@@ -47,15 +47,11 @@ interface SectionContentsProps {
|
||||
}
|
||||
|
||||
function SectionContents({ options, collapsed }: SectionContentsProps) {
|
||||
if (collapsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{options.map((option) => {
|
||||
return (
|
||||
<label key={option.title} className={style.label}>
|
||||
<label key={option.title} className={cx([style.label, collapsed && style.hidden])}>
|
||||
<span className={style.title}>{option.title}</span>
|
||||
<span className={style.description}>{option.description}</span>
|
||||
<ParamInput paramField={option} />
|
||||
|
||||
@@ -16,7 +16,7 @@ export type MultiselectOption = { value: string; label: string; colour: string }
|
||||
type MultiOptionsField = {
|
||||
type: 'multi-option';
|
||||
values: MultiselectOption[];
|
||||
defaultValue?: string;
|
||||
defaultValue?: string[];
|
||||
};
|
||||
|
||||
type StringField = { type: 'string'; defaultValue?: string; placeholder?: string };
|
||||
|
||||
Reference in New Issue
Block a user