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

264 lines
10 KiB
TypeScript
Raw Normal View History

2023-08-11 10:54:53 +03:00
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
2023-07-25 20:27:29 +03:00
2023-07-20 17:11:03 +03:00
import Button from '../../components/Common/Button';
import Label from '../../components/Common/Label';
2023-07-25 20:27:29 +03:00
import { Loader } from '../../components/Common/Loader';
2023-08-12 20:52:11 +03:00
import RSInput from '../../components/RSInput/RSInput';
import { getSymbolSubstitute, TextWrapper } from '../../components/RSInput/textEditing';
import { useRSForm } from '../../context/RSFormContext';
2023-07-20 17:11:03 +03:00
import useCheckExpression from '../../hooks/useCheckExpression';
import { TokenID } from '../../utils/enums';
import { IConstituenta, IRSErrorDescription, SyntaxTree } from '../../utils/models';
import { getCstExpressionPrefix, getTypificationLabel } from '../../utils/staticUI';
2023-07-28 00:03:37 +03:00
import ParsingResult from './elements/ParsingResult';
import RSLocalButton from './elements/RSLocalButton';
import RSTokenButton from './elements/RSTokenButton';
import StatusBar from './elements/StatusBar';
2023-07-28 00:03:37 +03:00
interface EditorRSExpressionProps {
id: string
activeCst?: IConstituenta
label: string
2023-07-20 17:11:03 +03:00
isActive: boolean
disabled?: boolean
placeholder?: string
2023-08-01 21:55:18 +03:00
onShowAST: (expression: string, ast: SyntaxTree) => void
2023-07-20 17:11:03 +03:00
toggleEditMode: () => void
setTypification: (typificaiton: string) => void
2023-08-10 18:35:49 +03:00
value: string
onChange: (newValue: string) => void
setValue: (expression: string) => void
}
2023-07-28 00:03:37 +03:00
function EditorRSExpression({
id, activeCst, label, disabled, isActive, placeholder, value, onShowAST,
2023-07-20 17:11:03 +03:00
toggleEditMode, setTypification, onChange
2023-07-28 00:03:37 +03:00
}: EditorRSExpressionProps) {
const { schema } = useRSForm();
2023-08-11 10:54:53 +03:00
2023-07-20 17:11:03 +03:00
const [isModified, setIsModified] = useState(false);
2023-07-25 20:27:29 +03:00
const { parseData, checkExpression, resetParse, loading } = useCheckExpression({ schema });
2023-08-11 10:54:53 +03:00
const rsInput = useRef<ReactCodeMirrorRef>(null);
2023-07-20 17:11:03 +03:00
useLayoutEffect(() => {
2023-07-20 17:11:03 +03:00
setIsModified(false);
resetParse();
}, [activeCst, resetParse]);
2023-07-20 17:11:03 +03:00
function handleFocusIn() {
toggleEditMode()
}
2023-08-11 10:54:53 +03:00
function handleChange(newvalue: string) {
onChange(newvalue);
setIsModified(true);
}
function handleCheckExpression() {
2023-07-25 20:27:29 +03:00
if (!activeCst) {
return;
}
const prefix = getCstExpressionPrefix(activeCst);
2023-07-20 17:11:03 +03:00
const expression = prefix + value;
checkExpression(expression, parse => {
if (parse.errors.length > 0) {
onShowError(parse.errors[0]);
} else {
rsInput.current?.view?.focus();
}
2023-07-20 17:11:03 +03:00
setIsModified(false);
setTypification(getTypificationLabel({
isValid: parse.parseResult,
resultType: parse.typification,
args: parse.args
}));
});
}
const onShowError = useCallback(
(error: IRSErrorDescription) => {
if (!activeCst || !rsInput.current) {
return;
}
const prefix = getCstExpressionPrefix(activeCst);
const errorPosition = error.position - prefix.length;
rsInput.current?.view?.dispatch({
selection: {
anchor: errorPosition,
head: errorPosition
}
});
rsInput.current?.view?.focus();
}, [activeCst]);
2023-07-20 17:11:03 +03:00
const handleEdit = useCallback((id: TokenID, key?: string) => {
if (!rsInput.current || !rsInput.current.editor || !rsInput.current.state || !rsInput.current.view) {
return;
}
const text = new TextWrapper(rsInput.current as Required<ReactCodeMirrorRef>);
if (id === TokenID.ID_LOCAL) {
2023-07-25 20:27:29 +03:00
text.insertChar(key ?? 'unknown_local');
} else {
text.insertToken(id);
}
rsInput.current?.view?.focus();
setIsModified(true);
}, []);
const handleInput = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (!rsInput.current) {
2023-07-25 20:27:29 +03:00
return;
}
const text = new TextWrapper(rsInput.current as Required<ReactCodeMirrorRef>);
2023-07-29 03:31:21 +03:00
if (event.shiftKey && event.key === '*' && !event.altKey) {
text.insertToken(TokenID.DECART);
} else if (event.altKey) {
if (!text.processAltKey(event.key)) {
return;
}
} else if (!event.ctrlKey) {
const newSymbol = getSymbolSubstitute(event.key);
2023-07-29 03:31:21 +03:00
if (!newSymbol) {
return;
}
2023-07-29 03:31:21 +03:00
text.replaceWith(newSymbol);
} else {
return;
}
2023-07-29 03:31:21 +03:00
event.preventDefault();
setIsModified(true);
}, []);
2023-07-20 17:11:03 +03:00
2023-07-25 20:27:29 +03:00
const EditButtons = useMemo(() => {
return (<div className='flex items-center justify-between w-full'>
<div className='text-sm w-fit'>
<div className='flex justify-start'>
<RSTokenButton id={TokenID.NT_DECLARATIVE_EXPR} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.NT_IMPERATIVE_EXPR} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.NT_RECURSIVE_FULL} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.BIGPR} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.SMALLPR} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.FILTER} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.REDUCE} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.CARD} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.BOOL} onInsert={handleEdit}/>
2023-07-25 20:27:29 +03:00
</div>
<div className='flex justify-start'>
<RSTokenButton id={TokenID.BOOLEAN} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.PUNC_PL} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.INTERSECTION} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.LIT_EMPTYSET} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.FORALL} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.NOT} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.IN} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.SUBSET_OR_EQ} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.AND} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.IMPLICATION} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.SET_MINUS} onInsert={handleEdit}/>
2023-07-25 20:27:29 +03:00
<RSTokenButton id={TokenID.PUNC_ITERATE} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.SUBSET} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.DEBOOL} onInsert={handleEdit}/>
</div>
<div className='flex justify-start'>
<RSTokenButton id={TokenID.DECART} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.PUNC_SL} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.UNION} onInsert={handleEdit}/>
2023-07-25 20:27:29 +03:00
<RSTokenButton id={TokenID.LIT_INTSET} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.EXISTS} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.NOTEQUAL} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.NOTIN} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.NOTSUBSET} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.OR} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.EQUIVALENT} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.SYMMINUS} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.PUNC_ASSIGN} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.EQUAL} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.GREATER_OR_EQ} onInsert={handleEdit}/>
<RSTokenButton id={TokenID.LESSER_OR_EQ} onInsert={handleEdit}/>
</div>
2023-07-20 17:11:03 +03:00
</div>
<div className='text-xs w-fit'>
<div className='flex justify-start'>
<RSLocalButton text='μ' tooltip='q' onInsert={handleEdit}/>
<RSLocalButton text='ω' tooltip='w' onInsert={handleEdit}/>
<RSLocalButton text='ε' tooltip='e' onInsert={handleEdit}/>
<RSLocalButton text='ρ' tooltip='r' onInsert={handleEdit}/>
<RSLocalButton text='τ' tooltip='t' onInsert={handleEdit}/>
<RSLocalButton text='π' tooltip='y' onInsert={handleEdit}/>
</div>
<div className='flex justify-start'>
<RSLocalButton text='α' tooltip='a' onInsert={handleEdit}/>
<RSLocalButton text='σ' tooltip='s' onInsert={handleEdit}/>
<RSLocalButton text='δ' tooltip='d' onInsert={handleEdit}/>
<RSLocalButton text='φ' tooltip='f' onInsert={handleEdit}/>
<RSLocalButton text='γ' tooltip='g' onInsert={handleEdit}/>
<RSLocalButton text='λ' tooltip='h' onInsert={handleEdit}/>
</div>
<div className='flex justify-start'>
<RSLocalButton text='ζ' tooltip='z' onInsert={handleEdit}/>
<RSLocalButton text='ξ' tooltip='x' onInsert={handleEdit}/>
<RSLocalButton text='ψ' tooltip='c' onInsert={handleEdit}/>
<RSLocalButton text='θ' tooltip='v' onInsert={handleEdit}/>
<RSLocalButton text='β' tooltip='b' onInsert={handleEdit}/>
<RSLocalButton text='η' tooltip='n' onInsert={handleEdit}/>
</div>
2023-07-20 17:11:03 +03:00
</div>
</div>);
}, [handleEdit]);
2023-07-25 20:27:29 +03:00
return (
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full'>
2023-08-08 23:04:21 +03:00
<div className='relative w-full'>
<div className='absolute top-[-0.3rem] right-0'>
<StatusBar
isModified={isModified}
constituenta={activeCst}
parseData={parseData}
/>
</div>
</div>
2023-07-25 20:27:29 +03:00
<Label
text={label}
required={false}
htmlFor={id}
/>
<RSInput innerref={rsInput}
className='mt-2'
2023-08-10 18:35:49 +03:00
value={value}
placeholder={placeholder}
editable={!disabled}
onChange={handleChange}
2023-08-10 18:35:49 +03:00
onKeyDown={handleInput}
onFocus={handleFocusIn}
/>
<div className='flex w-full gap-4 py-1 mt-1 justify-stretch'>
2023-07-20 17:11:03 +03:00
<div className='flex flex-col gap-2'>
<Button
tooltip='Проверить формальное выражение'
text='Проверить'
2023-08-08 23:04:21 +03:00
widthClass='h-full w-fit'
colorClass='clr-btn-default'
2023-07-20 17:11:03 +03:00
onClick={handleCheckExpression}
/>
</div>
{isActive && !disabled && EditButtons}
2023-07-20 17:11:03 +03:00
</div>
2023-07-29 03:31:21 +03:00
{ (loading || parseData) &&
<div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[7rem]'>
{ loading && <Loader />}
{ !loading && parseData &&
<ParsingResult
data={parseData}
2023-08-01 21:55:18 +03:00
onShowAST={ast => onShowAST(value, ast)}
onShowError={onShowError}
/>}
2023-07-29 03:31:21 +03:00
</div>}
</div>
);
}
2023-07-28 00:03:37 +03:00
export default EditorRSExpression;