R: Unify components API for user inputs

This commit is contained in:
Ivan 2025-02-04 20:35:18 +03:00
parent 4cf24d0200
commit e0abbe6534
55 changed files with 193 additions and 190 deletions

View File

@ -17,6 +17,9 @@ import { describeConstituenta } from '@/utils/labels';
interface PickConstituentaProps extends CProps.Styling {
id?: string;
value?: IConstituenta;
onChange: (newValue: IConstituenta) => void;
prefixID: string;
data?: IConstituenta[];
rows?: number;
@ -25,9 +28,6 @@ interface PickConstituentaProps extends CProps.Styling {
onBeginFilter?: (cst: IConstituenta) => boolean;
describeFunc?: (cst: IConstituenta) => string;
matchFunc?: (cst: IConstituenta, filter: string) => boolean;
value?: IConstituenta;
onSelectValue: (newValue: IConstituenta) => void;
}
const columnHelper = createColumnHelper<IConstituenta>();
@ -42,7 +42,7 @@ function PickConstituenta({
describeFunc = describeConstituenta,
matchFunc = (cst, filter) => matchConstituenta(cst, filter, CstMatchMode.ALL),
onBeginFilter,
onSelectValue,
onChange,
className,
...restProps
}: PickConstituentaProps) {
@ -110,7 +110,7 @@ function PickConstituenta({
<p>Измените параметры фильтра</p>
</NoData>
}
onRowClicked={onSelectValue}
onRowClicked={onChange}
/>
</div>
);

View File

@ -18,15 +18,15 @@ import ToolbarGraphSelection from './ToolbarGraphSelection';
interface PickMultiConstituentaProps extends CProps.Styling {
id?: string;
value: ConstituentaID[];
onChange: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
schema: IRSForm;
data: IConstituenta[];
prefixID: string;
rows?: number;
noBorder?: boolean;
selected: ConstituentaID[];
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
}
const columnHelper = createColumnHelper<IConstituenta>();
@ -38,8 +38,8 @@ function PickMultiConstituenta({
prefixID,
rows,
noBorder,
selected,
setSelected,
value,
onChange,
className,
...restProps
}: PickMultiConstituentaProps) {
@ -74,10 +74,10 @@ function PickMultiConstituenta({
}
const newRowSelection: RowSelectionState = {};
filtered.forEach((cst, index) => {
newRowSelection[String(index)] = selected.includes(cst.id);
newRowSelection[String(index)] = value.includes(cst.id);
});
setRowSelection(newRowSelection);
}, [filtered, setRowSelection, selected]);
}, [filtered, setRowSelection, value]);
useEffect(() => {
if (data.length === 0) {
@ -91,7 +91,7 @@ function PickMultiConstituenta({
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
if (!data) {
setSelected([]);
onChange([]);
} else {
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
const newSelection: ConstituentaID[] = [];
@ -100,7 +100,7 @@ function PickMultiConstituenta({
newSelection.push(cst.id);
}
});
setSelected(prev => [...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)), ...newSelection]);
onChange(prev => [...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)), ...newSelection]);
}
}
@ -122,7 +122,7 @@ function PickMultiConstituenta({
<div className={clsx(noBorder ? '' : 'border', className)} {...restProps}>
<div className={clsx('px-3 flex justify-between items-center', 'clr-input', 'border-b', 'rounded-t-md')}>
<div className='w-[24ch] select-none whitespace-nowrap'>
{data.length > 0 ? `Выбраны ${selected.length} из ${data.length}` : 'Конституенты'}
{data.length > 0 ? `Выбраны ${value.length} из ${data.length}` : 'Конституенты'}
</div>
<SearchBar
id='dlg_constituents_search'
@ -135,9 +135,9 @@ function PickMultiConstituenta({
graph={foldedGraph}
isCore={cstID => isBasicConcept(schema.cstByID.get(cstID)?.cst_type)}
isOwned={cstID => !schema.cstByID.get(cstID)?.is_inherited}
selected={selected}
setSelected={setSelected}
emptySelection={selected.length === 0}
value={value}
onChange={onChange}
emptySelection={value.length === 0}
className='w-fit'
/>
</div>

View File

@ -12,36 +12,36 @@ import NoData from '@/components/ui/NoData';
import { IOperation, OperationID } from '@/models/oss';
interface PickMultiOperationProps extends CProps.Styling {
rows?: number;
value: OperationID[];
onChange: React.Dispatch<React.SetStateAction<OperationID[]>>;
items: IOperation[];
selected: OperationID[];
setSelected: React.Dispatch<React.SetStateAction<OperationID[]>>;
rows?: number;
}
const columnHelper = createColumnHelper<IOperation>();
function PickMultiOperation({ rows, items, selected, setSelected, className, ...restProps }: PickMultiOperationProps) {
const selectedItems = selected.map(itemID => items.find(item => item.id === itemID)!);
const nonSelectedItems = items.filter(item => !selected.includes(item.id));
function PickMultiOperation({ rows, items, value, onChange, className, ...restProps }: PickMultiOperationProps) {
const selectedItems = value.map(itemID => items.find(item => item.id === itemID)!);
const nonSelectedItems = items.filter(item => !value.includes(item.id));
const [lastSelected, setLastSelected] = useState<IOperation | undefined>(undefined);
function handleDelete(operation: OperationID) {
setSelected(prev => prev.filter(item => item !== operation));
onChange(prev => prev.filter(item => item !== operation));
}
function handleSelect(operation?: IOperation) {
if (operation) {
setLastSelected(operation);
setSelected(prev => [...prev, operation.id]);
onChange(prev => [...prev, operation.id]);
setTimeout(() => setLastSelected(undefined), 1000);
}
}
function handleMoveUp(operation: OperationID) {
const index = selected.indexOf(operation);
const index = value.indexOf(operation);
if (index > 0) {
setSelected(prev => {
onChange(prev => {
const newSelected = [...prev];
newSelected[index] = newSelected[index - 1];
newSelected[index - 1] = operation;
@ -51,9 +51,9 @@ function PickMultiOperation({ rows, items, selected, setSelected, className, ...
}
function handleMoveDown(operation: OperationID) {
const index = selected.indexOf(operation);
if (index < selected.length - 1) {
setSelected(prev => {
const index = value.indexOf(operation);
if (index < value.length - 1) {
onChange(prev => {
const newSelected = [...prev];
newSelected[index] = newSelected[index + 1];
newSelected[index + 1] = operation;
@ -118,7 +118,7 @@ function PickMultiOperation({ rows, items, selected, setSelected, className, ...
noBorder
items={nonSelectedItems} // prettier: split-line
value={lastSelected}
onSelectValue={handleSelect}
onChange={handleSelect}
/>
<DataTable
dense

View File

@ -19,14 +19,15 @@ import SelectLocation from './SelectLocation';
interface PickSchemaProps extends CProps.Styling {
id?: string;
value?: LibraryItemID;
onChange: (newValue: LibraryItemID) => void;
initialFilter?: string;
rows?: number;
items: ILibraryItem[];
itemType: LibraryItemType;
value?: LibraryItemID;
baseFilter?: (target: ILibraryItem) => boolean;
onSelectValue: (newValue: LibraryItemID) => void;
}
const columnHelper = createColumnHelper<ILibraryItem>();
@ -38,7 +39,7 @@ function PickSchema({
items,
itemType,
value,
onSelectValue,
onChange,
baseFilter,
className,
...restProps
@ -156,7 +157,7 @@ function PickSchema({
<p>Измените параметры фильтра</p>
</FlexColumn>
}
onRowClicked={rowData => onSelectValue(rowData.id)}
onRowClicked={rowData => onChange(rowData.id)}
/>
</div>
);

View File

@ -20,8 +20,9 @@ import { errors } from '@/utils/labels';
import SelectLibraryItem from './SelectLibraryItem';
interface PickSubstitutionsProps extends CProps.Styling {
substitutions: ICstSubstitute[];
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
value: ICstSubstitute[];
onChange: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
suggestions?: ICstSubstitute[];
prefixID: string;
@ -35,8 +36,8 @@ interface PickSubstitutionsProps extends CProps.Styling {
const columnHelper = createColumnHelper<IMultiSubstitution>();
function PickSubstitutions({
substitutions,
setSubstitutions,
value,
onChange,
suggestions,
prefixID,
rows,
@ -66,7 +67,7 @@ function PickSubstitutions({
) ?? [];
const substitutionData: IMultiSubstitution[] = [
...substitutions.map(item => ({
...value.map(item => ({
original_source: getSchemaByCst(item.original)!,
original: getConstituenta(item.original)!,
substitution: getConstituenta(item.substitution)!,
@ -110,8 +111,8 @@ function PickSubstitutions({
original: deleteRight ? rightCst.id : leftCst.id,
substitution: deleteRight ? leftCst.id : rightCst.id
};
const toDelete = substitutions.map(item => item.original);
const replacements = substitutions.map(item => item.substitution);
const toDelete = value.map(item => item.original);
const replacements = value.map(item => item.substitution);
if (
toDelete.includes(newSubstitution.original) ||
toDelete.includes(newSubstitution.substitution) ||
@ -126,7 +127,7 @@ function PickSubstitutions({
return;
}
}
setSubstitutions(prev => [...prev, newSubstitution]);
onChange(prev => [...prev, newSubstitution]);
setLeftCst(undefined);
setRightCst(undefined);
}
@ -136,12 +137,12 @@ function PickSubstitutions({
}
function handleAcceptSuggestion(item: IMultiSubstitution) {
setSubstitutions(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
onChange(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
}
function handleDeleteSubstitution(target: IMultiSubstitution) {
handleDeclineSuggestion(target);
setSubstitutions(prev => {
onChange(prev => {
const newItems: ICstSubstitute[] = [];
prev.forEach(item => {
if (item.original !== target.original.id || item.substitution !== target.substitution.id) {
@ -230,15 +231,15 @@ function PickSubstitutions({
placeholder='Выберите аргумент'
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== rightArgument?.id)}
value={leftArgument}
onSelectValue={setLeftArgument}
onChange={setLeftArgument}
/>
<SelectConstituenta
noBorder
items={(leftArgument as IRSForm)?.items.filter(
cst => !substitutions.find(item => item.original === cst.id) && (!filter || filter(cst))
cst => !value.find(item => item.original === cst.id) && (!filter || filter(cst))
)}
value={leftCst}
onSelectValue={setLeftCst}
onChange={setLeftCst}
/>
</div>
<div className='flex flex-col gap-1'>
@ -268,15 +269,15 @@ function PickSubstitutions({
placeholder='Выберите аргумент'
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== leftArgument?.id)}
value={rightArgument}
onSelectValue={setRightArgument}
onChange={setRightArgument}
/>
<SelectConstituenta
noBorder
items={(rightArgument as IRSForm)?.items.filter(
cst => !substitutions.find(item => item.original === cst.id) && (!filter || filter(cst))
cst => !value.find(item => item.original === cst.id) && (!filter || filter(cst))
)}
value={rightCst}
onSelectValue={setRightCst}
onChange={setRightCst}
/>
</div>
</div>

View File

@ -15,6 +15,7 @@ import { describeAccessPolicy, labelAccessPolicy } from '@/utils/labels';
interface SelectAccessPolicyProps extends CProps.Styling {
value: AccessPolicy;
onChange: (value: AccessPolicy) => void;
disabled?: boolean;
stretchLeft?: boolean;
}

View File

@ -10,10 +10,10 @@ import { matchConstituenta } from '@/models/rsformAPI';
import { describeConstituenta, describeConstituentaTerm } from '@/utils/labels';
interface SelectConstituentaProps extends CProps.Styling {
items?: IConstituenta[];
value?: IConstituenta;
onSelectValue: (newValue?: IConstituenta) => void;
onChange: (newValue?: IConstituenta) => void;
items?: IConstituenta[];
placeholder?: string;
noBorder?: boolean;
}
@ -22,7 +22,7 @@ function SelectConstituenta({
className,
items,
value,
onSelectValue,
onChange,
placeholder = 'Выберите конституенту',
...restProps
}: SelectConstituentaProps) {
@ -42,7 +42,7 @@ function SelectConstituenta({
className={clsx('text-ellipsis', className)}
options={options}
value={value ? { value: value.id, label: `${value.alias}: ${describeConstituentaTerm(value)}` } : null}
onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))}
onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter}
placeholder={placeholder}

View File

@ -15,8 +15,8 @@ import { describeCstSource, labelCstSource } from '@/utils/labels';
interface SelectGraphFilterProps extends CProps.Styling {
value: DependencyMode;
dense?: boolean;
onChange: (value: DependencyMode) => void;
dense?: boolean;
}
function SelectGraphFilter({ value, dense, onChange, ...restProps }: SelectGraphFilterProps) {

View File

@ -10,7 +10,7 @@ import { matchLibraryItem } from '@/models/libraryAPI';
interface SelectLibraryItemProps extends CProps.Styling {
items?: ILibraryItem[];
value?: ILibraryItem;
onSelectValue: (newValue?: ILibraryItem) => void;
onChange: (newValue?: ILibraryItem) => void;
placeholder?: string;
noBorder?: boolean;
@ -20,7 +20,7 @@ function SelectLibraryItem({
className,
items,
value,
onSelectValue,
onChange,
placeholder = 'Выберите схему',
...restProps
}: SelectLibraryItemProps) {
@ -40,7 +40,7 @@ function SelectLibraryItem({
className={clsx('text-ellipsis', className)}
options={options}
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))}
onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter}
placeholder={placeholder}

View File

@ -12,9 +12,10 @@ import { labelFolderNode } from '@/utils/labels';
interface SelectLocationProps extends CProps.Styling {
value: string;
onClick: (event: CProps.EventMouse, target: FolderNode) => void;
prefix: string;
dense?: boolean;
onClick: (event: CProps.EventMouse, target: FolderNode) => void;
}
function SelectLocation({ value, dense, prefix, onClick, className, style }: SelectLocationProps) {

View File

@ -14,10 +14,9 @@ import SelectLocation from './SelectLocation';
interface SelectLocationContextProps extends CProps.Styling {
value: string;
onChange: (newValue: string) => void;
title?: string;
stretchTop?: boolean;
onChange: (newValue: string) => void;
}
function SelectLocationContext({

View File

@ -15,8 +15,8 @@ import { describeCstMatchMode, labelCstMatchMode } from '@/utils/labels';
interface SelectMatchModeProps extends CProps.Styling {
value: CstMatchMode;
dense?: boolean;
onChange: (value: CstMatchMode) => void;
dense?: boolean;
}
function SelectMatchMode({ value, dense, onChange, ...restProps }: SelectMatchModeProps) {

View File

@ -10,11 +10,11 @@ interface SelectMultiGrammemeProps
extends Omit<SelectMultiProps<IGrammemeOption>, 'value' | 'onChange'>,
CProps.Styling {
value: IGrammemeOption[];
onChangeValue: (newValue: IGrammemeOption[]) => void;
onChange: (newValue: IGrammemeOption[]) => void;
placeholder?: string;
}
function SelectMultiGrammeme({ value, onChangeValue, ...restProps }: SelectMultiGrammemeProps) {
function SelectMultiGrammeme({ value, onChange, ...restProps }: SelectMultiGrammemeProps) {
const [options, setOptions] = useState<IGrammemeOption[]>([]);
useEffect(() => {
@ -28,7 +28,7 @@ function SelectMultiGrammeme({ value, onChangeValue, ...restProps }: SelectMulti
<SelectMulti
options={options}
value={value}
onChange={newValue => onChangeValue([...newValue].sort(compareGrammemeOptions))}
onChange={newValue => onChange([...newValue].sort(compareGrammemeOptions))}
{...restProps}
/>
);

View File

@ -10,7 +10,7 @@ import { matchOperation } from '@/models/ossAPI';
interface SelectOperationProps extends CProps.Styling {
items?: IOperation[];
value?: IOperation;
onSelectValue: (newValue?: IOperation) => void;
onChange: (newValue?: IOperation) => void;
placeholder?: string;
noBorder?: boolean;
@ -20,7 +20,7 @@ function SelectOperation({
className,
items,
value,
onSelectValue,
onChange,
placeholder = 'Выберите операцию',
...restProps
}: SelectOperationProps) {
@ -40,7 +40,7 @@ function SelectOperation({
className={clsx('text-ellipsis', className)}
options={options}
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))}
onChange={data => onChange(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter}
placeholder={placeholder}

View File

@ -11,7 +11,7 @@ import { matchUser } from '@/models/userAPI';
interface SelectUserProps extends CProps.Styling {
value?: UserID;
onSelectValue: (newValue: UserID) => void;
onChange: (newValue: UserID) => void;
filter?: (userID: UserID) => boolean;
placeholder?: string;
@ -22,7 +22,7 @@ function SelectUser({
className,
filter,
value,
onSelectValue,
onChange,
placeholder = 'Выберите пользователя',
...restProps
}: SelectUserProps) {
@ -46,7 +46,7 @@ function SelectUser({
options={options}
value={value ? { value: value, label: getUserLabel(value) } : null}
onChange={data => {
if (data?.value !== undefined) onSelectValue(data.value);
if (data?.value !== undefined) onChange(data.value);
}}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filterLabel}

View File

@ -11,13 +11,13 @@ interface SelectVersionProps extends CProps.Styling {
id?: string;
items?: IVersionInfo[];
value?: VersionID;
onSelectValue: (newValue?: VersionID) => void;
onChange: (newValue?: VersionID) => void;
placeholder?: string;
noBorder?: boolean;
}
function SelectVersion({ id, className, items, value, onSelectValue, ...restProps }: SelectVersionProps) {
function SelectVersion({ id, className, items, value, onChange, ...restProps }: SelectVersionProps) {
const options = [
{
value: undefined,
@ -40,7 +40,7 @@ function SelectVersion({ id, className, items, value, onSelectValue, ...restProp
className={clsx('min-w-[12rem] text-ellipsis', className)}
options={options}
value={{ value: value, label: valueLabel }}
onChange={data => onSelectValue(data?.value)}
onChange={data => onChange(data?.value)}
{...restProps}
/>
);

View File

@ -10,16 +10,16 @@ import { prefixes } from '@/utils/constants';
import { DefaultWordForms, IGrammemeOption, SelectorGrammemes } from '@/utils/selectors';
interface SelectWordFormProps extends CProps.Styling {
selected: IGrammemeOption[];
setSelected: React.Dispatch<React.SetStateAction<IGrammemeOption[]>>;
value: IGrammemeOption[];
onChange: React.Dispatch<React.SetStateAction<IGrammemeOption[]>>;
}
function SelectWordForm({ selected, setSelected, className, ...restProps }: SelectWordFormProps) {
function SelectWordForm({ value, onChange, className, ...restProps }: SelectWordFormProps) {
const handleSelect = useCallback(
(grams: Grammeme[]) => {
setSelected(SelectorGrammemes.filter(({ value }) => grams.includes(value as Grammeme)));
onChange(SelectorGrammemes.filter(({ value }) => grams.includes(value as Grammeme)));
},
[setSelected]
[onChange]
);
return (
@ -30,7 +30,7 @@ function SelectWordForm({ selected, setSelected, className, ...restProps }: Sele
text={data.text}
example={data.example}
grams={data.grams}
isSelected={data.grams.every(gram => selected.find(item => (item.value as Grammeme) === gram))}
isSelected={data.grams.every(gram => value.find(item => (item.value as Grammeme) === gram))}
onSelectGrams={handleSelect}
/>
))}

View File

@ -17,37 +17,37 @@ import MiniButton from '@/components/ui/MiniButton';
import { Graph } from '@/models/Graph';
interface ToolbarGraphSelectionProps extends CProps.Styling {
value: number[];
onChange: (newSelection: number[]) => void;
graph: Graph;
selected: number[];
isCore: (item: number) => boolean;
isOwned?: (item: number) => boolean;
setSelected: (newSelection: number[]) => void;
emptySelection?: boolean;
}
function ToolbarGraphSelection({
className,
graph,
selected,
value: selected,
isCore,
isOwned,
setSelected,
onChange,
emptySelection,
...restProps
}: ToolbarGraphSelectionProps) {
const handleSelectCore = useCallback(() => {
const core = [...graph.nodes.keys()].filter(isCore);
setSelected([...core, ...graph.expandInputs(core)]);
}, [setSelected, graph, isCore]);
onChange([...core, ...graph.expandInputs(core)]);
}, [onChange, graph, isCore]);
const handleSelectOwned = useCallback(
() => (isOwned ? setSelected([...graph.nodes.keys()].filter(isOwned)) : undefined),
[setSelected, graph, isOwned]
() => (isOwned ? onChange([...graph.nodes.keys()].filter(isOwned)) : undefined),
[onChange, graph, isOwned]
);
const handleInvertSelection = useCallback(
() => setSelected([...graph.nodes.keys()].filter(item => !selected.includes(item))),
[setSelected, selected, graph]
() => onChange([...graph.nodes.keys()].filter(item => !selected.includes(item))),
[onChange, selected, graph]
);
return (
@ -55,37 +55,37 @@ function ToolbarGraphSelection({
<MiniButton
titleHtml='Сбросить выделение'
icon={<IconReset size='1.25rem' className='icon-primary' />}
onClick={() => setSelected([])}
onClick={() => onChange([])}
disabled={emptySelection}
/>
<MiniButton
titleHtml='Выделить все влияющие'
icon={<IconGraphCollapse size='1.25rem' className='icon-primary' />}
onClick={() => setSelected([...selected, ...graph.expandAllInputs(selected)])}
onClick={() => onChange([...selected, ...graph.expandAllInputs(selected)])}
disabled={emptySelection}
/>
<MiniButton
titleHtml='Выделить все зависимые'
icon={<IconGraphExpand size='1.25rem' className='icon-primary' />}
onClick={() => setSelected([...selected, ...graph.expandAllOutputs(selected)])}
onClick={() => onChange([...selected, ...graph.expandAllOutputs(selected)])}
disabled={emptySelection}
/>
<MiniButton
titleHtml='<b>Максимизация</b> <br/>дополнение выделения конституентами, <br/>зависимыми только от выделенных'
icon={<IconGraphMaximize size='1.25rem' className='icon-primary' />}
onClick={() => setSelected(graph.maximizePart(selected))}
onClick={() => onChange(graph.maximizePart(selected))}
disabled={emptySelection}
/>
<MiniButton
titleHtml='Выделить поставщиков'
icon={<IconGraphInputs size='1.25rem' className='icon-primary' />}
onClick={() => setSelected([...selected, ...graph.expandInputs(selected)])}
onClick={() => onChange([...selected, ...graph.expandInputs(selected)])}
disabled={emptySelection}
/>
<MiniButton
titleHtml='Выделить потребителей'
icon={<IconGraphOutputs size='1.25rem' className='icon-primary' />}
onClick={() => setSelected([...selected, ...graph.expandOutputs(selected)])}
onClick={() => onChange([...selected, ...graph.expandOutputs(selected)])}
disabled={emptySelection}
/>
<MiniButton

View File

@ -4,7 +4,7 @@ import { CheckboxChecked } from '@/components/Icons';
import { CProps } from '@/components/props';
import { globals } from '@/utils/constants';
export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick'> {
export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick' | 'onChange'> {
/** Label to display next to the checkbox. */
label?: string;
@ -15,7 +15,7 @@ export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick'>
value?: boolean;
/** Callback to set the `value`. */
setValue?: (newValue: boolean) => void;
onChange?: (newValue: boolean) => void;
}
/**
@ -29,18 +29,18 @@ function Checkbox({
hideTitle,
className,
value,
setValue,
onChange,
...restProps
}: CheckboxProps) {
const cursor = disabled ? 'cursor-arrow' : setValue ? 'cursor-pointer' : '';
const cursor = disabled ? 'cursor-arrow' : onChange ? 'cursor-pointer' : '';
function handleClick(event: CProps.EventMouse): void {
event.preventDefault();
event.stopPropagation();
if (disabled || !setValue) {
if (disabled || !onChange) {
return;
}
setValue(!value);
onChange(!value);
}
return (

View File

@ -6,12 +6,12 @@ import { globals } from '@/utils/constants';
import { CheckboxProps } from './Checkbox';
export interface CheckboxTristateProps extends Omit<CheckboxProps, 'value' | 'setValue'> {
export interface CheckboxTristateProps extends Omit<CheckboxProps, 'value' | 'onChange'> {
/** Current value - `null`, `true` or `false`. */
value: boolean | null;
/** Callback to set the `value`. */
setValue?: (newValue: boolean | null) => void;
onChange?: (newValue: boolean | null) => void;
}
/**
@ -25,23 +25,23 @@ function CheckboxTristate({
hideTitle,
className,
value,
setValue,
onChange,
...restProps
}: CheckboxTristateProps) {
const cursor = disabled ? 'cursor-arrow' : setValue ? 'cursor-pointer' : '';
const cursor = disabled ? 'cursor-arrow' : onChange ? 'cursor-pointer' : '';
function handleClick(event: CProps.EventMouse): void {
event.preventDefault();
event.stopPropagation();
if (disabled || !setValue) {
if (disabled || !onChange) {
return;
}
if (value === false) {
setValue(null);
onChange(null);
} else if (value === null) {
setValue(true);
onChange(true);
} else {
setValue(false);
onChange(false);
}
}

View File

@ -22,7 +22,7 @@ function SelectAll<TData>({ table, resetLastSelected }: SelectAllProps<TData>) {
value={
!table.getIsAllPageRowsSelected() && table.getIsSomePageRowsSelected() ? null : table.getIsAllPageRowsSelected()
}
setValue={handleChange}
onChange={handleChange}
/>
);
}

View File

@ -15,7 +15,7 @@ function SelectRow<TData>({ row, onChangeLastSelected }: SelectRowProps<TData>)
row.toggleSelected(value);
}
return <Checkbox tabIndex={-1} value={row.getIsSelected()} setValue={handleChange} />;
return <Checkbox tabIndex={-1} value={row.getIsSelected()} onChange={handleChange} />;
}
export default SelectRow;

View File

@ -3,7 +3,7 @@ import clsx from 'clsx';
import Checkbox, { CheckboxProps } from './Checkbox';
/** Animated {@link Checkbox} inside a {@link Dropdown} item. */
function DropdownCheckbox({ setValue, disabled, ...restProps }: CheckboxProps) {
function DropdownCheckbox({ onChange: setValue, disabled, ...restProps }: CheckboxProps) {
return (
<div
className={clsx(
@ -13,7 +13,7 @@ function DropdownCheckbox({ setValue, disabled, ...restProps }: CheckboxProps) {
!!setValue && !disabled && 'clr-hover'
)}
>
<Checkbox tabIndex={-1} disabled={disabled} setValue={setValue} {...restProps} />
<Checkbox tabIndex={-1} disabled={disabled} onChange={setValue} {...restProps} />
</div>
);
}

View File

@ -19,7 +19,7 @@ interface SelectTreeProps<ItemType> extends CProps.Styling {
prefix: string;
/** Callback to be called when the value changes. */
onChangeValue: (newItem: ItemType) => void;
onChange: (newItem: ItemType) => void;
/** Callback providing the parent of the item. */
getParent: (item: ItemType) => ItemType;
@ -40,7 +40,7 @@ function SelectTree<ItemType>({
getParent,
getLabel,
getDescription,
onChangeValue,
onChange,
prefix,
...restProps
}: SelectTreeProps<ItemType>) {
@ -75,7 +75,7 @@ function SelectTree<ItemType>({
function handleSetValue(event: CProps.EventMouse, target: ItemType) {
event.preventDefault();
event.stopPropagation();
onChangeValue(target);
onChange(target);
}
return (

View File

@ -61,7 +61,7 @@ function DlgChangeInputSchema() {
items={sortedItems}
itemType={LibraryItemType.RSFORM}
value={selected} // prettier: split-line
onSelectValue={handleSelectLocation}
onChange={handleSelectLocation}
rows={14}
baseFilter={baseFilter}
/>

View File

@ -6,7 +6,7 @@ import { useState } from 'react';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls';
import { useAuthSuspense } from '@/backend/auth/useAuth';
import { IRSFormCloneDTO } from '@/backend/library/api';
import { IRCloneLibraryItemDTO } from '@/backend/library/api';
import { useCloneItem } from '@/backend/library/useCloneItem';
import { VisibilityIcon } from '@/components/DomainIcons';
import SelectAccessPolicy from '@/components/select/SelectAccessPolicy';
@ -59,7 +59,7 @@ function DlgCloneLibraryItem() {
}
function handleSubmit() {
const data: IRSFormCloneDTO = {
const data: IRCloneLibraryItemDTO = {
id: base.id,
item_type: base.item_type,
title: title,
@ -137,7 +137,7 @@ function DlgCloneLibraryItem() {
id='dlg_only_selected'
label={`Только выбранные конституенты [${selected.length} из ${totalCount}]`}
value={onlySelected}
setValue={value => setOnlySelected(value)}
onChange={value => setOnlySelected(value)}
/>
</Modal>
);

View File

@ -98,7 +98,7 @@ function TabInputOperation({
</div>
<Checkbox
value={createSchema}
setValue={onChangeCreateSchema}
onChange={onChangeCreateSchema}
label='Создать новую схему'
titleHtml='Создать пустую схему для загрузки'
/>
@ -108,7 +108,7 @@ function TabInputOperation({
items={sortedItems}
value={attachedID}
itemType={LibraryItemType.RSFORM}
onSelectValue={onChangeAttachedID}
onChange={onChangeAttachedID}
rows={8}
baseFilter={baseFilter}
/>

View File

@ -57,7 +57,7 @@ function TabSynthesisOperation({
<FlexColumn>
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} />
<PickMultiOperation items={oss.items} selected={inputs} setSelected={setInputs} rows={6} />
<PickMultiOperation items={oss.items} value={inputs} onChange={setInputs} rows={6} />
</FlexColumn>
</div>
);

View File

@ -64,7 +64,7 @@ function DlgCreateVersion() {
id='dlg_only_selected'
label={`Только выбранные конституенты [${selected.length} из ${totalCount}]`}
value={onlySelected}
setValue={value => setOnlySelected(value)}
onChange={value => setOnlySelected(value)}
/>
</Modal>
);

View File

@ -190,7 +190,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
id='dlg_argument_picker'
value={selectedCst}
data={schema.items}
onSelectValue={handleSelectConstituenta}
onChange={handleSelectConstituenta}
prefixID={prefixes.cst_modal_list}
rows={7}
/>

View File

@ -102,7 +102,7 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
id='dlg_template_picker'
value={state.prototype}
data={filteredData}
onSelectValue={cst => partialUpdate({ prototype: cst })}
onChange={cst => partialUpdate({ prototype: cst })}
prefixID={prefixes.cst_template_ist}
className='rounded-t-none'
rows={8}

View File

@ -55,7 +55,7 @@ function DlgDeleteCst() {
label='Удалить зависимые конституенты'
className='mb-2'
value={expandOut}
setValue={value => setExpandOut(value)}
onChange={value => setExpandOut(value)}
/>
{hasInherited ? (
<p className='text-sm clr-text-red'>Внимание! Выбранные конституенты имеют наследников в ОСС</p>

View File

@ -39,7 +39,7 @@ function DlgDeleteOperation() {
label='Сохранить наследованные конституенты'
titleHtml='Наследованные конституенты <br/>превратятся в дописанные'
value={keepConstituents}
setValue={setKeepConstituents}
onChange={setKeepConstituents}
disabled={target.result === null}
/>
<Checkbox
@ -50,7 +50,7 @@ function DlgDeleteOperation() {
: 'Удалить схему вместе с операцией'
}
value={deleteSchema}
setValue={setDeleteSchema}
onChange={setDeleteSchema}
disabled={!target.is_owned || target.result === null}
/>
</Modal>

View File

@ -63,7 +63,7 @@ function DlgEditEditors() {
<SelectUser
filter={id => !selected.includes(id)}
value={undefined}
onSelectValue={onAddEditor}
onChange={onAddEditor}
className='w-[25rem]'
/>
</div>

View File

@ -19,7 +19,7 @@ function TabArguments({ oss, inputs, target, setInputs }: TabArgumentsProps) {
<div className='cc-fade-in cc-column'>
<FlexColumn>
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} />
<PickMultiOperation items={filtered} selected={inputs} setSelected={setInputs} rows={8} />
<PickMultiOperation items={filtered} value={inputs} onChange={setInputs} rows={8} />
</FlexColumn>
</div>
);

View File

@ -29,8 +29,8 @@ function TabSynthesis({
schemas={schemas}
prefixID={prefixes.dlg_cst_substitutes_list}
rows={8}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
value={substitutions}
onChange={setSubstitutions}
suggestions={suggestions}
/>
<TextArea

View File

@ -64,7 +64,7 @@ function TabEntityReference({ initial, schema, onChangeValid, onChangeReference
initialFilter={initial.text}
value={selectedCst}
data={schema.items}
onSelectValue={handleSelectConstituenta}
onChange={handleSelectConstituenta}
prefixID={prefixes.cst_modal_list}
describeFunc={cst => cst.term_resolved}
matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)}
@ -94,7 +94,7 @@ function TabEntityReference({ initial, schema, onChangeValid, onChangeReference
/>
</div>
<SelectWordForm selected={selectedGrams} setSelected={setSelectedGrams} />
<SelectWordForm value={selectedGrams} onChange={setSelectedGrams} />
<div className='flex items-center gap-4'>
<Label text='Словоформа' />
@ -104,7 +104,7 @@ function TabEntityReference({ initial, schema, onChangeValid, onChangeReference
className='flex-grow'
menuPlacement='top'
value={selectedGrams}
onChangeValue={setSelectedGrams}
onChange={setSelectedGrams}
/>
</div>
</div>

View File

@ -172,7 +172,7 @@ function DlgEditWordForms() {
placeholder='Выберите граммемы'
className='min-w-[15rem] h-fit'
value={inputGrams}
onChangeValue={setInputGrams}
onChange={setInputGrams}
/>
</div>

View File

@ -31,31 +31,31 @@ function DlgGraphParams() {
label='Скрыть текст'
title='Не отображать термины'
value={params.noText}
setValue={value => updateParams({ noText: value })}
onChange={value => updateParams({ noText: value })}
/>
<Checkbox
label='Скрыть несвязанные'
title='Неиспользуемые конституенты'
value={params.noHermits}
setValue={value => updateParams({ noHermits: value })}
onChange={value => updateParams({ noHermits: value })}
/>
<Checkbox
label='Скрыть шаблоны'
titleHtml='Терм-функции и предикат-функции <br/>с параметризованными аргументами'
value={params.noTemplates}
setValue={value => updateParams({ noTemplates: value })}
onChange={value => updateParams({ noTemplates: value })}
/>
<Checkbox
label='Транзитивная редукция'
titleHtml='Удалить связи, образующие <br/>транзитивные пути в графе'
value={params.noTransitive}
setValue={value => updateParams({ noTransitive: value })}
onChange={value => updateParams({ noTransitive: value })}
/>
<Checkbox
label='Свернуть порожденные'
title='Не отображать порожденные понятия'
value={params.foldDerived}
setValue={value => updateParams({ foldDerived: value })}
onChange={value => updateParams({ foldDerived: value })}
/>
</div>
<div className='flex flex-col gap-1'>
@ -63,42 +63,42 @@ function DlgGraphParams() {
<Checkbox
label={labelCstType(CstType.BASE)}
value={params.allowBase}
setValue={value => updateParams({ allowBase: value })}
onChange={value => updateParams({ allowBase: value })}
/>
<Checkbox
label={labelCstType(CstType.STRUCTURED)}
value={params.allowStruct}
setValue={value => updateParams({ allowStruct: value })}
onChange={value => updateParams({ allowStruct: value })}
/>
<Checkbox
label={labelCstType(CstType.TERM)}
value={params.allowTerm}
setValue={value => updateParams({ allowTerm: value })}
onChange={value => updateParams({ allowTerm: value })}
/>
<Checkbox
label={labelCstType(CstType.AXIOM)}
value={params.allowAxiom}
setValue={value => updateParams({ allowAxiom: value })}
onChange={value => updateParams({ allowAxiom: value })}
/>
<Checkbox
label={labelCstType(CstType.FUNCTION)}
value={params.allowFunction}
setValue={value => updateParams({ allowFunction: value })}
onChange={value => updateParams({ allowFunction: value })}
/>
<Checkbox
label={labelCstType(CstType.PREDICATE)}
value={params.allowPredicate}
setValue={value => updateParams({ allowPredicate: value })}
onChange={value => updateParams({ allowPredicate: value })}
/>
<Checkbox
label={labelCstType(CstType.CONSTANT)}
value={params.allowConstant}
setValue={value => updateParams({ allowConstant: value })}
onChange={value => updateParams({ allowConstant: value })}
/>
<Checkbox
label={labelCstType(CstType.THEOREM)}
value={params.allowTheorem}
setValue={value => updateParams({ allowTheorem: value })}
onChange={value => updateParams({ allowTheorem: value })}
/>
</div>
</Modal>

View File

@ -21,8 +21,8 @@ function TabConstituents({ itemID, selected, setSelected }: TabConstituentsProps
data={schema.items}
rows={13}
prefixID={prefixes.cst_inline_synth_list}
selected={selected}
setSelected={setSelected}
value={selected}
onChange={setSelected}
/>
);
}

View File

@ -25,7 +25,7 @@ function TabSource({ selected, receiver, setSelected }: TabSourceProps) {
itemType={LibraryItemType.RSFORM}
rows={14}
value={selected}
onSelectValue={setSelected}
onChange={setSelected}
/>
<div className='flex items-center gap-6 '>
<span className='select-none'>Выбрана</span>

View File

@ -22,8 +22,8 @@ function TabSubstitutions({ sourceID, receiver, selected, substitutions, setSubs
return (
<PickSubstitutions
substitutions={substitutions}
setSubstitutions={setSubstitutions}
value={substitutions}
onChange={setSubstitutions}
rows={10}
prefixID={prefixes.cst_inline_synth_substitutes}
schemas={schemas}

View File

@ -103,7 +103,7 @@ function DlgRelocateConstituents() {
placeholder='Выберите исходную схему'
items={sourceSchemas}
value={source}
onSelectValue={handleSelectSource}
onChange={handleSelectSource}
/>
<MiniButton
title='Направление перемещения'
@ -116,7 +116,7 @@ function DlgRelocateConstituents() {
placeholder='Выберите целевую схему'
items={destinationSchemas}
value={destination}
onSelectValue={handleSelectDestination}
onChange={handleSelectDestination}
/>
</div>
{sourceData.isLoading ? <Loader /> : null}
@ -127,8 +127,8 @@ function DlgRelocateConstituents() {
data={filteredConstituents}
rows={12}
prefixID={prefixes.dlg_cst_constituents_list}
selected={selected}
setSelected={setSelected}
value={selected}
onChange={setSelected}
/>
) : null}
</div>

View File

@ -40,8 +40,8 @@ function DlgSubstituteCst() {
>
<PickSubstitutions
allowSelfSubstitution
substitutions={substitutions}
setSubstitutions={setSubstitutions}
value={substitutions}
onChange={setSubstitutions}
rows={6}
prefixID={prefixes.dlg_cst_substitutes_list}
schemas={[schema]}

View File

@ -53,7 +53,7 @@ function DlgUploadRSForm() {
label='Загружать название и комментарий'
className='py-2'
value={loadMetadata}
setValue={value => setLoadMetadata(value)}
onChange={value => setLoadMetadata(value)}
/>
</Modal>
);

View File

@ -127,7 +127,7 @@ function ToolbarSearch({ total, filtered }: ToolbarSearchProps) {
placeholder='Выберите владельца'
className='min-w-[15rem] text-sm mx-1 mb-1'
value={filterUser}
onSelectValue={setFilterUser}
onChange={setFilterUser}
/>
</Dropdown>
</div>

View File

@ -57,7 +57,7 @@ function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) {
<SelectTree
items={Object.values(HelpTopic).map(item => item as HelpTopic)}
value={activeTopic}
onChangeValue={handleSelectTopic}
onChange={handleSelectTopic}
prefix={prefixes.topic_list}
getParent={item => topicParent.get(item) ?? item}
getLabel={labelHelpTopic}

View File

@ -17,7 +17,7 @@ function TopicsStatic({ activeTopic, onChangeTopic }: TopicsStaticProps) {
<SelectTree
items={Object.values(HelpTopic).map(item => item as HelpTopic)}
value={activeTopic}
onChangeValue={onChangeTopic}
onChange={onChangeTopic}
prefix={prefixes.topic_list}
getParent={item => topicParent.get(item) ?? item}
getLabel={labelHelpTopic}

View File

@ -3,7 +3,7 @@
import clsx from 'clsx';
import { useEffect, useState } from 'react';
import { ILibraryUpdateDTO } from '@/backend/library/api';
import { IUpdateLibraryItemDTO } from '@/backend/library/api';
import { useUpdateItem } from '@/backend/library/useUpdateItem';
import { useMutatingOss } from '@/backend/oss/useMutatingOss';
import { IconSave } from '@/components/Icons';
@ -73,7 +73,7 @@ function FormOSS({ id }: FormOSSProps) {
if (!schema) {
return;
}
const data: ILibraryUpdateDTO = {
const data: IUpdateLibraryItemDTO = {
id: schema.id,
item_type: LibraryItemType.RSFORM,
title: title,

View File

@ -17,9 +17,9 @@ export function Component() {
const router = useConceptNavigation();
const token = useQueryStrings().get('token') ?? '';
const { validateToken, resetPassword, isPending, error } = useResetPassword();
const { validateToken, resetPassword, isPending, error: serverError } = useResetPassword();
const [isValidated, setIsValidated] = useState(false);
const [isTokenValidated, setIsTokenValidated] = useState(false);
const [newPassword, setNewPassword] = useState('');
const [newPasswordRepeat, setNewPasswordRepeat] = useState('');
@ -45,11 +45,11 @@ export function Component() {
}
useEffect(() => {
if (!isValidated && !isPending) {
if (!isTokenValidated && !isPending) {
validateToken({ token: token });
setIsValidated(true);
setIsTokenValidated(true);
}
}, [token, validateToken, isValidated, isPending]);
}, [token, validateToken, isTokenValidated, isPending]);
if (isPending) {
return <Loader />;
@ -88,13 +88,13 @@ export function Component() {
loading={isPending}
disabled={!canSubmit}
/>
{error ? <ProcessError error={error} /> : null}
{serverError ? <ServerError error={serverError} /> : null}
</form>
);
}
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
function ServerError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 404) {
return <div className='mx-auto mt-6 text-sm select-text text-warn-600'>Данная ссылка не действительна</div>;
}

View File

@ -112,7 +112,7 @@ function EditorLibraryItem({ controller }: EditorLibraryItemProps) {
<SelectUser
className='w-[25rem] sm:w-[26rem] text-sm'
value={controller.schema.owner ?? undefined}
onSelectValue={onSelectUser}
onChange={onSelectUser}
/>
) : null}
</Overlay>

View File

@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls';
import { ILibraryUpdateDTO } from '@/backend/library/api';
import { IUpdateLibraryItemDTO } from '@/backend/library/api';
import { useUpdateItem } from '@/backend/library/useUpdateItem';
import { useMutatingRSForm } from '@/backend/rsform/useMutatingRSForm';
import { IconSave } from '@/components/Icons';
@ -83,7 +83,7 @@ function FormRSForm({ id }: FormRSFormProps) {
if (!schema) {
return;
}
const data: ILibraryUpdateDTO = {
const data: IUpdateLibraryItemDTO = {
id: schema.id,
item_type: LibraryItemType.RSFORM,
title: title,
@ -131,7 +131,7 @@ function FormRSForm({ id }: FormRSFormProps) {
className='select-none'
value={schema.version} //
items={schema.versions}
onSelectValue={handleSelectVersion}
onChange={handleSelectVersion}
/>
</div>
</div>

View File

@ -311,8 +311,8 @@ function TGFlow() {
? cstID => !controller.schema.cstByID.get(cstID)?.is_inherited
: undefined
}
selected={controller.selected}
setSelected={handleSetSelected}
value={controller.selected}
onChange={handleSetSelected}
emptySelection={controller.selected.length === 0}
/>
) : null}

View File

@ -141,11 +141,11 @@ function FormSignup() {
</div>
<div className='flex gap-1 text-sm'>
<Checkbox id='accept_terms' label='Принимаю условия' value={acceptPrivacy} setValue={setAcceptPrivacy} />
<Checkbox id='accept_terms' label='Принимаю условия' value={acceptPrivacy} onChange={setAcceptPrivacy} />
<TextURL text='обработки персональных данных...' href={urls.help_topic(HelpTopic.INFO_PRIVACY)} />
</div>
<div className='flex gap-1 text-sm'>
<Checkbox id='accept_rules' label='Принимаю ' value={acceptRules} setValue={setAcceptRules} />
<Checkbox id='accept_rules' label='Принимаю ' value={acceptRules} onChange={setAcceptRules} />
<TextURL text='правила поведения на Портале...' href={urls.help_topic(HelpTopic.INFO_RULES)} />
</div>

View File

@ -11,7 +11,7 @@ import TextInput from '@/components/ui/TextInput';
import TextURL from '@/components/ui/TextURL';
export function Component() {
const { requestPasswordReset, isPending, error, reset } = useRequestPasswordReset();
const { requestPasswordReset, isPending, error: serverError, reset: clearServerError } = useRequestPasswordReset();
const [isCompleted, setIsCompleted] = useState(false);
const [email, setEmail] = useState('');
@ -24,8 +24,8 @@ export function Component() {
}
useEffect(() => {
reset();
}, [email, reset]);
clearServerError();
}, [email, clearServerError]);
if (isCompleted) {
return (
@ -54,14 +54,14 @@ export function Component() {
loading={isPending}
disabled={!email}
/>
{error ? <ProcessError error={error} /> : null}
{serverError ? <ServerError error={serverError} /> : null}
</form>
);
}
}
// ====== Internals =========
function ProcessError({ error }: { error: ErrorData }): React.ReactElement {
function ServerError({ error }: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
return (
<div className='mx-auto mt-6 text-sm select-text text-warn-600'>Данный email не используется на Портале.</div>