Update constituenta creation

This commit is contained in:
IRBorisov 2024-04-01 15:11:17 +03:00
parent 99f9bdb856
commit 52b4953fc7
10 changed files with 233 additions and 239 deletions

View File

@ -1,76 +0,0 @@
'use client';
import RSInput from '@/components/RSInput';
import SelectSingle from '@/components/ui/SelectSingle';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import { CstType, ICstCreateData } from '@/models/rsform';
import { labelCstType } from '@/utils/labels';
import { SelectorCstType } from '@/utils/selectors';
interface ConstituentaTabProps {
state: ICstCreateData;
partialUpdate: React.Dispatch<Partial<ICstCreateData>>;
}
function ConstituentaTab({ state, partialUpdate }: ConstituentaTabProps) {
return (
<>
<div className='flex self-center gap-3 pr-2'>
<SelectSingle
id='dlg_cst_type'
className='min-w-[14rem]'
options={SelectorCstType}
placeholder='Выберите тип'
value={{ value: state.cst_type, label: labelCstType(state.cst_type) }}
onChange={data => partialUpdate({ cst_type: data?.value ?? CstType.TERM })}
/>
<TextInput
id='dlg_cst_alias'
dense
label='Имя'
className='w-[7rem]'
value={state.alias}
onChange={event => partialUpdate({ alias: event.target.value })}
/>
</div>
<TextArea
id='dlg_cst_term'
spellCheck
label='Термин'
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
rows={2}
value={state.term_raw}
onChange={event => partialUpdate({ term_raw: event.target.value })}
/>
<RSInput
id='dlg_cst_expression'
label='Формальное определение'
placeholder='Родоструктурное выражение, задающее формальное определение'
height='5.1rem'
value={state.definition_formal}
onChange={value => partialUpdate({ definition_formal: value })}
/>
<TextArea
id='dlg_cst_definition'
label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения'
rows={2}
value={state.definition_raw}
spellCheck
onChange={event => partialUpdate({ definition_raw: event.target.value })}
/>
<TextArea
id='dlg_cst_convention'
spellCheck
label='Конвенция / Комментарий'
placeholder='Договоренность об интерпретации или пояснение к схеме'
rows={2}
value={state.convention}
onChange={event => partialUpdate({ convention: event.target.value })}
/>
</>
);
}
export default ConstituentaTab;

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useLayoutEffect, useMemo, useState } from 'react'; import { useLayoutEffect, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import HelpButton from '@/components/man/HelpButton'; import HelpButton from '@/components/man/HelpButton';
@ -11,11 +11,11 @@ import TabLabel from '@/components/ui/TabLabel';
import usePartialUpdate from '@/hooks/usePartialUpdate'; import usePartialUpdate from '@/hooks/usePartialUpdate';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform'; import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
import { generateAlias, validateNewAlias } from '@/models/rsformAPI'; import { generateAlias } from '@/models/rsformAPI';
import { inferTemplatedType, substituteTemplateArgs } from '@/models/rslangAPI'; import { inferTemplatedType, substituteTemplateArgs } from '@/models/rslangAPI';
import FormCreateCst from '../DlgCreateCst/FormCreateCst';
import ArgumentsTab, { IArgumentsState } from './ArgumentsTab'; import ArgumentsTab, { IArgumentsState } from './ArgumentsTab';
import ConstituentaTab from './ConstituentaTab';
import TemplateTab, { ITemplateState } from './TemplateTab'; import TemplateTab, { ITemplateState } from './TemplateTab';
interface DlgConstituentaTemplateProps extends Pick<ModalProps, 'hideWindow'> { interface DlgConstituentaTemplateProps extends Pick<ModalProps, 'hideWindow'> {
@ -49,11 +49,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
term_forms: [] term_forms: []
}); });
const validated = useMemo( const [validated, setValidated] = useState(false);
() => validateNewAlias(constituenta.alias, constituenta.cst_type, schema),
[constituenta.alias, constituenta.cst_type, schema]
);
const handleSubmit = () => onCreate(constituenta); const handleSubmit = () => onCreate(constituenta);
useLayoutEffect(() => { useLayoutEffect(() => {
@ -138,7 +134,12 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
</TabPanel> </TabPanel>
<TabPanel className='cc-column' style={{ display: activeTab === TabID.CONSTITUENTA ? '' : 'none' }}> <TabPanel className='cc-column' style={{ display: activeTab === TabID.CONSTITUENTA ? '' : 'none' }}>
<ConstituentaTab state={constituenta} partialUpdate={updateConstituenta} /> <FormCreateCst
state={constituenta}
partialUpdate={updateConstituenta}
schema={schema}
setValidated={setValidated}
/>
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</Modal> </Modal>

View File

@ -1,113 +0,0 @@
'use client';
import clsx from 'clsx';
import { useEffect, useLayoutEffect, useState } from 'react';
import RSInput from '@/components/RSInput';
import Modal, { ModalProps } from '@/components/ui/Modal';
import SelectSingle from '@/components/ui/SelectSingle';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import usePartialUpdate from '@/hooks/usePartialUpdate';
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
import { generateAlias, validateNewAlias } from '@/models/rsformAPI';
import { labelCstType } from '@/utils/labels';
import { SelectorCstType } from '@/utils/selectors';
interface DlgCreateCstProps extends Pick<ModalProps, 'hideWindow'> {
initial?: ICstCreateData;
schema: IRSForm;
onCreate: (data: ICstCreateData) => void;
}
function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstProps) {
const [validated, setValidated] = useState(false);
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(cstData);
useLayoutEffect(() => {
updateCstData({ alias: generateAlias(cstData.cst_type, schema) });
}, [cstData.cst_type, updateCstData, schema]);
useEffect(() => {
setValidated(validateNewAlias(cstData.alias, cstData.cst_type, schema));
}, [cstData.alias, cstData.cst_type, schema]);
return (
<Modal
header='Создание конституенты'
hideWindow={hideWindow}
canSubmit={validated}
onSubmit={handleSubmit}
submitText='Создать'
className={clsx('cc-column', 'w-[35rem]', 'py-2 px-6')}
>
<div className='flex self-center gap-6'>
<SelectSingle
id='dlg_cst_type'
placeholder='Выберите тип'
className='min-w-[15rem]'
options={SelectorCstType}
value={{ value: cstData.cst_type, label: labelCstType(cstData.cst_type) }}
onChange={data => updateCstData({ cst_type: data?.value ?? CstType.BASE })}
/>
<TextInput
id='dlg_cst_alias'
dense
label='Имя'
className='w-[7rem]'
value={cstData.alias}
onChange={event => updateCstData({ alias: event.target.value })}
/>
</div>
<TextArea
id='dlg_cst_term'
spellCheck
label='Термин'
placeholder='Обозначение, используемое в текстовых определениях'
rows={2}
value={cstData.term_raw}
onChange={event => updateCstData({ term_raw: event.target.value })}
/>
<RSInput
id='dlg_cst_expression'
label='Формальное определение'
placeholder='Родоструктурное выражение'
value={cstData.definition_formal}
onChange={value => updateCstData({ definition_formal: value })}
/>
<TextArea
id='dlg_cst_definition'
spellCheck
label='Текстовое определение'
placeholder='Текстовая интерпретация формального выражения'
rows={2}
value={cstData.definition_raw}
onChange={event => updateCstData({ definition_raw: event.target.value })}
/>
<TextArea
id='dlg_cst_convention'
spellCheck
label='Конвенция / Комментарий'
placeholder='Договоренность об интерпретации или пояснение'
rows={2}
value={cstData.convention}
onChange={event => updateCstData({ convention: event.target.value })}
/>
</Modal>
);
}
export default DlgCreateCst;

View File

@ -0,0 +1,57 @@
'use client';
import { useEffect, useLayoutEffect, useState } from 'react';
import Modal, { ModalProps } from '@/components/ui/Modal';
import usePartialUpdate from '@/hooks/usePartialUpdate';
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
import { generateAlias, validateNewAlias } from '@/models/rsformAPI';
import FormCreateCst from './FormCreateCst';
interface DlgCreateCstProps extends Pick<ModalProps, 'hideWindow'> {
initial?: ICstCreateData;
schema: IRSForm;
onCreate: (data: ICstCreateData) => void;
}
function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstProps) {
const [validated, setValidated] = useState(false);
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(cstData);
useLayoutEffect(() => {
updateCstData({ alias: generateAlias(cstData.cst_type, schema) });
}, [cstData.cst_type, updateCstData, schema]);
useEffect(() => {
setValidated(validateNewAlias(cstData.alias, cstData.cst_type, schema));
}, [cstData.alias, cstData.cst_type, schema]);
return (
<Modal
header='Создание конституенты'
hideWindow={hideWindow}
canSubmit={validated}
onSubmit={handleSubmit}
submitText='Создать'
className='cc-column w-[35rem] h-[30rem] py-2 px-6'
>
<FormCreateCst schema={schema} state={cstData} partialUpdate={updateCstData} setValidated={setValidated} />
</Modal>
);
}
export default DlgCreateCst;

View File

@ -0,0 +1,123 @@
'use client';
import { AnimatePresence } from 'framer-motion';
import { useLayoutEffect, useMemo, useState } from 'react';
import RSInput from '@/components/RSInput';
import SelectSingle from '@/components/ui/SelectSingle';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
import { generateAlias, isBaseSet, isBasicConcept, isFunctional, validateNewAlias } from '@/models/rsformAPI';
import { labelCstType } from '@/utils/labels';
import { SelectorCstType } from '@/utils/selectors';
interface FormCreateCstProps {
schema: IRSForm;
state: ICstCreateData;
partialUpdate: React.Dispatch<Partial<ICstCreateData>>;
setValidated: React.Dispatch<React.SetStateAction<boolean>>;
}
function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreateCstProps) {
const [forceComment, setForceComment] = useState(false);
const isBasic = useMemo(() => isBasicConcept(state.cst_type), [state]);
const isElementary = useMemo(() => isBaseSet(state.cst_type), [state]);
const showConvention = useMemo(() => !!state.convention || forceComment || isBasic, [state, forceComment, isBasic]);
useLayoutEffect(() => {
partialUpdate({ alias: generateAlias(state.cst_type, schema) });
setForceComment(false);
}, [state.cst_type, partialUpdate, schema]);
useLayoutEffect(() => {
setValidated(validateNewAlias(state.alias, state.cst_type, schema));
}, [state.alias, state.cst_type, schema, setValidated]);
return (
<AnimatePresence>
<div className='flex self-center gap-6'>
<SelectSingle
id='dlg_cst_type'
placeholder='Выберите тип'
className='w-[15rem]'
options={SelectorCstType}
value={{ value: state.cst_type, label: labelCstType(state.cst_type) }}
onChange={data => partialUpdate({ cst_type: data?.value ?? CstType.BASE })}
/>
<TextInput
id='dlg_cst_alias'
dense
label='Имя'
className='w-[7rem]'
value={state.alias}
onChange={event => partialUpdate({ alias: event.target.value })}
/>
</div>
<TextArea
id='dlg_cst_term'
spellCheck
label='Термин'
placeholder='Обозначение, используемое в текстовых определениях'
rows={2}
value={state.term_raw}
onChange={event => partialUpdate({ term_raw: event.target.value })}
/>
<AnimateFade hideContent={!state.definition_formal && isElementary}>
<RSInput
id='dlg_cst_expression'
label={
state.cst_type === CstType.STRUCTURED
? 'Область определения'
: isFunctional(state.cst_type)
? 'Определение функции'
: 'Формальное определение'
}
placeholder={
state.cst_type !== CstType.STRUCTURED
? 'Родоструктурное выражение'
: 'Определение множества, которому принадлежат элементы родовой структуры'
}
value={state.definition_formal}
onChange={value => partialUpdate({ definition_formal: value })}
/>
</AnimateFade>
<AnimateFade hideContent={!state.definition_raw && isElementary}>
<TextArea
id='dlg_cst_definition'
spellCheck
label='Текстовое определение'
placeholder='Текстовая интерпретация формального выражения'
rows={2}
value={state.definition_raw}
onChange={event => partialUpdate({ definition_raw: event.target.value })}
/>
</AnimateFade>
{!showConvention ? (
<button
type='button'
className='self-start cc-label clr-text-url hover:underline'
onClick={() => setForceComment(true)}
>
Добавить комментарий
</button>
) : null}
<AnimateFade hideContent={!showConvention}>
<TextArea
id='dlg_cst_convention'
spellCheck
label={isBasic ? 'Конвенция' : 'Комментарий'}
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
rows={2}
value={state.convention}
onChange={event => partialUpdate({ convention: event.target.value })}
/>
</AnimateFade>
</AnimatePresence>
);
}
export default FormCreateCst;

View File

@ -0,0 +1 @@
export { default } from './DlgCreateCst';

View File

@ -121,7 +121,7 @@ function CreateRSFormPage() {
/> />
<TextArea <TextArea
id='schema_comment' id='schema_comment'
label='Комментарий' label='Описание'
placeholder={file && 'Загрузить из файла'} placeholder={file && 'Загрузить из файла'}
value={comment} value={comment}
onChange={event => setComment(event.target.value)} onChange={event => setComment(event.target.value)}

View File

@ -105,7 +105,7 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
disabled={disabled} disabled={disabled}
showList={showList} showList={showList}
id={globals.constituenta_editor} id={globals.constituenta_editor}
constituenta={activeCst} state={activeCst}
isModified={isModified} isModified={isModified}
toggleReset={toggleReset} toggleReset={toggleReset}
onToggleList={() => setShowList(prev => !prev)} onToggleList={() => setShowList(prev => !prev)}

View File

@ -28,7 +28,7 @@ interface FormConstituentaProps {
showList: boolean; showList: boolean;
id?: string; id?: string;
constituenta?: IConstituenta; state?: IConstituenta;
isModified: boolean; isModified: boolean;
toggleReset: boolean; toggleReset: boolean;
@ -43,7 +43,7 @@ function FormConstituenta({
disabled, disabled,
showList, showList,
id, id,
constituenta, state,
isModified, isModified,
setIsModified, setIsModified,
@ -61,33 +61,34 @@ function FormConstituenta({
const [expression, setExpression] = useState(''); const [expression, setExpression] = useState('');
const [convention, setConvention] = useState(''); const [convention, setConvention] = useState('');
const [typification, setTypification] = useState('N/A'); const [typification, setTypification] = useState('N/A');
const [forceComment, setForceComment] = useState(false); const [forceComment, setForceComment] = useState(false);
const isBasic = useMemo(() => !!constituenta && isBasicConcept(constituenta.cst_type), [constituenta]); const isBasic = useMemo(() => !!state && isBasicConcept(state.cst_type), [state]);
const isElementary = useMemo(() => !!constituenta && isBaseSet(constituenta.cst_type), [constituenta]); const isElementary = useMemo(() => !!state && isBaseSet(state.cst_type), [state]);
const showConvention = useMemo( const showConvention = useMemo(
() => !constituenta || !!constituenta.convention || forceComment || isBasic, () => !state || !!state.convention || forceComment || isBasic,
[constituenta, forceComment, isBasic] [state, forceComment, isBasic]
); );
useEffect(() => { useEffect(() => {
if (!constituenta) { if (!state) {
setIsModified(false); setIsModified(false);
return; return;
} }
setIsModified( setIsModified(
constituenta.term_raw !== term || state.term_raw !== term ||
constituenta.definition_raw !== textDefinition || state.definition_raw !== textDefinition ||
constituenta.convention !== convention || state.convention !== convention ||
constituenta.definition_formal !== expression state.definition_formal !== expression
); );
return () => setIsModified(false); return () => setIsModified(false);
}, [ }, [
constituenta, state,
constituenta?.term_raw, state?.term_raw,
constituenta?.definition_formal, state?.definition_formal,
constituenta?.definition_raw, state?.definition_raw,
constituenta?.convention, state?.convention,
term, term,
textDefinition, textDefinition,
expression, expression,
@ -96,26 +97,26 @@ function FormConstituenta({
]); ]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (constituenta) { if (state) {
setAlias(constituenta.alias); setAlias(state.alias);
setConvention(constituenta.convention || ''); setConvention(state.convention || '');
setTerm(constituenta.term_raw || ''); setTerm(state.term_raw || '');
setTextDefinition(constituenta.definition_raw || ''); setTextDefinition(state.definition_raw || '');
setExpression(constituenta.definition_formal || ''); setExpression(state.definition_formal || '');
setTypification(constituenta ? labelCstTypification(constituenta) : 'N/A'); setTypification(state ? labelCstTypification(state) : 'N/A');
setForceComment(false); setForceComment(false);
} }
}, [constituenta, schema, toggleReset]); }, [state, schema, toggleReset]);
function handleSubmit(event?: React.FormEvent<HTMLFormElement>) { function handleSubmit(event?: React.FormEvent<HTMLFormElement>) {
if (event) { if (event) {
event.preventDefault(); event.preventDefault();
} }
if (!constituenta || processing) { if (!state || processing) {
return; return;
} }
const data: ICstUpdateData = { const data: ICstUpdateData = {
id: constituenta.id, id: state.id,
alias: alias, alias: alias,
convention: convention, convention: convention,
definition_formal: expression, definition_formal: expression,
@ -131,7 +132,7 @@ function FormConstituenta({
disabled={disabled} disabled={disabled}
modified={isModified} modified={isModified}
processing={processing} processing={processing}
constituenta={constituenta} constituenta={state}
onEditTerm={onEditTerm} onEditTerm={onEditTerm}
onRename={onRename} onRename={onRename}
/> />
@ -147,8 +148,8 @@ function FormConstituenta({
placeholder='Обозначение, используемое в текстовых определениях' placeholder='Обозначение, используемое в текстовых определениях'
items={schema?.items} items={schema?.items}
value={term} value={term}
initialValue={constituenta?.term_raw ?? ''} initialValue={state?.term_raw ?? ''}
resolved={constituenta?.term_resolved ?? ''} resolved={state?.term_resolved ?? ''}
disabled={disabled} disabled={disabled}
onChange={newValue => setTerm(newValue)} onChange={newValue => setTerm(newValue)}
/> />
@ -165,23 +166,23 @@ function FormConstituenta({
resize: 'none' resize: 'none'
}} }}
/> />
<AnimateFade hideContent={!!constituenta && !constituenta?.definition_formal && isElementary}> <AnimateFade hideContent={!!state && !state?.definition_formal && isElementary}>
<EditorRSExpression <EditorRSExpression
id='cst_expression' id='cst_expression'
label={ label={
constituenta?.cst_type === CstType.STRUCTURED state?.cst_type === CstType.STRUCTURED
? 'Область определения' ? 'Область определения'
: !!constituenta && isFunctional(constituenta.cst_type) : !!state && isFunctional(state.cst_type)
? 'Определение функции' ? 'Определение функции'
: 'Формальное определение' : 'Формальное определение'
} }
placeholder={ placeholder={
constituenta?.cst_type !== CstType.STRUCTURED state?.cst_type !== CstType.STRUCTURED
? 'Родоструктурное выражение' ? 'Родоструктурное выражение'
: 'Определение множества, которому принадлежат элементы родовой структуры' : 'Определение множества, которому принадлежат элементы родовой структуры'
} }
value={expression} value={expression}
activeCst={constituenta} activeCst={state}
showList={showList} showList={showList}
disabled={disabled} disabled={disabled}
toggleReset={toggleReset} toggleReset={toggleReset}
@ -190,7 +191,7 @@ function FormConstituenta({
setTypification={setTypification} setTypification={setTypification}
/> />
</AnimateFade> </AnimateFade>
<AnimateFade hideContent={!!constituenta && !constituenta?.definition_raw && isElementary}> <AnimateFade hideContent={!!state && !state?.definition_raw && isElementary}>
<RefsInput <RefsInput
id='cst_definition' id='cst_definition'
label='Текстовое определение' label='Текстовое определение'
@ -198,8 +199,8 @@ function FormConstituenta({
height='3.8rem' height='3.8rem'
items={schema?.items} items={schema?.items}
value={textDefinition} value={textDefinition}
initialValue={constituenta?.definition_raw ?? ''} initialValue={state?.definition_raw ?? ''}
resolved={constituenta?.definition_resolved ?? ''} resolved={state?.definition_resolved ?? ''}
disabled={disabled} disabled={disabled}
onChange={newValue => setTextDefinition(newValue)} onChange={newValue => setTextDefinition(newValue)}
/> />
@ -210,7 +211,7 @@ function FormConstituenta({
spellCheck spellCheck
className='h-[3.8rem]' className='h-[3.8rem]'
label={isBasic ? 'Конвенция' : 'Комментарий'} label={isBasic ? 'Конвенция' : 'Комментарий'}
placeholder='Договоренность об интерпретации или пояснение' placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
value={convention} value={convention}
disabled={disabled} disabled={disabled}
rows={convention.length > 2 * ROW_SIZE_IN_CHARACTERS ? 3 : 2} rows={convention.length > 2 * ROW_SIZE_IN_CHARACTERS ? 3 : 2}

View File

@ -149,7 +149,7 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
</div> </div>
<TextArea <TextArea
id='schema_comment' id='schema_comment'
label='Комментарий' label='Описание'
rows={3} rows={3}
value={comment} value={comment}
disabled={!controller.isContentEditable || controller.isProcessing} disabled={!controller.isContentEditable || controller.isProcessing}