ConceptPortal-public/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta.tsx
2023-09-11 21:24:06 +03:00

267 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Dispatch, SetStateAction, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify';
import ConceptTooltip from '../../components/Common/ConceptTooltip';
import MiniButton from '../../components/Common/MiniButton';
import ReferenceInput from '../../components/Common/ReferenceInput';
import SubmitButton from '../../components/Common/SubmitButton';
import TextArea from '../../components/Common/TextArea';
import HelpConstituenta from '../../components/Help/HelpConstituenta';
import { DumpBinIcon, HelpIcon, PenIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext';
import useWindowSize from '../../hooks/useWindowSize';
import { EditMode } from '../../models/miscelanious';
import { CstType, IConstituenta, ICstCreateData, ICstRenameData, ICstUpdateData } from '../../models/rsform';
import { SyntaxTree } from '../../models/rslang';
import { getCstTypificationLabel } from '../../utils/staticUI';
import EditorRSExpression from './EditorRSExpression';
import ViewSideConstituents from './elements/ViewSideConstituents';
// Max height of content for left enditor pane
const UNFOLDED_HEIGHT = '59.1rem';
const SIDELIST_HIDE_THRESHOLD = 1000;
interface EditorConstituentaProps {
activeID?: number
activeCst?: IConstituenta | undefined
onOpenEdit: (cstID: number) => void
onShowAST: (expression: string, ast: SyntaxTree) => void
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
onRenameCst: (initial: ICstRenameData) => void
onEditTerm: () => void
onDeleteCst: (selected: number[], callback?: (items: number[]) => void) => void
isModified: boolean
setIsModified: Dispatch<SetStateAction<boolean>>
}
function EditorConstituenta({
isModified, setIsModified, activeID, activeCst, onEditTerm,
onShowAST, onCreateCst, onRenameCst, onOpenEdit, onDeleteCst
}: EditorConstituentaProps) {
const windowSize = useWindowSize();
const { schema, processing, isEditable, cstUpdate, isForceAdmin } = useRSForm();
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('');
const [typification, setTypification] = useState('N/A');
const isEnabled = useMemo(() => activeCst && isEditable, [activeCst, isEditable]);
useLayoutEffect(
() => {
if (!activeCst) {
setIsModified(false);
return;
}
setIsModified(
activeCst.term_raw !== term ||
activeCst.definition_raw !== textDefinition ||
activeCst.convention !== convention ||
activeCst.definition_formal !== expression
);
return () => setIsModified(false);
}, [activeCst, activeCst?.term_raw, activeCst?.definition_formal,
activeCst?.definition_raw, activeCst?.convention,
term, textDefinition, expression, convention, setIsModified]);
useLayoutEffect(
() => {
if (activeCst) {
setAlias(activeCst.alias);
setConvention(activeCst.convention || '');
setTerm(activeCst.term_raw || '');
setTextDefinition(activeCst.definition_raw || '');
setExpression(activeCst.definition_formal || '');
setTypification(activeCst ? getCstTypificationLabel(activeCst) : 'N/A');
}
}, [activeCst, onOpenEdit, schema]);
function handleSubmit(event?: React.FormEvent<HTMLFormElement>) {
if (event) {
event.preventDefault();
}
if (!activeID || processing) {
return;
}
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() {
if (!activeID || !schema) {
return;
}
const data: ICstCreateData = {
insert_after: activeID,
cst_type: activeCst?.cst_type ?? CstType.BASE,
alias: '',
term_raw: '',
definition_formal: '',
definition_raw: '',
convention: '',
};
onCreateCst(data);
}
function handleRename() {
if (!activeID || !activeCst) {
return;
}
const data: ICstRenameData = {
id: activeID,
alias: activeCst?.alias,
cst_type: activeCst.cst_type
};
onRenameCst(data);
}
return (
<div className='flex items-stretch w-full gap-2 mb-2 justify-stretch'>
<form onSubmit={handleSubmit} className='min-w-[50rem] max-w-min px-4 py-2 border-y border-r'>
<div className='relative'>
<div className='absolute top-0 left-0'>
<MiniButton
tooltip='Сохранить изменения'
disabled={!isModified || !isEnabled}
icon={<SaveIcon size={6} color={isModified && isEnabled ? 'text-primary' : ''}/>}
onClick={() => handleSubmit()}
/>
</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-warning' : ''} />}
/>
<MiniButton
tooltip='Создать конституенты после данной'
disabled={!isEnabled}
onClick={handleCreateCst}
icon={<SmallPlusIcon size={5} color={isEnabled ? 'text-success' : ''} />}
/>
<div id='cst-help' className='flex items-center ml-[6px]'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#cst-help'>
<HelpConstituenta />
</ConceptTooltip>
</div>
</div>
<div className='flex items-center justify-center w-full gap-1 pr-10'>
<div className='font-semibold w-fit'>
<span className=''>Конституента </span>
<span className='ml-4'>{alias}</span>
</div>
<MiniButton
tooltip='Переименовать конституенту'
disabled={!isEnabled}
noHover
onClick={handleRename}
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/>
</div>
{isForceAdmin && <div className='relative'>
<div className='absolute left-[3.2rem] top-[0.5rem]'>
<MiniButton
tooltip='Редактировать словоформы термина'
disabled={!isEnabled}
noHover
onClick={onEditTerm}
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/>
</div>
</div>}
<ReferenceInput id='term' label='Термин'
placeholder='Обозначение, используемое в текстовых определениях данной схемы'
rows={2}
value={term}
initialValue={activeCst?.term_raw ?? ''}
resolved={activeCst?.term_resolved ?? ''}
disabled={!isEnabled}
spellCheck
onChange={event => setTerm(event.target.value)}
onFocus={() => setEditMode(EditMode.TEXT)}
/>
<TextArea id='typification' label='Типизация'
rows={1}
value={typification}
colorClass='clr-app'
disabled
/>
<EditorRSExpression id='expression' label='Формальное выражение'
activeCst={activeCst}
placeholder='Родоструктурное выражение, задающее формальное определение'
value={expression}
disabled={!isEnabled}
isActive={editMode === EditMode.RSLANG}
toggleEditMode={() => setEditMode(EditMode.RSLANG)}
onShowAST={onShowAST}
onChange={newValue => setExpression(newValue)}
setTypification={setTypification}
/>
<ReferenceInput id='definition' label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения'
rows={4}
value={textDefinition}
initialValue={activeCst?.definition_raw ?? ''}
resolved={activeCst?.definition_resolved ?? ''}
disabled={!isEnabled}
spellCheck
onChange={event => setTextDefinition(event.target.value)}
onFocus={() => setEditMode(EditMode.TEXT)}
/>
<TextArea id='convention' label='Конвенция / Комментарий'
placeholder='Договоренность об интерпретации неопределяемого понятия&#x000D;&#x000A;Комментарий к производному понятию'
rows={4}
value={convention}
disabled={!isEnabled}
spellCheck
onChange={event => setConvention(event.target.value)}
onFocus={() => setEditMode(EditMode.TEXT)}
/>
<div className='flex justify-center w-full mt-4 mb-2'>
<SubmitButton
text='Сохранить изменения'
disabled={!isModified || !isEnabled}
icon={<SaveIcon size={6} />}
/>
</div>
</form>
{(windowSize.width ?? 0) >= SIDELIST_HIDE_THRESHOLD &&
<div className='self-stretch w-full pb-1 border'>
<ViewSideConstituents
expression={expression}
baseHeight={UNFOLDED_HEIGHT}
activeID={activeID}
onOpenEdit={onOpenEdit}
/>
</div>}
</div>
);
}
export default EditorConstituenta;