2023-08-02 18:24:17 +03:00
|
|
|
|
import { useLayoutEffect, useMemo, useState } from 'react';
|
2023-07-16 20:25:55 +03:00
|
|
|
|
import { toast } from 'react-toastify';
|
2023-07-25 20:27:29 +03:00
|
|
|
|
|
2023-08-02 21:35:24 +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';
|
2023-07-16 20:25:55 +03:00
|
|
|
|
import SubmitButton from '../../components/Common/SubmitButton';
|
2023-07-25 20:27:29 +03:00
|
|
|
|
import TextArea from '../../components/Common/TextArea';
|
2023-08-02 21:35:24 +03:00
|
|
|
|
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';
|
2023-08-06 23:13:45 +03:00
|
|
|
|
import { getCstTypeLabel, getCstTypificationLabel, 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 {
|
2023-08-02 18:24:17 +03:00
|
|
|
|
activeID?: number
|
|
|
|
|
onOpenEdit: (cstID: number) => void
|
2023-08-01 21:55:18 +03:00
|
|
|
|
onShowAST: (expression: string, ast: SyntaxTree) => void
|
2023-08-02 18:24:17 +03:00
|
|
|
|
onCreateCst: (selectedID: number | undefined, type: CstType | undefined) => void
|
|
|
|
|
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void
|
2023-07-29 03:31:21 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-02 18:24:17 +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-16 20:25:55 +03:00
|
|
|
|
|
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);
|
|
|
|
|
|
2023-07-16 20:25:55 +03:00
|
|
|
|
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-16 20:25:55 +03:00
|
|
|
|
|
2023-07-25 22:29:33 +03:00
|
|
|
|
const isEnabled = useMemo(() => activeCst && isEditable, [activeCst, isEditable]);
|
|
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
|
if (!activeCst) {
|
|
|
|
|
setIsModified(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setIsModified(
|
2023-07-26 23:11:00 +03:00
|
|
|
|
activeCst.term.raw !== term ||
|
|
|
|
|
activeCst.definition.text.raw !== textDefinition ||
|
2023-07-25 22:29:33 +03:00
|
|
|
|
activeCst.convention !== convention ||
|
2023-07-26 23:11:00 +03:00
|
|
|
|
activeCst.definition.formal !== expression
|
2023-07-25 22:29:33 +03:00
|
|
|
|
);
|
2023-07-26 23:11:00 +03:00
|
|
|
|
}, [activeCst, activeCst?.term, activeCst?.definition.formal,
|
|
|
|
|
activeCst?.definition.text.raw, activeCst?.convention,
|
|
|
|
|
term, textDefinition, expression, convention]);
|
2023-07-16 20:25:55 +03:00
|
|
|
|
|
2023-07-24 22:34:03 +03:00
|
|
|
|
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 ?? '');
|
2023-08-06 23:13:45 +03:00
|
|
|
|
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-07-16 20:25:55 +03:00
|
|
|
|
}
|
2023-08-05 01:14:41 +03:00
|
|
|
|
}, [activeCst, onOpenEdit, schema]);
|
2023-07-25 20:27:29 +03:00
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
2023-07-16 20:25:55 +03:00
|
|
|
|
event.preventDefault();
|
2023-07-26 23:11:00 +03:00
|
|
|
|
if (!activeID || processing) {
|
|
|
|
|
return;
|
2023-07-18 14:55:40 +03:00
|
|
|
|
}
|
2023-07-26 23:11:00 +03:00
|
|
|
|
const data: ICstUpdateData = {
|
|
|
|
|
id: activeID,
|
|
|
|
|
alias: alias,
|
|
|
|
|
convention: convention,
|
|
|
|
|
definition_formal: expression,
|
|
|
|
|
definition_raw: textDefinition,
|
|
|
|
|
term_raw: term
|
|
|
|
|
};
|
|
|
|
|
cstUpdate(data, () => { toast.success('Изменения сохранены'); });
|
2023-08-02 18:24:17 +03:00
|
|
|
|
}
|
2023-07-16 20:25:55 +03:00
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
function handleDelete() {
|
|
|
|
|
if (!schema || !activeID) {
|
2023-07-23 21:38:04 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
onDeleteCst([activeID]);
|
|
|
|
|
}
|
2023-07-23 21:38:04 +03:00
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
function handleCreateCst() {
|
2023-07-29 15:37:49 +03:00
|
|
|
|
if (!activeID || !schema) {
|
2023-07-23 21:38:04 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-02 18:24:17 +03:00
|
|
|
|
onCreateCst(activeID, activeCst?.cstType);
|
|
|
|
|
}
|
2023-07-23 21:38:04 +03:00
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
function handleRename() {
|
2023-07-16 20:25:55 +03:00
|
|
|
|
toast.info('Переименование в разработке');
|
2023-08-02 18:24:17 +03:00
|
|
|
|
}
|
2023-07-16 20:25:55 +03:00
|
|
|
|
|
2023-08-02 18:24:17 +03:00
|
|
|
|
function handleChangeType() {
|
2023-07-16 20:25:55 +03:00
|
|
|
|
toast.info('Изменение типа в разработке');
|
2023-08-02 18:24:17 +03:00
|
|
|
|
}
|
2023-07-16 20:25:55 +03:00
|
|
|
|
|
2023-07-15 17:46:19 +03:00
|
|
|
|
return (
|
2023-08-09 10:33:42 +03:00
|
|
|
|
<div className='flex items-stretch w-full gap-2 mb-2'>
|
|
|
|
|
<form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] max-w-min max-h-fit px-4 py-2 border'>
|
2023-07-22 03:18:48 +03:00
|
|
|
|
<div className='flex items-start justify-between'>
|
2023-07-23 21:38:04 +03:00
|
|
|
|
<button type='submit'
|
|
|
|
|
title='Сохранить изменения'
|
2023-08-08 23:04:21 +03:00
|
|
|
|
className='px-1 py-1 font-bold border rounded whitespace-nowrap disabled:cursor-not-allowed clr-btn-primary'
|
2023-07-25 22:29:33 +03:00
|
|
|
|
disabled={!isModified || !isEnabled}
|
2023-07-23 21:38:04 +03:00
|
|
|
|
>
|
|
|
|
|
<SaveIcon size={5} />
|
|
|
|
|
</button>
|
2023-07-22 03:18:48 +03:00
|
|
|
|
<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='Переименовать конституенту'
|
2023-07-22 03:18:48 +03:00
|
|
|
|
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
|
2023-07-22 03:18:48 +03:00
|
|
|
|
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-08-02 18:24:17 +03:00
|
|
|
|
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}
|
2023-07-23 21:38:04 +03:00
|
|
|
|
onClick={handleDelete}
|
2023-07-27 22:04:25 +03:00
|
|
|
|
icon={<DumpBinIcon size={5} color={isEnabled ? 'text-red' : ''} />}
|
|
|
|
|
/>
|
2023-08-02 21:35:24 +03:00
|
|
|
|
<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>
|
2023-08-08 23:04:21 +03:00
|
|
|
|
<p>- двойной клик / Alt + клик - выбор редактируемой конституенты</p>
|
2023-08-02 21:35:24 +03:00
|
|
|
|
<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>
|
2023-07-22 03:18:48 +03:00
|
|
|
|
</div>
|
2023-07-16 20:25:55 +03:00
|
|
|
|
</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-16 20:25:55 +03:00
|
|
|
|
/>
|
2023-07-28 00:03:37 +03:00
|
|
|
|
<EditorRSExpression id='expression' label='Формальное выражение'
|
2023-08-02 21:35:24 +03:00
|
|
|
|
activeCst={activeCst}
|
2023-07-16 20:25:55 +03:00
|
|
|
|
placeholder='Родоструктурное выражение, задающее формальное определение'
|
|
|
|
|
value={expression}
|
2023-07-25 22:29:33 +03:00
|
|
|
|
disabled={!isEnabled}
|
2023-07-26 23:11:00 +03:00
|
|
|
|
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); }}
|
2023-07-21 18:44:14 +03:00
|
|
|
|
setValue={setExpression}
|
2023-07-20 17:11:03 +03:00
|
|
|
|
setTypification={setTypification}
|
2023-07-16 20:25:55 +03:00
|
|
|
|
/>
|
|
|
|
|
<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); }}
|
2023-07-16 20:25:55 +03:00
|
|
|
|
/>
|
|
|
|
|
<TextArea id='convention' label='Конвенция / Комментарий'
|
2023-07-22 03:18:48 +03:00
|
|
|
|
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
2023-07-16 20:25:55 +03:00
|
|
|
|
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); }}
|
2023-07-16 20:25:55 +03:00
|
|
|
|
/>
|
2023-07-22 03:18:48 +03:00
|
|
|
|
<div className='flex justify-center w-full mt-2'>
|
|
|
|
|
<SubmitButton
|
|
|
|
|
text='Сохранить изменения'
|
2023-07-25 22:29:33 +03:00
|
|
|
|
disabled={!isModified || !isEnabled}
|
2023-07-22 03:18:48 +03:00
|
|
|
|
icon={<SaveIcon size={6} />}
|
|
|
|
|
/>
|
2023-07-21 18:44:14 +03:00
|
|
|
|
</div>
|
2023-07-16 20:25:55 +03:00
|
|
|
|
</form>
|
2023-08-02 18:24:17 +03:00
|
|
|
|
<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;
|