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

246 lines
9.2 KiB
TypeScript
Raw Normal View History

import { useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify';
2023-07-25 20:27:29 +03:00
import ConceptTooltip from '../../components/Common/ConceptTooltip';
2023-07-27 22:04:25 +03:00
import MiniButton from '../../components/Common/MiniButton';
2023-08-23 22:57:25 +03:00
import ReferenceInput from '../../components/Common/ReferenceInput';
import SubmitButton from '../../components/Common/SubmitButton';
2023-07-25 20:27:29 +03:00
import TextArea from '../../components/Common/TextArea';
2023-08-23 18:11:42 +03:00
import HelpConstituenta from '../../components/Help/HelpConstituenta';
2023-08-22 23:45:59 +03:00
import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
2023-07-25 20:27:29 +03:00
import { useRSForm } from '../../context/RSFormContext';
2023-08-23 01:36:17 +03:00
import { CstType, EditMode, ICstCreateData, ICstRenameData, ICstUpdateData, SyntaxTree } from '../../utils/models';
2023-08-22 23:45:59 +03:00
import { getCstTypificationLabel } from '../../utils/staticUI';
2023-07-28 00:03:37 +03:00
import EditorRSExpression from './EditorRSExpression';
import ViewSideConstituents from './elements/ViewSideConstituents';
2023-07-15 17:46:19 +03:00
// Max height of content for left enditor pane
const UNFOLDED_HEIGHT = '59.1rem';
2023-07-29 03:31:21 +03:00
interface EditorConstituentaProps {
activeID?: number
onOpenEdit: (cstID: number) => void
2023-08-01 21:55:18 +03:00
onShowAST: (expression: string, ast: SyntaxTree) => void
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
2023-08-23 01:36:17 +03:00
onRenameCst: (initial: ICstRenameData) => void
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void
2023-07-29 03:31:21 +03:00
}
2023-08-23 01:36:17 +03:00
function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onOpenEdit, onDeleteCst }: EditorConstituentaProps) {
const { schema, processing, isEditable, cstUpdate } = useRSForm();
const activeCst = useMemo(
() => {
return schema?.items?.find((cst) => cst.id === activeID);
}, [schema?.items, activeID]);
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 [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]);
2023-08-27 16:35:17 +03:00
useLayoutEffect(
() => {
2023-07-25 22:29:33 +03:00
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]);
2023-08-27 16:35:17 +03:00
useLayoutEffect(
() => {
if (activeCst) {
setAlias(activeCst.alias);
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 ? getCstTypificationLabel(activeCst) : 'N/A');
2023-08-04 13:26:51 +03:00
} else if (schema && schema?.items.length > 0) {
onOpenEdit(schema.items[0].id);
}
2023-08-05 01:14:41 +03:00
}, [activeCst, onOpenEdit, schema]);
2023-07-25 20:27:29 +03:00
function 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
};
2023-08-27 16:35:17 +03:00
cstUpdate(data, () => toast.success('Изменения сохранены'));
}
function handleDelete() {
if (!schema || !activeID) {
return;
}
onDeleteCst([activeID]);
}
function handleCreateCst() {
2023-07-29 15:37:49 +03:00
if (!activeID || !schema) {
return;
}
const data: ICstCreateData = {
insert_after: activeID,
cst_type: activeCst?.cstType ?? CstType.BASE,
alias: '',
term_raw: '',
definition_formal: '',
definition_raw: '',
convention: '',
};
onCreateCst(data);
}
function handleRename() {
2023-08-23 01:36:17 +03:00
if (!activeID || !activeCst) {
return;
}
const data: ICstRenameData = {
id: activeID,
alias: activeCst?.alias,
cst_type: activeCst.cstType
};
onRenameCst(data);
}
2023-07-15 17:46:19 +03:00
return (
<div className='flex items-stretch w-full gap-2 mb-2 justify-stretch'>
2023-08-27 15:39:49 +03:00
<form onSubmit={handleSubmit} className='min-w-[50rem] max-w-min px-4 py-2 border-y border-r clr-border'>
2023-08-22 23:45:59 +03:00
<div className='relative'>
<div className='absolute top-0 left-0'>
<MiniButton
tooltip='Сохранить изменения'
disabled={!isModified || !isEnabled}
icon={<SaveIcon size={6} color={isModified && isEnabled ? 'text-primary' : ''}/>}
>
</MiniButton>
</div>
</div>
<div className='relative'>
<div className='absolute top-0 right-0 flex justify-end'>
<MiniButton
tooltip='Удалить редактируемую конституенту'
disabled={!isEnabled}
onClick={handleDelete}
icon={<DumpBinIcon size={5} color={isEnabled ? 'text-red' : ''} />}
/>
<MiniButton
tooltip='Создать конституенты после данной'
disabled={!isEnabled}
onClick={handleCreateCst}
icon={<SmallPlusIcon size={5} color={isEnabled ? 'text-green' : ''} />}
/>
<div id='cst-help' className='flex items-center ml-[6px]'>
<HelpIcon color='text-primary' size={5} />
</div>
2023-08-22 23:45:59 +03:00
<ConceptTooltip anchorSelect='#cst-help'>
2023-08-23 18:11:42 +03:00
<HelpConstituenta />
2023-08-22 23:45:59 +03:00
</ConceptTooltip>
</div>
</div>
<div className='flex items-center justify-center w-full pr-10'>
<div className='font-semibold w-fit'>
<span className=''>Конституента </span>
<span className='ml-4'>{alias}</span>
</div>
2023-08-22 23:45:59 +03:00
<MiniButton
tooltip='Переименовать конституету'
disabled={!isEnabled}
noHover
onClick={handleRename}
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/>
</div>
2023-08-23 22:57:25 +03:00
<ReferenceInput id='term' label='Термин'
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
rows={2}
value={term}
2023-08-23 22:57:25 +03:00
initialValue={activeCst?.term.raw ?? ''}
resolved={activeCst?.term.resolved ?? ''}
2023-07-25 22:29:33 +03:00
disabled={!isEnabled}
2023-07-20 17:11:03 +03:00
spellCheck
2023-08-10 18:35:49 +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
/>
2023-07-28 00:03:37 +03:00
<EditorRSExpression id='expression' label='Формальное выражение'
activeCst={activeCst}
placeholder='Родоструктурное выражение, задающее формальное определение'
value={expression}
2023-07-25 22:29:33 +03:00
disabled={!isEnabled}
isActive={editMode === EditMode.RSLANG}
2023-08-10 18:35:49 +03:00
toggleEditMode={() => setEditMode(EditMode.RSLANG)}
2023-07-29 03:31:21 +03:00
onShowAST={onShowAST}
2023-08-10 18:35:49 +03:00
onChange={newValue => setExpression(newValue)}
setValue={setExpression}
2023-07-20 17:11:03 +03:00
setTypification={setTypification}
/>
2023-08-23 22:57:25 +03:00
<ReferenceInput id='definition' label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения'
rows={4}
value={textDefinition}
2023-08-23 22:57:25 +03:00
initialValue={activeCst?.definition.text.raw ?? ''}
resolved={activeCst?.definition.text.resolved ?? ''}
2023-07-25 22:29:33 +03:00
disabled={!isEnabled}
2023-07-20 17:11:03 +03:00
spellCheck
2023-08-27 16:35:17 +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-08-27 16:35:17 +03:00
onChange={event => setConvention(event.target.value)}
onFocus={() => setEditMode(EditMode.TEXT)}
/>
2023-08-27 15:39:49 +03:00
<div className='flex justify-center w-full mt-4 mb-2'>
<SubmitButton
text='Сохранить изменения'
2023-07-25 22:29:33 +03:00
disabled={!isModified || !isEnabled}
icon={<SaveIcon size={6} />}
/>
</div>
</form>
2023-08-15 21:22:21 +03:00
<div className='self-stretch w-full pb-1 border'>
<ViewSideConstituents
expression={expression}
baseHeight={UNFOLDED_HEIGHT}
activeID={activeID}
onOpenEdit={onOpenEdit}
/>
</div>
2023-07-20 17:11:03 +03:00
</div>
2023-07-15 17:46:19 +03:00
);
}
2023-07-28 00:03:37 +03:00
export default EditorConstituenta;