Refactor dialogs using partialUpdate hook for states

This commit is contained in:
IRBorisov 2023-11-01 20:14:50 +03:00
parent e1d82d4b3a
commit 90c8303ee7
7 changed files with 142 additions and 220 deletions

View File

@ -5,9 +5,10 @@ import SelectSingle from '../components/Common/SelectSingle';
import TextArea from '../components/Common/TextArea';
import TextInput from '../components/Common/TextInput';
import RSInput from '../components/RSInput';
import usePartialUpdate from '../hooks/usePartialUpdate';
import { CstType,ICstCreateData, IRSForm } from '../models/rsform';
import { labelCstType } from '../utils/labels';
import { createAliasFor, getCstTypePrefix } from '../utils/misc';
import { createAliasFor, validateCstAlias } from '../utils/misc';
import { SelectorCstType } from '../utils/selectors';
interface DlgCreateCstProps
@ -19,53 +20,31 @@ extends Pick<ModalProps, 'hideWindow'> {
function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstProps) {
const [validated, setValidated] = useState(false);
const [selectedType, setSelectedType] = useState<CstType>(CstType.BASE);
const [alias, setAlias] = useState('');
const [term, setTerm] = useState('');
const [textDefinition, setTextDefinition] = useState('');
const [expression, setExpression] = useState('');
const [convention, setConvention] = useState('');
function getData(): ICstCreateData {
return {
cst_type: selectedType,
insert_after: initial?.insert_after ?? null,
alias: alias,
convention: convention,
definition_formal: expression,
definition_raw: textDefinition,
term_raw: term,
const [cstData, updateCstData] = usePartialUpdate(
initial || {
cst_type: CstType.BASE,
insert_after: null,
alias: '',
convention: '',
definition_formal: '',
definition_raw: '',
term_raw: '',
term_forms: []
};
}
const handleSubmit = () => onCreate(getData());
useLayoutEffect(() => {
if (initial) {
setSelectedType(initial.cst_type);
setTerm(initial.term_raw);
setTextDefinition(initial.definition_raw);
setExpression(initial.definition_formal);
setConvention(initial.convention);
setAlias(initial.alias);
}
}, [initial]);
);
const handleSubmit = () => onCreate(cstData);
useLayoutEffect(
() => {
setAlias(createAliasFor(selectedType, schema));
}, [selectedType, schema]);
updateCstData({ alias: createAliasFor(cstData.cst_type, schema) });
}, [cstData.cst_type, updateCstData, schema]);
useEffect(
() => {
if(alias.length < 2 || alias[0] !== getCstTypePrefix(selectedType)) {
setValidated(false);
} else {
setValidated(!schema.items.find(cst => cst.alias === alias))
}
}, [alias, selectedType, schema]);
setValidated(validateCstAlias(cstData.alias, cstData.cst_type, schema));
}, [cstData.alias, cstData.cst_type, schema]);
return (
<Modal
@ -81,43 +60,43 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
className='my-2 min-w-[15rem] self-center'
options={SelectorCstType}
placeholder='Выберите тип'
value={selectedType ? { value: selectedType, label: labelCstType(selectedType) } : null}
onChange={data => setSelectedType(data?.value ?? CstType.BASE)}
value={{ value: cstData.cst_type, label: labelCstType(cstData.cst_type) }}
onChange={data => updateCstData({ cst_type: data?.value ?? CstType.BASE})}
/>
<TextInput id='alias' label='Имя'
dense
dimensions='w-[7rem]'
value={alias}
onChange={event => setAlias(event.target.value)}
value={cstData.alias}
onChange={event => updateCstData({ alias: event.target.value})}
/>
</div>
<TextArea id='term' label='Термин'
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
rows={2}
value={term}
value={cstData.term_raw}
spellCheck
onChange={event => setTerm(event.target.value)}
onChange={event => updateCstData({ term_raw: event.target.value })}
/>
<RSInput id='expression' label='Формальное выражение'
placeholder='Родоструктурное выражение, задающее формальное определение'
editable
height='4.8rem'
value={expression}
onChange={value => setExpression(value)}
value={cstData.definition_formal}
onChange={value => updateCstData({definition_formal: value})}
/>
<TextArea id='definition' label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения'
rows={2}
value={textDefinition}
value={cstData.definition_raw}
spellCheck
onChange={event => setTextDefinition(event.target.value)}
onChange={event => updateCstData({ definition_raw: event.target.value })}
/>
<TextArea id='convention' label='Конвенция / Комментарий'
placeholder='Договоренность об интерпретации неопределяемого понятия&#x000D;&#x000A;Комментарий к производному понятию'
rows={2}
value={convention}
value={cstData.convention}
spellCheck
onChange={event => setConvention(event.target.value)}
onChange={event => updateCstData({ convention: event.target.value })}
/>
</div>
</Modal>

View File

@ -1,7 +1,6 @@
import { useLayoutEffect, useState } from 'react';
import Checkbox from '../components/Common/Checkbox';
import Modal, { ModalProps } from '../components/Common/Modal';
import usePartialUpdate from '../hooks/usePartialUpdate';
import { GraphEditorParams } from '../models/miscelanious';
import { CstType } from '../models/rsform';
import { labelCstType } from '../utils/labels';
@ -12,60 +11,14 @@ extends Pick<ModalProps, 'hideWindow'> {
onConfirm: (params: GraphEditorParams) => void
}
function DlgGraphOptions({ hideWindow, initial, onConfirm }:DlgGraphOptionsProps) {
const [ noHermits, setNoHermits ] = useState(true);
const [ noTransitive, setNoTransitive ] = useState(false);
const [ noTemplates, setNoTemplates ] = useState(true);
const [ noTerms, setNoTerms ] = useState(true);
const [ allowBase, setAllowBase ] = useState(true);
const [ allowStruct, setAllowStruct ] = useState(true);
const [ allowTerm, setAllowTerm ] = useState(true);
const [ allowAxiom, setAllowAxiom ] = useState(true);
const [ allowFunction, setAllowFunction ] = useState(true);
const [ allowPredicate, setAllowPredicate ] = useState(true);
const [ allowConstant, setAllowConstant ] = useState(true);
const [ allowTheorem, setAllowTheorem ] = useState(true);
function getParams() {
return {
noHermits: noHermits,
noTransitive: noTransitive,
noTemplates: noTemplates,
noTerms: noTerms,
allowBase: allowBase,
allowStruct: allowStruct,
allowTerm: allowTerm,
allowAxiom: allowAxiom,
allowFunction: allowFunction,
allowPredicate: allowPredicate,
allowConstant: allowConstant,
allowTheorem: allowTheorem
}
}
function DlgGraphOptions({ hideWindow, initial, onConfirm } : DlgGraphOptionsProps) {
const [params, updateParams] = usePartialUpdate(initial);
const handleSubmit = () => {
hideWindow();
onConfirm(getParams());
onConfirm(params);
};
useLayoutEffect(() => {
setNoHermits(initial.noHermits);
setNoTransitive(initial.noTransitive);
setNoTemplates(initial.noTemplates);
setNoTerms(initial.noTerms);
setAllowBase(initial.allowBase);
setAllowStruct(initial.allowStruct);
setAllowTerm(initial.allowTerm);
setAllowAxiom(initial.allowAxiom);
setAllowFunction(initial.allowFunction);
setAllowPredicate(initial.allowPredicate);
setAllowConstant(initial.allowConstant);
setAllowTheorem(initial.allowTheorem);
}, [initial]);
return (
<Modal
hideWindow={hideWindow}
@ -80,69 +33,69 @@ function DlgGraphOptions({ hideWindow, initial, onConfirm }:DlgGraphOptionsProps
<Checkbox
label='Скрыть текст'
tooltip='Не отображать термины'
value={noTerms}
setValue={ value => setNoTerms(value) }
value={params.noTerms}
setValue={ value => updateParams({noTerms: value}) }
/>
<Checkbox
label='Скрыть несвязанные'
tooltip='Неиспользуемые конституенты'
value={noHermits}
setValue={ value => setNoHermits(value) }
value={params.noHermits}
setValue={ value => updateParams({ noHermits: value}) }
/>
<Checkbox
label='Скрыть шаблоны'
tooltip='Терм-функции и предикат-функции с параметризованными аргументами'
value={noTemplates}
setValue={ value => setNoTemplates(value) }
value={params.noTemplates}
setValue={ value => updateParams({ noTemplates: value}) }
/>
<Checkbox
label='Транзитивная редукция'
tooltip='Удалить связи, образующие транзитивные пути в графе'
value={noTransitive}
setValue={ value => setNoTransitive(value) }
value={params.noTransitive}
setValue={ value => updateParams({ noTransitive: value}) }
/>
</div>
<div className='flex flex-col gap-1'>
<h1>Типы конституент</h1>
<Checkbox
label={labelCstType(CstType.BASE)}
value={allowBase}
setValue={ value => setAllowBase(value) }
value={params.allowBase}
setValue={ value => updateParams({ allowBase: value}) }
/>
<Checkbox
label={labelCstType(CstType.STRUCTURED)}
value={allowStruct}
setValue={ value => setAllowStruct(value) }
value={params.allowStruct}
setValue={ value => updateParams({ allowStruct: value}) }
/>
<Checkbox
label={labelCstType(CstType.TERM)}
value={allowTerm}
setValue={ value => setAllowTerm(value) }
value={params.allowTerm}
setValue={ value => updateParams({ allowTerm: value}) }
/>
<Checkbox
label={labelCstType(CstType.AXIOM)}
value={allowAxiom}
setValue={ value => setAllowAxiom(value) }
value={params.allowAxiom}
setValue={ value => updateParams({ allowAxiom: value}) }
/>
<Checkbox
label={labelCstType(CstType.FUNCTION)}
value={allowFunction}
setValue={ value => setAllowFunction(value) }
value={params.allowFunction}
setValue={ value => updateParams({ allowFunction: value}) }
/>
<Checkbox
label={labelCstType(CstType.PREDICATE)}
value={allowPredicate}
setValue={ value => setAllowPredicate(value) }
value={params.allowPredicate}
setValue={ value => updateParams({ allowPredicate: value}) }
/>
<Checkbox
label={labelCstType(CstType.CONSTANT)}
value={allowConstant}
setValue={ value => setAllowConstant(value) }
value={params.allowConstant}
setValue={ value => updateParams({ allowConstant: value}) }
/>
<Checkbox
label={labelCstType(CstType.THEOREM)}
value={allowTheorem}
setValue ={ value => setAllowTheorem(value) }
value={params.allowTheorem}
setValue ={ value => updateParams({ allowTheorem: value}) }
/>
</div>
</div>

View File

@ -4,60 +4,40 @@ import Modal, { ModalProps } from '../components/Common/Modal';
import SelectSingle from '../components/Common/SelectSingle';
import TextInput from '../components/Common/TextInput';
import { useRSForm } from '../context/RSFormContext';
import usePartialUpdate from '../hooks/usePartialUpdate';
import { CstType, ICstRenameData } from '../models/rsform';
import { labelCstType } from '../utils/labels';
import { createAliasFor, getCstTypePrefix } from '../utils/misc';
import { createAliasFor, validateCstAlias } from '../utils/misc';
import { SelectorCstType } from '../utils/selectors';
interface DlgRenameCstProps
extends Pick<ModalProps, 'hideWindow'> {
initial?: ICstRenameData
initial: ICstRenameData
onRename: (data: ICstRenameData) => void
}
function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
const { schema } = useRSForm();
const [validated, setValidated] = useState(false);
const [cstType, setCstType] = useState<CstType>(CstType.BASE);
const [cstID, setCstID] = useState(0)
const [alias, setAlias] = useState('');
function getData(): ICstRenameData {
return {
cst_type: cstType,
alias: alias,
id: cstID
}
}
const handleSubmit = () => onRename(getData());
const [cstData, updateData] = usePartialUpdate(initial);
const handleSubmit = () => onRename(cstData);
useLayoutEffect(
() => {
if (schema && initial && cstType !== initial.cst_type) {
setAlias(createAliasFor(cstType, schema));
if (schema && initial && cstData.cst_type !== initial.cst_type) {
updateData({ alias: createAliasFor(cstData.cst_type, schema)});
}
}, [initial, cstType, schema]);
}, [initial, cstData.cst_type, updateData, schema]);
useLayoutEffect(
() => {
if (initial) {
setCstType(initial.cst_type);
setAlias(initial.alias);
setCstID(initial.id);
}
}, [initial]);
useLayoutEffect(
() => {
if (!initial || !schema) {
setValidated(false);
} else if(alias === initial.alias || alias.length < 2 || alias[0] !== getCstTypePrefix(cstType)) {
setValidated(false);
} else {
setValidated(!schema.items.find(cst => cst.alias === alias))
}
}, [cstType, alias, initial, schema]);
setValidated(
!!schema &&
cstData.alias !== initial.alias &&
validateCstAlias(cstData.alias, cstData.cst_type, schema)
);
}, [cstData.cst_type, cstData.alias, initial, schema]);
return (
<Modal
@ -73,15 +53,15 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
className='min-w-[14rem] self-center z-modal-top'
options={SelectorCstType}
placeholder='Выберите тип'
value={cstType ? { value: cstType, label: labelCstType(cstType) } : null}
onChange={data => setCstType(data?.value ?? CstType.BASE)}
value={{ value: cstData.cst_type, label: labelCstType(cstData.cst_type) }}
onChange={data => updateData({cst_type: data?.value ?? CstType.BASE})}
/>
<div>
<TextInput id='alias' label='Имя'
dense
dimensions='w-[7rem]'
value={alias}
onChange={event => setAlias(event.target.value)}
value={cstData.alias}
onChange={event => updateData({alias: event.target.value})}
/>
</div>
</div>

View File

@ -1,18 +1,19 @@
import { useEffect, useLayoutEffect, useState } from 'react';
import ConceptTooltip from '../components/Common/ConceptTooltip';
import Modal, { ModalProps } from '../components/Common/Modal';
import SelectSingle from '../components/Common/SelectSingle';
import SwitchButton from '../components/Common/SwitchButton';
import TextArea from '../components/Common/TextArea';
import TextInput from '../components/Common/TextInput';
import HelpRSTemplates from '../components/Help/HelpRSTemplates';
import { HelpIcon } from '../components/Icons';
import RSInput from '../components/RSInput';
import { CstType,ICstCreateData, IRSForm } from '../models/rsform';
import { labelCstType } from '../utils/labels';
import { createAliasFor, getCstTypePrefix } from '../utils/misc';
import { SelectorCstType } from '../utils/selectors';
import ConceptTooltip from '../../components/Common/ConceptTooltip';
import Modal, { ModalProps } from '../../components/Common/Modal';
import SelectSingle from '../../components/Common/SelectSingle';
import SwitchButton from '../../components/Common/SwitchButton';
import TextArea from '../../components/Common/TextArea';
import TextInput from '../../components/Common/TextInput';
import HelpRSTemplates from '../../components/Help/HelpRSTemplates';
import { HelpIcon } from '../../components/Icons';
import RSInput from '../../components/RSInput';
import usePartialUpdate from '../../hooks/usePartialUpdate';
import { CstType,ICstCreateData, IRSForm } from '../../models/rsform';
import { labelCstType } from '../../utils/labels';
import { createAliasFor, validateCstAlias } from '../../utils/misc';
import { SelectorCstType } from '../../utils/selectors';
interface DlgTemplatesProps
extends Pick<ModalProps, 'hideWindow'> {
@ -22,44 +23,30 @@ extends Pick<ModalProps, 'hideWindow'> {
function DlgTemplates({ hideWindow, schema, onCreate }: DlgTemplatesProps) {
const [validated, setValidated] = useState(false);
const [selectedType, setSelectedType] = useState<CstType>(CstType.TERM);
const [alias, setAlias] = useState('');
const [term, setTerm] = useState('');
const [textDefinition, setTextDefinition] = useState('');
const [expression, setExpression] = useState('');
const [convention, setConvention] = useState('');
const [cstData, updateCstData] = usePartialUpdate({
cst_type: CstType.TERM,
insert_after: null,
alias: '',
convention: '',
definition_formal: '',
definition_raw: '',
term_raw: '',
term_forms: []
});
const [ showAttributes, setShowAttributes ] = useState(false);
function getData(): ICstCreateData {
return {
cst_type: selectedType,
insert_after: null,
alias: alias,
convention: convention,
definition_formal: expression,
definition_raw: textDefinition,
term_raw: term,
term_forms: []
};
}
const handleSubmit = () => onCreate(getData());
const handleSubmit = () => onCreate(cstData);
useLayoutEffect(
() => {
setAlias(createAliasFor(selectedType, schema));
}, [selectedType, schema]);
updateCstData({ alias: createAliasFor(cstData.cst_type, schema) });
}, [cstData.cst_type, updateCstData, schema]);
useEffect(
() => {
if(alias.length < 2 || alias[0] !== getCstTypePrefix(selectedType)) {
setValidated(false);
} else {
setValidated(!schema.items.find(cst => cst.alias === alias))
}
}, [alias, selectedType, schema]);
setValidated(validateCstAlias(cstData.alias, cstData.cst_type, schema));
}, [cstData.alias, cstData.cst_type, schema]);
return (
<Modal
@ -111,43 +98,43 @@ function DlgTemplates({ hideWindow, schema, onCreate }: DlgTemplatesProps) {
className='my-2 min-w-[15rem] self-center'
options={SelectorCstType}
placeholder='Выберите тип'
value={selectedType ? { value: selectedType, label: labelCstType(selectedType) } : null}
onChange={data => setSelectedType(data?.value ?? CstType.BASE)}
value={{ value: cstData.cst_type, label: labelCstType(cstData.cst_type) }}
onChange={data => updateCstData({ cst_type: data?.value ?? CstType.TERM})}
/>
<TextInput id='alias' label='Имя'
dense
dimensions='w-[7rem]'
value={alias}
onChange={event => setAlias(event.target.value)}
value={cstData.alias}
onChange={event => updateCstData({ alias: event.target.value})}
/>
</div>
<TextArea id='term' label='Термин'
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
rows={2}
value={term}
value={cstData.term_raw}
spellCheck
onChange={event => setTerm(event.target.value)}
onChange={event => updateCstData({ term_raw: event.target.value })}
/>
<RSInput id='expression' label='Формальное выражение'
placeholder='Родоструктурное выражение, задающее формальное определение'
editable
height='4.8rem'
value={expression}
onChange={value => setExpression(value)}
value={cstData.definition_formal}
onChange={value => updateCstData({definition_formal: value})}
/>
<TextArea id='definition' label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения'
rows={2}
value={textDefinition}
value={cstData.definition_raw}
spellCheck
onChange={event => setTextDefinition(event.target.value)}
onChange={event => updateCstData({ definition_raw: event.target.value })}
/>
<TextArea id='convention' label='Конвенция / Комментарий'
placeholder='Договоренность об интерпретации неопределяемого понятия&#x000D;&#x000A;Комментарий к производному понятию'
rows={2}
value={convention}
value={cstData.convention}
spellCheck
onChange={event => setConvention(event.target.value)}
onChange={event => updateCstData({ convention: event.target.value })}
/>
</div>}
</div>

View File

@ -0,0 +1,15 @@
import { useReducer } from 'react';
function usePartialUpdate<ValueType>(initialValue: ValueType) {
const [value, updateValue] = useReducer(
(data: ValueType, newData: Partial<ValueType>) => ({
...data,
...newData,
}),
initialValue
);
return [value, updateValue] as [ValueType, typeof updateValue];
}
export default usePartialUpdate;

View File

@ -360,7 +360,7 @@ function RSTabs() {
<DlgRenameCst
hideWindow={() => setShowRenameCst(false)}
onRename={handleRenameCst}
initial={renameInitialData}
initial={renameInitialData!}
/>}
{showDeleteCst &&
<DlgDeleteCst

View File

@ -19,6 +19,14 @@ export function getCstTypePrefix(type: CstType) {
}
}
export function validateCstAlias(alias: string, type: CstType, schema: IRSForm): boolean {
return (
alias.length >= 2 &&
alias[0] == getCstTypePrefix(type) &&
!schema.items.find(cst => cst.alias === alias)
);
}
export function getCstExpressionPrefix(cst: IConstituenta): string {
return cst.alias + (cst.cst_type === CstType.STRUCTURED ? '::=' : ':==');
}