ConceptPortal-public/rsconcept/frontend/src/pages/RSFormPage/ConstituentEditor.tsx

241 lines
9.2 KiB
TypeScript
Raw Normal View History

2023-07-25 22:29:33 +03:00
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
2023-07-25 20:27:29 +03:00
2023-07-27 22:04:25 +03:00
import MiniButton from '../../components/Common/MiniButton';
import SubmitButton from '../../components/Common/SubmitButton';
2023-07-25 20:27:29 +03:00
import TextArea from '../../components/Common/TextArea';
import { DumpBinIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext';
import { type CstType, EditMode, type ICstCreateData, ICstUpdateData } from '../../utils/models';
import { createAliasFor, getCstTypeLabel } from '../../utils/staticUI';
2023-07-20 17:11:03 +03:00
import ConstituentsSideList from './ConstituentsSideList';
import CreateCstModal from './CreateCstModal';
2023-07-25 20:27:29 +03:00
import ExpressionEditor from './ExpressionEditor';
2023-07-27 22:04:25 +03:00
import { RSTabsList } from './RSTabs';
2023-07-15 17:46:19 +03:00
function ConstituentEditor() {
const navigate = useNavigate();
2023-07-25 20:27:29 +03:00
const {
activeCst, activeID, schema, setActiveID, processing, isEditable,
cstDelete, cstUpdate, cstCreate
2023-07-18 14:55:40 +03:00
} = useRSForm();
const [showCstModal, setShowCstModal] = useState(false);
2023-07-25 22:29:33 +03:00
const [isModified, setIsModified] = useState(false);
2023-07-20 17:11:03 +03:00
const [editMode, setEditMode] = useState(EditMode.TEXT);
const [alias, setAlias] = useState('');
const [type, setType] = useState('');
const [term, setTerm] = useState('');
const [textDefinition, setTextDefinition] = useState('');
const [expression, setExpression] = useState('');
const [convention, setConvention] = useState('');
2023-07-20 17:11:03 +03:00
const [typification, setTypification] = useState('N/A');
2023-07-25 22:29:33 +03:00
const isEnabled = useMemo(() => activeCst && isEditable, [activeCst, isEditable]);
useLayoutEffect(() => {
if (schema && schema?.items.length > 0) {
setActiveID((prev) => (prev ?? schema.items[0].id ?? undefined));
}
2023-07-25 22:29:33 +03:00
}, [schema, setActiveID]);
useLayoutEffect(() => {
if (!activeCst) {
setIsModified(false);
return;
}
setIsModified(
activeCst.term.raw !== term ||
activeCst.definition.text.raw !== textDefinition ||
2023-07-25 22:29:33 +03:00
activeCst.convention !== convention ||
activeCst.definition.formal !== expression
2023-07-25 22:29:33 +03:00
);
}, [activeCst, activeCst?.term, activeCst?.definition.formal,
activeCst?.definition.text.raw, activeCst?.convention,
term, textDefinition, expression, convention]);
useLayoutEffect(() => {
if (activeCst) {
setAlias(activeCst.alias);
setType(getCstTypeLabel(activeCst.cstType));
2023-07-25 20:27:29 +03:00
setConvention(activeCst.convention ?? '');
setTerm(activeCst.term?.raw ?? '');
setTextDefinition(activeCst.definition?.text?.raw ?? '');
setExpression(activeCst.definition?.formal ?? '');
setTypification(activeCst?.parse?.typification || 'N/A');
}
}, [activeCst]);
2023-07-25 20:27:29 +03:00
const handleSubmit =
(event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!activeID || processing) {
return;
2023-07-18 14:55:40 +03:00
}
const data: ICstUpdateData = {
id: activeID,
alias: alias,
convention: convention,
definition_formal: expression,
definition_raw: textDefinition,
term_raw: term
};
cstUpdate(data, () => { toast.success('Изменения сохранены'); });
};
const handleDelete = useCallback(
2023-07-25 20:27:29 +03:00
() => {
if (!activeID || !schema?.items || !window.confirm('Вы уверены, что хотите удалить конституенту?')) {
return;
}
2023-07-25 20:27:29 +03:00
const data = {
items: [{ id: activeID }]
}
const index = schema.items.findIndex((cst) => cst.id === activeID);
if (index !== -1 && index + 1 < schema.items.length) {
setActiveID(schema.items[index + 1].id);
}
cstDelete(data, () => toast.success('Конституента удалена'));
}, [activeID, schema, setActiveID, cstDelete]);
const handleAddNew = useCallback(
(type?: CstType) => {
if (!activeID || !schema?.items) {
return;
}
if (!type) {
setShowCstModal(true);
} else {
const data: ICstCreateData = {
cst_type: type,
alias: createAliasFor(type, schema),
2023-07-25 20:27:29 +03:00
insert_after: activeID
}
cstCreate(data, newCst => {
2023-07-27 22:04:25 +03:00
navigate(`/rsforms/${schema.id}?tab=${RSTabsList.CST_EDIT}&active=${newCst.id}`);
toast.success(`Конституента добавлена: ${newCst.alias}`);
2023-07-25 20:27:29 +03:00
});
}
}, [activeID, schema, cstCreate, navigate]);
const handleRename = useCallback(() => {
toast.info('Переименование в разработке');
}, []);
const handleChangeType = useCallback(() => {
toast.info('Изменение типа в разработке');
}, []);
2023-07-15 17:46:19 +03:00
return (
<div className='flex items-start w-full gap-2'>
<CreateCstModal
show={showCstModal}
2023-07-25 22:29:33 +03:00
hideWindow={() => { setShowCstModal(false); }}
onCreate={handleAddNew}
defaultType={activeCst?.cstType as CstType}
/>
2023-07-20 17:11:03 +03:00
<form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] max-w-min px-4 py-2 border'>
<div className='flex items-start justify-between'>
<button type='submit'
title='Сохранить изменения'
className='px-1 py-1 font-bold rounded whitespace-nowrap disabled:cursor-not-allowed clr-btn-primary'
2023-07-25 22:29:33 +03:00
disabled={!isModified || !isEnabled}
>
<SaveIcon size={5} />
</button>
<div className='flex items-start justify-center w-full gap-4'>
<span className='mr-12'>
2023-07-25 20:27:29 +03:00
<label
title='Переименовать конституенту'
className='font-semibold underline cursor-pointer'
onClick={handleRename}
>
ID
</label>
<b className='ml-2'>{alias}</b>
</span>
<span>
2023-07-25 20:27:29 +03:00
<label
title='Изменить тип конституенты'
className='font-semibold underline cursor-pointer'
onClick={handleChangeType}
>
Тип
</label>
<span className='ml-2'>{type}</span>
</span>
</div>
<div className='flex justify-end'>
2023-07-27 22:04:25 +03:00
<MiniButton
tooltip='Создать конституенты после данной'
2023-07-25 22:29:33 +03:00
disabled={!isEnabled}
2023-07-25 20:27:29 +03:00
onClick={() => { handleAddNew(); }}
2023-07-27 22:04:25 +03:00
icon={<SmallPlusIcon size={5} color={isEnabled ? 'text-green' : ''} />}
/>
<MiniButton
tooltip='Удалить редактируемую конституенту'
2023-07-25 22:29:33 +03:00
disabled={!isEnabled}
onClick={handleDelete}
2023-07-27 22:04:25 +03:00
icon={<DumpBinIcon size={5} color={isEnabled ? 'text-red' : ''} />}
/>
</div>
</div>
<TextArea id='term' label='Термин'
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
rows={2}
value={term}
2023-07-25 22:29:33 +03:00
disabled={!isEnabled}
2023-07-20 17:11:03 +03:00
spellCheck
2023-07-25 20:27:29 +03:00
onChange={event => { setTerm(event.target.value); }}
onFocus={() => { setEditMode(EditMode.TEXT); }}
2023-07-20 17:11:03 +03:00
/>
<TextArea id='typification' label='Типизация'
rows={1}
value={typification}
disabled
/>
<ExpressionEditor id='expression' label='Формальное выражение'
placeholder='Родоструктурное выражение, задающее формальное определение'
value={expression}
2023-07-25 22:29:33 +03:00
disabled={!isEnabled}
isActive={editMode === EditMode.RSLANG}
2023-07-25 20:27:29 +03:00
toggleEditMode={() => { setEditMode(EditMode.RSLANG); }}
onChange={event => { setExpression(event.target.value); }}
setValue={setExpression}
2023-07-20 17:11:03 +03:00
setTypification={setTypification}
/>
<TextArea id='definition' label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения'
rows={4}
value={textDefinition}
2023-07-25 22:29:33 +03:00
disabled={!isEnabled}
2023-07-20 17:11:03 +03:00
spellCheck
2023-07-25 20:27:29 +03:00
onChange={event => { setTextDefinition(event.target.value); }}
onFocus={() => { setEditMode(EditMode.TEXT); }}
/>
<TextArea id='convention' label='Конвенция / Комментарий'
placeholder='Договоренность об интерпретации неопределяемого понятия&#x000D;&#x000A;Комментарий к производному понятию'
rows={4}
value={convention}
2023-07-25 22:29:33 +03:00
disabled={!isEnabled}
2023-07-20 17:11:03 +03:00
spellCheck
2023-07-25 20:27:29 +03:00
onChange={event => { setConvention(event.target.value); }}
onFocus={() => { setEditMode(EditMode.TEXT); }}
/>
<div className='flex justify-center w-full mt-2'>
<SubmitButton
text='Сохранить изменения'
2023-07-25 22:29:33 +03:00
disabled={!isModified || !isEnabled}
icon={<SaveIcon size={6} />}
/>
</div>
</form>
2023-07-20 17:11:03 +03:00
<ConstituentsSideList expression={expression}/>
</div>
2023-07-15 17:46:19 +03:00
);
}
2023-07-25 20:27:29 +03:00
export default ConstituentEditor;