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

251 lines
11 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';
import Divider from '../../components/Common/Divider';
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, HelpIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
2023-07-25 20:27:29 +03:00
import { useRSForm } from '../../context/RSFormContext';
2023-07-29 15:37:49 +03:00
import { type CstType, EditMode, ICstUpdateData, SyntaxTree } from '../../utils/models';
import { getCstTypeLabel, mapStatusInfo } 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
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: (selectedID: number | undefined, type: CstType | undefined) => void
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void
2023-07-29 03:31:21 +03:00
}
function EditorConstituenta({ activeID, onShowAST, onCreateCst, 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 [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 (!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');
2023-08-04 13:26:51 +03:00
} else if (schema && schema?.items.length > 0) {
onOpenEdit(schema.items[0].id);
}
}, [activeCst]);
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
};
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;
}
onCreateCst(activeID, activeCst?.cstType);
}
function handleRename() {
toast.info('Переименование в разработке');
}
function handleChangeType() {
toast.info('Изменение типа в разработке');
}
2023-07-15 17:46:19 +03:00
return (
<div className='flex items-start w-full gap-2'>
2023-08-04 13:26:51 +03:00
<form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] max-w-min px-4 py-2 mb-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}
onClick={handleCreateCst}
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 id='cst-help' className='flex items-center ml-[0.25rem]'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#cst-help'>
<div className='max-w-[35rem]'>
<h1>Подсказки</h1>
<p><b className='text-red'>Изменения сохраняются ПОСЛЕ нажатия на кнопку снизу или слева вверху</b></p>
<p><b>Клик на формальное выражение</b> - обратите внимание на кнопки снизу.<br/>Для каждой есть горячая клавиша в подсказке</p>
<p><b>Список конституент справа</b> - обратите внимание на настройки фильтрации</p>
<p>- слева от ввода текста настраивается набор атрибутов конституенты</p>
<p>- справа от ввода текста настраивается список конституент, которые фильтруются</p>
<p>- текущая конституента выделена цветом строки</p>
<p>- двойнок клин / Alt + клик - выбор редактируемой конституенты</p>
<p>- при наведении на ID конституенты отображаются ее атрибуты</p>
<p>- столбец "Описание" содержит один из непустых текстовых атрибутов</p>
<Divider margins='mt-2' />
<h1>Статусы</h1>
{ [... mapStatusInfo.values()].map(info => {
return (<p className='py-1'>
<span className={`inline-block font-semibold min-w-[4rem] text-center border ${info.color}`}>
{info.text}
</span>
<span> - </span>
<span>
{info.tooltip}
</span>
</p>);
})}
</div>
</ConceptTooltip>
</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
/>
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-07-25 20:27:29 +03:00
toggleEditMode={() => { setEditMode(EditMode.RSLANG); }}
2023-07-29 03:31:21 +03:00
onShowAST={onShowAST}
2023-07-25 20:27:29 +03:00
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>
<ViewSideConstituents
expression={expression}
activeID={activeID}
onOpenEdit={onOpenEdit}
/>
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;