2023-07-25 22:29:33 +03:00
|
|
|
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
2023-07-26 23:11:00 +03:00
|
|
|
|
import { useNavigate } from 'react-router-dom';
|
2023-07-16 20:25:55 +03:00
|
|
|
|
import { toast } from 'react-toastify';
|
2023-07-25 20:27:29 +03:00
|
|
|
|
|
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';
|
|
|
|
|
import { DumpBinIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
|
|
|
|
import { useRSForm } from '../../context/RSFormContext';
|
2023-07-26 23:11:00 +03:00
|
|
|
|
import { type CstType, EditMode, type ICstCreateData, ICstUpdateData } from '../../utils/models';
|
2023-07-23 21:38:04 +03:00
|
|
|
|
import { createAliasFor, getCstTypeLabel } from '../../utils/staticUI';
|
2023-07-20 17:11:03 +03:00
|
|
|
|
import ConstituentsSideList from './ConstituentsSideList';
|
2023-07-23 21:38:04 +03:00
|
|
|
|
import CreateCstModal from './CreateCstModal';
|
2023-07-25 20:27:29 +03:00
|
|
|
|
import ExpressionEditor from './ExpressionEditor';
|
2023-07-26 23:11:00 +03:00
|
|
|
|
import { RSFormTabsList } from './RSFormTabs';
|
2023-07-15 17:46:19 +03:00
|
|
|
|
|
|
|
|
|
function ConstituentEditor() {
|
2023-07-26 23:11:00 +03:00
|
|
|
|
const navigate = useNavigate();
|
2023-07-25 20:27:29 +03:00
|
|
|
|
const {
|
2023-07-24 22:34:03 +03:00
|
|
|
|
activeCst, activeID, schema, setActiveID, processing, isEditable,
|
2023-07-23 21:38:04 +03:00
|
|
|
|
cstDelete, cstUpdate, cstCreate
|
2023-07-18 14:55:40 +03:00
|
|
|
|
} = useRSForm();
|
2023-07-16 20:25:55 +03:00
|
|
|
|
|
2023-07-23 21:38:04 +03:00
|
|
|
|
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);
|
|
|
|
|
|
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]);
|
|
|
|
|
|
2023-07-24 22:34:03 +03:00
|
|
|
|
useLayoutEffect(() => {
|
2023-07-26 23:11:00 +03:00
|
|
|
|
if (schema && schema?.items.length > 0) {
|
|
|
|
|
setActiveID((prev) => (prev ?? schema.items[0].id ?? undefined));
|
2023-07-16 20:25:55 +03:00
|
|
|
|
}
|
2023-07-25 22:29:33 +03:00
|
|
|
|
}, [schema, setActiveID]);
|
|
|
|
|
|
|
|
|
|
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-07-26 23:11:00 +03:00
|
|
|
|
setTypification(activeCst?.parse?.typification || 'N/A');
|
2023-07-16 20:25:55 +03:00
|
|
|
|
}
|
2023-07-24 22:34:03 +03:00
|
|
|
|
}, [activeCst]);
|
2023-07-25 20:27:29 +03:00
|
|
|
|
|
|
|
|
|
const 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-07-16 20:25:55 +03:00
|
|
|
|
};
|
|
|
|
|
|
2023-07-23 21:38:04 +03:00
|
|
|
|
const handleDelete = useCallback(
|
2023-07-25 20:27:29 +03:00
|
|
|
|
() => {
|
2023-07-24 22:34:03 +03:00
|
|
|
|
if (!activeID || !schema?.items || !window.confirm('Вы уверены, что хотите удалить конституенту?')) {
|
2023-07-23 21:38:04 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2023-07-25 20:27:29 +03:00
|
|
|
|
const data = {
|
|
|
|
|
items: [{ id: activeID }]
|
2023-07-23 21:38:04 +03:00
|
|
|
|
}
|
2023-07-24 22:34:03 +03:00
|
|
|
|
const index = schema.items.findIndex((cst) => cst.id === activeID);
|
|
|
|
|
if (index !== -1 && index + 1 < schema.items.length) {
|
|
|
|
|
setActiveID(schema.items[index + 1].id);
|
2023-07-23 21:38:04 +03:00
|
|
|
|
}
|
2023-07-25 00:20:37 +03:00
|
|
|
|
cstDelete(data, () => toast.success('Конституента удалена'));
|
2023-07-24 22:34:03 +03:00
|
|
|
|
}, [activeID, schema, setActiveID, cstDelete]);
|
2023-07-23 21:38:04 +03:00
|
|
|
|
|
|
|
|
|
const handleAddNew = useCallback(
|
2023-07-26 23:11:00 +03:00
|
|
|
|
(type?: CstType) => {
|
2023-07-24 22:34:03 +03:00
|
|
|
|
if (!activeID || !schema?.items) {
|
2023-07-23 21:38:04 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2023-07-26 23:11:00 +03:00
|
|
|
|
if (!type) {
|
2023-07-23 21:38:04 +03:00
|
|
|
|
setShowCstModal(true);
|
|
|
|
|
} else {
|
2023-07-26 23:11:00 +03:00
|
|
|
|
const data: ICstCreateData = {
|
|
|
|
|
cst_type: type,
|
|
|
|
|
alias: createAliasFor(type, schema),
|
2023-07-25 20:27:29 +03:00
|
|
|
|
insert_after: activeID
|
2023-07-23 21:38:04 +03:00
|
|
|
|
}
|
2023-07-26 23:11:00 +03:00
|
|
|
|
cstCreate(data, newCst => {
|
|
|
|
|
navigate(`/rsforms/${schema.id}?tab=${RSFormTabsList.CST_EDIT}&active=${newCst.id}`);
|
|
|
|
|
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
2023-07-25 20:27:29 +03:00
|
|
|
|
});
|
2023-07-23 21:38:04 +03:00
|
|
|
|
}
|
2023-07-26 23:11:00 +03:00
|
|
|
|
}, [activeID, schema, cstCreate, navigate]);
|
2023-07-23 21:38:04 +03:00
|
|
|
|
|
2023-07-16 20:25:55 +03:00
|
|
|
|
const handleRename = useCallback(() => {
|
|
|
|
|
toast.info('Переименование в разработке');
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleChangeType = useCallback(() => {
|
|
|
|
|
toast.info('Изменение типа в разработке');
|
|
|
|
|
}, []);
|
|
|
|
|
|
2023-07-15 17:46:19 +03:00
|
|
|
|
return (
|
2023-07-16 20:25:55 +03:00
|
|
|
|
<div className='flex items-start w-full gap-2'>
|
2023-07-23 21:38:04 +03:00
|
|
|
|
<CreateCstModal
|
|
|
|
|
show={showCstModal}
|
2023-07-25 22:29:33 +03:00
|
|
|
|
hideWindow={() => { setShowCstModal(false); }}
|
2023-07-23 21:38:04 +03:00
|
|
|
|
onCreate={handleAddNew}
|
2023-07-24 22:34:03 +03:00
|
|
|
|
defaultType={activeCst?.cstType as CstType}
|
2023-07-23 21:38:04 +03:00
|
|
|
|
/>
|
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'>
|
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='Сохранить изменения'
|
|
|
|
|
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}
|
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-23 21:38:04 +03:00
|
|
|
|
<button type='button'
|
|
|
|
|
title='Создать конституенты после данной'
|
|
|
|
|
className='px-1 py-1 font-bold rounded-full whitespace-nowrap disabled:cursor-not-allowed clr-btn-clear'
|
2023-07-25 22:29:33 +03:00
|
|
|
|
disabled={!isEnabled}
|
2023-07-25 20:27:29 +03:00
|
|
|
|
onClick={() => { handleAddNew(); }}
|
2023-07-23 21:38:04 +03:00
|
|
|
|
>
|
2023-07-25 22:29:33 +03:00
|
|
|
|
<SmallPlusIcon size={5} color={isEnabled ? 'text-green' : ''} />
|
2023-07-23 21:38:04 +03:00
|
|
|
|
</button>
|
|
|
|
|
<button type='button'
|
|
|
|
|
title='Удалить редактируемую конституенту'
|
|
|
|
|
className='px-1 py-1 font-bold rounded-full whitespace-nowrap disabled:cursor-not-allowed clr-btn-clear'
|
2023-07-25 22:29:33 +03:00
|
|
|
|
disabled={!isEnabled}
|
2023-07-23 21:38:04 +03:00
|
|
|
|
onClick={handleDelete}
|
|
|
|
|
>
|
2023-07-25 22:29:33 +03:00
|
|
|
|
<DumpBinIcon size={5} color={isEnabled ? 'text-red' : ''} />
|
2023-07-23 21:38:04 +03:00
|
|
|
|
</button>
|
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
|
|
|
|
/>
|
|
|
|
|
<ExpressionEditor id='expression' label='Формальное выражение'
|
|
|
|
|
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); }}
|
|
|
|
|
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-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;
|