mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Improve Consistuenta editing experience
This commit is contained in:
parent
5bcadaf58b
commit
8861ec5378
|
@ -2,15 +2,17 @@ interface SubmitButtonProps {
|
||||||
text: string
|
text: string
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
icon?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function SubmitButton({text='ОК', disabled, loading=false}: SubmitButtonProps) {
|
function SubmitButton({text='ОК', icon, disabled, loading=false}: SubmitButtonProps) {
|
||||||
return (
|
return (
|
||||||
<button type='submit'
|
<button type='submit'
|
||||||
className={`px-4 py-2 font-bold disabled:cursor-not-allowed rounded clr-btn-primary ${loading ? ' cursor-progress': ''}`}
|
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-bold disabled:cursor-not-allowed rounded clr-btn-primary ${loading ? ' cursor-progress': ''}`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{text}
|
{icon && <span>{icon}</span>}
|
||||||
|
{text && <span>{text}</span>}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,3 +275,11 @@ export function ArrowsRotateIcon(props: IconProps) {
|
||||||
</IconSVG>
|
</IconSVG>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SaveIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<IconSVG viewbox='0 0 24 24' {...props}>
|
||||||
|
<path d='M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 000 14a6 6 0 006 6h13a5 5 0 005-5c0-2.64-2.05-4.78-4.65-4.96M19 18H6a4 4 0 01-4-4c0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95A5.469 5.469 0 0112 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11A2.98 2.98 0 0122 15a3 3 0 01-3 3M8 13h2.55v3h2.9v-3H16l-4-4-4 4z' />
|
||||||
|
</IconSVG>
|
||||||
|
);
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import ExpressionEditor from './ExpressionEditor';
|
||||||
import SubmitButton from '../../components/Common/SubmitButton';
|
import SubmitButton from '../../components/Common/SubmitButton';
|
||||||
import { getCstTypeLabel } from '../../utils/staticUI';
|
import { getCstTypeLabel } from '../../utils/staticUI';
|
||||||
import ConstituentsSideList from './ConstituentsSideList';
|
import ConstituentsSideList from './ConstituentsSideList';
|
||||||
|
import { SaveIcon } from '../../components/Icons';
|
||||||
|
|
||||||
function ConstituentEditor() {
|
function ConstituentEditor() {
|
||||||
const {
|
const {
|
||||||
|
@ -78,27 +79,38 @@ function ConstituentEditor() {
|
||||||
return (
|
return (
|
||||||
<div className='flex items-start w-full gap-2'>
|
<div className='flex items-start w-full gap-2'>
|
||||||
<form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] max-w-min px-4 py-2 border'>
|
<form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] max-w-min px-4 py-2 border'>
|
||||||
<div className='flex items-start justify-between gap-1'>
|
<div className='flex items-start justify-between'>
|
||||||
<span className='mr-12'>
|
<div className='flex items-start justify-center w-full gap-4'>
|
||||||
<label
|
<span className='mr-12'>
|
||||||
title='Переименовать конституенту'
|
<label
|
||||||
className='font-semibold underline cursor-pointer'
|
title='Переименовать конституенту'
|
||||||
onClick={handleRename}
|
className='font-semibold underline cursor-pointer'
|
||||||
>
|
onClick={handleRename}
|
||||||
ID
|
>
|
||||||
</label>
|
ID
|
||||||
<b className='ml-2'>{alias}</b>
|
</label>
|
||||||
</span>
|
<b className='ml-2'>{alias}</b>
|
||||||
<span>
|
</span>
|
||||||
<label
|
<span>
|
||||||
title='Изменить тип конституенты'
|
<label
|
||||||
className='font-semibold underline cursor-pointer'
|
title='Изменить тип конституенты'
|
||||||
onClick={handleChangeType}
|
className='font-semibold underline cursor-pointer'
|
||||||
>
|
onClick={handleChangeType}
|
||||||
Тип
|
>
|
||||||
</label>
|
Тип
|
||||||
<span className='ml-2'>{type}</span>
|
</label>
|
||||||
</span>
|
<span className='ml-2'>{type}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className='flex justify-end'>
|
||||||
|
<button type='submit'
|
||||||
|
title='Сохранить изменения'
|
||||||
|
className={'px-1 py-1 whitespace-nowrap font-bold disabled:cursor-not-allowed rounded clr-btn-primary'}
|
||||||
|
disabled={!isEditable}
|
||||||
|
>
|
||||||
|
<SaveIcon size={5} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TextArea id='term' label='Термин'
|
<TextArea id='term' label='Термин'
|
||||||
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
|
placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение'
|
||||||
|
@ -134,7 +146,7 @@ function ConstituentEditor() {
|
||||||
onFocus={() => setEditMode(EditMode.TEXT)}
|
onFocus={() => setEditMode(EditMode.TEXT)}
|
||||||
/>
|
/>
|
||||||
<TextArea id='convention' label='Конвенция / Комментарий'
|
<TextArea id='convention' label='Конвенция / Комментарий'
|
||||||
placeholder='Договоренность об интерпретации неопределяемых понятий или комментарий к производному понятию'
|
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
||||||
rows={4}
|
rows={4}
|
||||||
value={convention}
|
value={convention}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
|
@ -142,8 +154,12 @@ function ConstituentEditor() {
|
||||||
onChange={event => setConvention(event.target.value)}
|
onChange={event => setConvention(event.target.value)}
|
||||||
onFocus={() => setEditMode(EditMode.TEXT)}
|
onFocus={() => setEditMode(EditMode.TEXT)}
|
||||||
/>
|
/>
|
||||||
<div className='mt-2 w-full flex justify-center'>
|
<div className='flex justify-center w-full mt-2'>
|
||||||
<SubmitButton text='Сохранить изменения' disabled={!isEditable} />
|
<SubmitButton
|
||||||
|
text='Сохранить изменения'
|
||||||
|
disabled={!isEditable}
|
||||||
|
icon={<SaveIcon size={6} />}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<ConstituentsSideList expression={expression}/>
|
<ConstituentsSideList expression={expression}/>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useCallback, useState, useMemo, useEffect } from 'react';
|
import { useCallback, useState, useMemo, useEffect } from 'react';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { IConstituenta, matchConstituenta } from '../../utils/models';
|
import { CstType, IConstituenta, matchConstituenta } from '../../utils/models';
|
||||||
import Checkbox from '../../components/Common/Checkbox';
|
import Checkbox from '../../components/Common/Checkbox';
|
||||||
import DataTableThemed from '../../components/Common/DataTableThemed';
|
import DataTableThemed from '../../components/Common/DataTableThemed';
|
||||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||||
|
@ -20,8 +20,20 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
if (!schema?.items) {
|
if (!schema?.items) {
|
||||||
setFilteredData([]);
|
setFilteredData([]);
|
||||||
} else if (onlyExpression) {
|
} else if (onlyExpression) {
|
||||||
const aliases: string[] = extractGlobals(expression);
|
const aliases = extractGlobals(expression);
|
||||||
setFilteredData(schema?.items.filter((cst) => aliases.includes(cst.alias)));
|
let filtered = schema?.items.filter((cst) => aliases.has(cst.alias));
|
||||||
|
const names = filtered.map(cst => cst.alias)
|
||||||
|
const diff = Array.from(aliases).filter(name => names.indexOf(name) < 0);
|
||||||
|
if (diff.length > 0) {
|
||||||
|
diff.forEach(
|
||||||
|
(alias, i) => filtered.push({
|
||||||
|
entityUID: -i,
|
||||||
|
alias: alias,
|
||||||
|
convention: 'Конституента отсутствует',
|
||||||
|
cstType: CstType.BASE
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
setFilteredData(filtered);
|
||||||
} else if (!filterText) {
|
} else if (!filterText) {
|
||||||
setFilteredData(schema?.items);
|
setFilteredData(schema?.items);
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,14 +43,14 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
|
|
||||||
const handleRowClicked = useCallback(
|
const handleRowClicked = useCallback(
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
if (event.ctrlKey) {
|
if (event.altKey && cst.entityUID > 0) {
|
||||||
setActive(cst);
|
setActive(cst);
|
||||||
}
|
}
|
||||||
}, [setActive]);
|
}, [setActive]);
|
||||||
|
|
||||||
const handleDoubleClick = useCallback(
|
const handleDoubleClick = useCallback(
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
setActive(cst);
|
if (cst.entityUID > 0) setActive(cst);
|
||||||
}, [setActive]);
|
}, [setActive]);
|
||||||
|
|
||||||
const columns = useMemo(() =>
|
const columns = useMemo(() =>
|
||||||
|
@ -54,13 +66,25 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
selector: (cst: IConstituenta) => cst.alias,
|
selector: (cst: IConstituenta) => cst.alias,
|
||||||
width: '62px',
|
width: '62px',
|
||||||
maxWidth: '62px',
|
maxWidth: '62px',
|
||||||
|
conditionalCellStyles: [
|
||||||
|
{
|
||||||
|
when: (cst: IConstituenta) => cst.entityUID <= 0,
|
||||||
|
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Описание',
|
name: 'Описание',
|
||||||
id: 'description',
|
id: 'description',
|
||||||
selector: (cst: IConstituenta) => cst.term?.resolved || cst.definition?.text.resolved || cst.definition?.formal || '',
|
selector: (cst: IConstituenta) => cst.term?.resolved || cst.definition?.text.resolved || cst.definition?.formal || cst.convention || '',
|
||||||
minWidth: '350px',
|
minWidth: '350px',
|
||||||
wrap: true,
|
wrap: true,
|
||||||
|
conditionalCellStyles: [
|
||||||
|
{
|
||||||
|
when: (cst: IConstituenta) => cst.entityUID <= 0,
|
||||||
|
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Выражение',
|
name: 'Выражение',
|
||||||
|
@ -70,6 +94,12 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
|
||||||
hide: 1600,
|
hide: 1600,
|
||||||
grow: 2,
|
grow: 2,
|
||||||
wrap: true,
|
wrap: true,
|
||||||
|
conditionalCellStyles: [
|
||||||
|
{
|
||||||
|
when: (cst: IConstituenta) => cst.entityUID <= 0,
|
||||||
|
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
], []
|
], []
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,7 +24,7 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||||
|
|
||||||
const handleRowClicked = useCallback(
|
const handleRowClicked = useCallback(
|
||||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
if (event.ctrlKey) {
|
if (event.altKey) {
|
||||||
onOpenEdit(cst);
|
onOpenEdit(cst);
|
||||||
}
|
}
|
||||||
}, [onOpenEdit]);
|
}, [onOpenEdit]);
|
||||||
|
|
|
@ -3,14 +3,15 @@ import Button from '../../components/Common/Button';
|
||||||
import Label from '../../components/Common/Label';
|
import Label from '../../components/Common/Label';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import RSEditButton from './RSEditButton';
|
import RSTokenButton from './RSTokenButton';
|
||||||
import { CstType, TokenID } from '../../utils/models';
|
import { CstType, TokenID } from '../../utils/models';
|
||||||
import useCheckExpression from '../../hooks/useCheckExpression';
|
import useCheckExpression from '../../hooks/useCheckExpression';
|
||||||
import ParsingResult from './ParsingResult';
|
import ParsingResult from './ParsingResult';
|
||||||
import { Loader } from '../../components/Common/Loader';
|
import { Loader } from '../../components/Common/Loader';
|
||||||
import StatusBar from './StatusBar';
|
import StatusBar from './StatusBar';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
import { TextWrapper } from '../../utils/textEditing';
|
import { TextWrapper, getSymbolSubstitute } from './textEditing';
|
||||||
|
import RSLocalButton from './RSLocalButton';
|
||||||
|
|
||||||
interface ExpressionEditorProps {
|
interface ExpressionEditorProps {
|
||||||
id: string
|
id: string
|
||||||
|
@ -50,16 +51,21 @@ function ExpressionEditor({
|
||||||
});
|
});
|
||||||
}, [value, checkExpression, active, setTypification]);
|
}, [value, checkExpression, active, setTypification]);
|
||||||
|
|
||||||
const handleEdit = useCallback((id: TokenID) => {
|
const handleEdit = useCallback((id: TokenID, key?: string) => {
|
||||||
if (!expressionCtrl.current) {
|
if (!expressionCtrl.current) {
|
||||||
toast.error('Нет доступа к полю редактирования формального выражения');
|
toast.error('Нет доступа к полю редактирования формального выражения');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let text = new TextWrapper(expressionCtrl.current);
|
let text = new TextWrapper(expressionCtrl.current);
|
||||||
text.insertToken(id);
|
if (id === TokenID.ID_LOCAL) {
|
||||||
|
text.insertChar(key!);
|
||||||
|
} else {
|
||||||
|
text.insertToken(id);
|
||||||
|
}
|
||||||
text.finalize();
|
text.finalize();
|
||||||
text.focus();
|
text.focus();
|
||||||
setValue(text.value);
|
setValue(text.value);
|
||||||
|
setIsModified(true);
|
||||||
}, [setValue]);
|
}, [setValue]);
|
||||||
|
|
||||||
const handleChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
@ -67,57 +73,106 @@ function ExpressionEditor({
|
||||||
setIsModified(true);
|
setIsModified(true);
|
||||||
}, [setIsModified, onChange]);
|
}, [setIsModified, onChange]);
|
||||||
|
|
||||||
|
const handleInput = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (event.altKey) {
|
||||||
|
let text = new TextWrapper(expressionCtrl.current!);
|
||||||
|
if (text.processAltKey(event.key)) {
|
||||||
|
event.preventDefault();
|
||||||
|
text.finalize();
|
||||||
|
setValue(text.value);
|
||||||
|
setIsModified(true);
|
||||||
|
}
|
||||||
|
} else if (!event.ctrlKey) {
|
||||||
|
const newSymbol = getSymbolSubstitute(event.key);
|
||||||
|
if (newSymbol) {
|
||||||
|
event.preventDefault();
|
||||||
|
let text = new TextWrapper(expressionCtrl.current!);
|
||||||
|
text.replaceWith(newSymbol);
|
||||||
|
text.finalize();
|
||||||
|
setValue(text.value);
|
||||||
|
setIsModified(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [expressionCtrl, setValue]);
|
||||||
|
|
||||||
const handleFocusIn = useCallback(() => {
|
const handleFocusIn = useCallback(() => {
|
||||||
toggleEditMode()
|
toggleEditMode()
|
||||||
}, [toggleEditMode]);
|
}, [toggleEditMode]);
|
||||||
|
|
||||||
const EditButtons = useMemo( () => {
|
const EditButtons = useMemo( () => {
|
||||||
return (<div className='w-full text-sm'>
|
return (<div className='flex items-center justify-between w-full'>
|
||||||
<div className='flex justify-start'>
|
<div className='text-sm w-fit'>
|
||||||
<RSEditButton id={TokenID.NT_DECLARATIVE_EXPR} onInsert={handleEdit}/>
|
<div className='flex justify-start'>
|
||||||
<RSEditButton id={TokenID.NT_IMPERATIVE_EXPR} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.NT_DECLARATIVE_EXPR} onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.NT_RECURSIVE_FULL} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.NT_IMPERATIVE_EXPR} onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.BIGPR} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.NT_RECURSIVE_FULL} onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.SMALLPR} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.BIGPR} onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.FILTER} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.SMALLPR} onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.REDUCE} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.FILTER} onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.CARD} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.REDUCE} onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.BOOL} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.CARD} onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.DEBOOL} onInsert={handleEdit}/>
|
<RSTokenButton id={TokenID.BOOL} onInsert={handleEdit}/>
|
||||||
</div>
|
|
||||||
<div className='flex justify-start'>
|
|
||||||
<RSEditButton id={TokenID.BOOLEAN} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.PUNC_PL} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.FORALL} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.IN} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.OR} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.SUBSET_OR_EQ} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.INTERSECTION} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.MINUS} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.LIT_EMPTYSET} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.SUBSET} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.EQUAL} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.NOT} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.GREATER_OR_EQ} onInsert={handleEdit}/>
|
|
||||||
<RSEditButton id={TokenID.LESSER_OR_EQ} onInsert={handleEdit}/>
|
|
||||||
|
|
||||||
|
</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}/>
|
||||||
|
<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}/>
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-start'>
|
<div className='text-xs w-fit'>
|
||||||
<RSEditButton id={TokenID.DECART} onInsert={handleEdit}/>
|
<div className='flex justify-start'>
|
||||||
<RSEditButton id={TokenID.PUNC_SL} onInsert={handleEdit}/>
|
<RSLocalButton text='μ' tooltip='q' onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.EXISTS} onInsert={handleEdit}/>
|
<RSLocalButton text='ω' tooltip='w' onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.NOTIN} onInsert={handleEdit}/>
|
<RSLocalButton text='ε' tooltip='e' onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.AND} onInsert={handleEdit}/>
|
<RSLocalButton text='ρ' tooltip='r' onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.IMPLICATION} onInsert={handleEdit}/>
|
<RSLocalButton text='τ' tooltip='t' onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.UNION} onInsert={handleEdit}/>
|
<RSLocalButton text='π' tooltip='y' onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.SYMMINUS} onInsert={handleEdit}/>
|
</div>
|
||||||
<RSEditButton id={TokenID.LIT_INTSET} onInsert={handleEdit}/>
|
<div className='flex justify-start'>
|
||||||
<RSEditButton id={TokenID.NOTSUBSET} onInsert={handleEdit}/>
|
<RSLocalButton text='α' tooltip='a' onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.NOTEQUAL} onInsert={handleEdit}/>
|
<RSLocalButton text='σ' tooltip='s' onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.EQUIVALENT} onInsert={handleEdit}/>
|
<RSLocalButton text='δ' tooltip='d' onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.PUNC_ASSIGN} onInsert={handleEdit}/>
|
<RSLocalButton text='φ' tooltip='f' onInsert={handleEdit}/>
|
||||||
<RSEditButton id={TokenID.PUNC_ITERATE} 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>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
}, [handleEdit])
|
}, [handleEdit])
|
||||||
|
@ -136,9 +191,10 @@ function ExpressionEditor({
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onFocus={handleFocusIn}
|
onFocus={handleFocusIn}
|
||||||
|
onKeyDown={handleInput}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<div className='flex gap-4 py-1 mt-1 justify-stretch items-stre'>
|
<div className='flex w-full gap-4 py-1 mt-1 justify-stretch'>
|
||||||
<div className='flex flex-col gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
{isActive && <StatusBar
|
{isActive && <StatusBar
|
||||||
isModified={isModified}
|
isModified={isModified}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import TextInput from '../../components/Common/TextInput';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import Button from '../../components/Common/Button';
|
import Button from '../../components/Common/Button';
|
||||||
import { CrownIcon, DownloadIcon, DumpBinIcon, ShareIcon } from '../../components/Icons';
|
import { CrownIcon, DownloadIcon, DumpBinIcon, SaveIcon, ShareIcon } from '../../components/Icons';
|
||||||
import { useUsers } from '../../context/UsersContext';
|
import { useUsers } from '../../context/UsersContext';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
@ -86,7 +86,12 @@ function RSFormCard() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex items-center justify-between gap-1 py-2 mt-2'>
|
<div className='flex items-center justify-between gap-1 py-2 mt-2'>
|
||||||
<SubmitButton text='Сохранить изменения' loading={processing} disabled={!isEditable || processing} />
|
<SubmitButton
|
||||||
|
text='Сохранить изменения'
|
||||||
|
loading={processing}
|
||||||
|
disabled={!isEditable || processing}
|
||||||
|
icon={<SaveIcon size={6} />}
|
||||||
|
/>
|
||||||
<div className='flex justify-end gap-1'>
|
<div className='flex justify-end gap-1'>
|
||||||
<Button
|
<Button
|
||||||
tooltip='Поделиться схемой'
|
tooltip='Поделиться схемой'
|
||||||
|
|
25
rsconcept/frontend/src/pages/RSFormPage/RSLocalButton.tsx
Normal file
25
rsconcept/frontend/src/pages/RSFormPage/RSLocalButton.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { TokenID } from '../../utils/models'
|
||||||
|
|
||||||
|
interface RSLocalButtonProps {
|
||||||
|
text: string
|
||||||
|
tooltip: string
|
||||||
|
disabled?: boolean
|
||||||
|
onInsert: (token: TokenID, key?: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function RSLocalButton({text, tooltip, disabled, onInsert}: RSLocalButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={() => onInsert(TokenID.ID_LOCAL, text)}
|
||||||
|
title={tooltip}
|
||||||
|
tabIndex={-1}
|
||||||
|
className='w-[1.5rem] h-7 cursor-pointer border rounded-none clr-btn-clear'
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RSLocalButton;
|
|
@ -1,13 +1,13 @@
|
||||||
import { TokenID } from '../../utils/models'
|
import { TokenID } from '../../utils/models'
|
||||||
import { getRSButtonData } from '../../utils/staticUI'
|
import { getRSButtonData } from '../../utils/staticUI'
|
||||||
|
|
||||||
interface RSEditButtonProps {
|
interface RSTokenButtonProps {
|
||||||
id: TokenID
|
id: TokenID
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onInsert: (token: TokenID) => void
|
onInsert: (token: TokenID, key?: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSEditButton({id, disabled, onInsert}: RSEditButtonProps) {
|
function RSTokenButton({id, disabled, onInsert}: RSTokenButtonProps) {
|
||||||
const data = getRSButtonData(id);
|
const data = getRSButtonData(id);
|
||||||
const width = data.text.length > 3 ? 'w-[4rem]' : 'w-[2rem]';
|
const width = data.text.length > 3 ? 'w-[4rem]' : 'w-[2rem]';
|
||||||
return (
|
return (
|
||||||
|
@ -21,7 +21,7 @@ function RSEditButton({id, disabled, onInsert}: RSEditButtonProps) {
|
||||||
>
|
>
|
||||||
{data.text && <span className='whitespace-nowrap'>{data.text}</span>}
|
{data.text && <span className='whitespace-nowrap'>{data.text}</span>}
|
||||||
</button>
|
</button>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RSEditButton;
|
export default RSTokenButton;
|
|
@ -102,7 +102,7 @@ function TablistTools() {
|
||||||
</div>
|
</div>
|
||||||
<div ref={editMenu.ref}>
|
<div ref={editMenu.ref}>
|
||||||
<Button
|
<Button
|
||||||
tooltip={'измнение ' + (isEditable ? 'доступно': 'запрещено')}
|
tooltip={'измнение: ' + (isEditable ? '[доступно]': '[запрещено]')}
|
||||||
borderClass=''
|
borderClass=''
|
||||||
icon={<PenIcon size={5} color={isEditable ? 'text-green': 'text-red'}/>}
|
icon={<PenIcon size={5} color={isEditable ? 'text-green': 'text-red'}/>}
|
||||||
dense
|
dense
|
||||||
|
@ -130,7 +130,7 @@ function TablistTools() {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
tooltip={'отслеживание: ' + (isTracking ? 'включено': 'выключено')}
|
tooltip={'отслеживание: ' + (isTracking ? '[включено]': '[выключено]')}
|
||||||
icon={isTracking ?
|
icon={isTracking ?
|
||||||
<EyeIcon color='text-primary' size={5}/>
|
<EyeIcon color='text-primary' size={5}/>
|
||||||
: <EyeOffIcon size={5}/>
|
: <EyeOffIcon size={5}/>
|
||||||
|
|
212
rsconcept/frontend/src/pages/RSFormPage/textEditing.ts
Normal file
212
rsconcept/frontend/src/pages/RSFormPage/textEditing.ts
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
// Formatted text editing helpers
|
||||||
|
|
||||||
|
import { TokenID } from '../../utils/models'
|
||||||
|
|
||||||
|
export function getSymbolSubstitute(input: string): string | undefined {
|
||||||
|
switch(input) {
|
||||||
|
case '`': return '∀';
|
||||||
|
case '~': return '∃';
|
||||||
|
|
||||||
|
// qwerty = μωερτπ
|
||||||
|
// asdfgh = ασδφγλ
|
||||||
|
// zxcvbn = ζξψθβη
|
||||||
|
case 'q': return 'μ';
|
||||||
|
case 'w': return 'ω';
|
||||||
|
case 'e': return 'ε';
|
||||||
|
case 'r': return 'ρ';
|
||||||
|
case 't': return 'τ';
|
||||||
|
case 'y': return 'π';
|
||||||
|
|
||||||
|
case 'a': return 'α';
|
||||||
|
case 's': return 'σ';
|
||||||
|
case 'd': return 'δ';
|
||||||
|
case 'f': return 'φ';
|
||||||
|
case 'g': return 'γ';
|
||||||
|
case 'h': return 'λ';
|
||||||
|
|
||||||
|
case 'z': return 'ζ';
|
||||||
|
case 'x': return 'ξ';
|
||||||
|
case 'c': return 'ψ';
|
||||||
|
case 'v': return 'θ';
|
||||||
|
case 'b': return 'β';
|
||||||
|
case 'n': return 'η';
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IManagedText {
|
||||||
|
value: string
|
||||||
|
selStart: number
|
||||||
|
selEnd: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Wrapper class for textareafield.
|
||||||
|
// WARNING! Manipulations on value do not support UNDO browser
|
||||||
|
// WARNING! No checks for selection out of text boundaries
|
||||||
|
export class TextWrapper implements IManagedText {
|
||||||
|
value: string
|
||||||
|
selStart: number
|
||||||
|
selEnd: number
|
||||||
|
object: HTMLTextAreaElement
|
||||||
|
|
||||||
|
constructor(element: HTMLTextAreaElement) {
|
||||||
|
this.object = element;
|
||||||
|
this.value = this.object.value;
|
||||||
|
this.selStart = this.object.selectionStart;
|
||||||
|
this.selEnd = this.object.selectionEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.object.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.value = this.object.value;
|
||||||
|
this.selStart = this.object.selectionStart;
|
||||||
|
this.selEnd = this.object.selectionEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {
|
||||||
|
this.object.value = this.value;
|
||||||
|
this.object.selectionStart = this.selStart;
|
||||||
|
this.object.selectionEnd = this.selEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceWith(data: string) {
|
||||||
|
this.value = this.value.substring(0, this.selStart) + data + this.value.substring(this.selEnd, this.value.length);
|
||||||
|
this.selEnd += data.length - this.selEnd + this.selStart;
|
||||||
|
this.selStart = this.selEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
envelopeWith(left: string, right: string) {
|
||||||
|
this.value = this.value.substring(0, this.selStart) + left +
|
||||||
|
this.value.substring(this.selStart, this.selEnd) + right +
|
||||||
|
this.value.substring(this.selEnd, this.value.length);
|
||||||
|
this.selEnd += left.length + right.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveSel(shift: number) {
|
||||||
|
this.selStart += shift;
|
||||||
|
this.selEnd += shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSel(start: number, end: number) {
|
||||||
|
this.selStart = start;
|
||||||
|
this.selEnd = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
insertChar(key: string) {
|
||||||
|
this.replaceWith(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
insertToken(tokenID: TokenID): boolean {
|
||||||
|
switch(tokenID) {
|
||||||
|
case TokenID.NT_DECLARATIVE_EXPR: this.envelopeWith('D{ξ∈X1 | ', '}'); return true;
|
||||||
|
case TokenID.NT_IMPERATIVE_EXPR: this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; ', '}'); return true;
|
||||||
|
case TokenID.NT_RECURSIVE_FULL: this.envelopeWith('R{ ξ:=D1 | 1=1 | ', '}'); return true;
|
||||||
|
case TokenID.BIGPR: this.envelopeWith('Pr1(', ')'); return true;
|
||||||
|
case TokenID.SMALLPR: this.envelopeWith('pr1(', ')'); return true;
|
||||||
|
case TokenID.FILTER: this.envelopeWith('Fi1[α](', ')'); return true;
|
||||||
|
case TokenID.REDUCE: this.envelopeWith('red(', ')'); return true;
|
||||||
|
case TokenID.CARD: this.envelopeWith('card(', ')'); return true;
|
||||||
|
case TokenID.BOOL: this.envelopeWith('bool(', ')'); return true;
|
||||||
|
case TokenID.DEBOOL: this.envelopeWith('debool(', ')'); return true;
|
||||||
|
|
||||||
|
case TokenID.PUNC_PL: {
|
||||||
|
this.envelopeWith('(', ')');
|
||||||
|
this.selEnd = this.selStart + 1;
|
||||||
|
this.selStart = this.selEnd;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case TokenID.PUNC_SL: {
|
||||||
|
this.envelopeWith('[', ']');
|
||||||
|
this.selEnd = this.selStart + 1;
|
||||||
|
this.selStart = this.selEnd;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case TokenID.BOOLEAN: {
|
||||||
|
if (this.selEnd !== this.selStart && this.value.at(this.selStart) === 'ℬ') {
|
||||||
|
this.envelopeWith('ℬ', '');
|
||||||
|
} else {
|
||||||
|
this.envelopeWith('ℬ(', ')');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TokenID.DECART: this.replaceWith('×'); return true;
|
||||||
|
case TokenID.FORALL: this.replaceWith('∀'); return true;
|
||||||
|
case TokenID.EXISTS: this.replaceWith('∃'); return true;
|
||||||
|
case TokenID.IN: this.replaceWith('∈'); return true;
|
||||||
|
case TokenID.NOTIN: this.replaceWith('∉'); return true;
|
||||||
|
case TokenID.OR: this.replaceWith('∨'); return true;
|
||||||
|
case TokenID.AND: this.replaceWith('&'); return true;
|
||||||
|
case TokenID.SUBSET_OR_EQ: this.replaceWith('⊆'); return true;
|
||||||
|
case TokenID.IMPLICATION: this.replaceWith('⇒'); return true;
|
||||||
|
case TokenID.INTERSECTION: this.replaceWith('∩'); return true;
|
||||||
|
case TokenID.UNION: this.replaceWith('∪'); return true;
|
||||||
|
case TokenID.SET_MINUS: this.replaceWith('\\'); return true;
|
||||||
|
case TokenID.SYMMINUS: this.replaceWith('∆'); return true;
|
||||||
|
case TokenID.LIT_EMPTYSET: this.replaceWith('∅'); return true;
|
||||||
|
case TokenID.LIT_INTSET: this.replaceWith('Z'); return true;
|
||||||
|
case TokenID.SUBSET: this.replaceWith('⊂'); return true;
|
||||||
|
case TokenID.NOTSUBSET: this.replaceWith('⊄'); return true;
|
||||||
|
case TokenID.EQUAL: this.replaceWith('='); return true;
|
||||||
|
case TokenID.NOTEQUAL: this.replaceWith('≠'); return true;
|
||||||
|
case TokenID.NOT: this.replaceWith('¬'); return true;
|
||||||
|
case TokenID.EQUIVALENT: this.replaceWith('⇔'); return true;
|
||||||
|
case TokenID.GREATER_OR_EQ: this.replaceWith('≥'); return true;
|
||||||
|
case TokenID.LESSER_OR_EQ: this.replaceWith('≤'); return true;
|
||||||
|
case TokenID.PUNC_ASSIGN: this.replaceWith(':='); return true;
|
||||||
|
case TokenID.PUNC_ITERATE: this.replaceWith(':∈'); return true;
|
||||||
|
case TokenID.MULTIPLY: this.replaceWith('*'); return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
processAltKey(key: string): boolean {
|
||||||
|
switch(key) {
|
||||||
|
// qwert
|
||||||
|
// asdfg
|
||||||
|
// zxcvb
|
||||||
|
case 'q': return this.insertToken(TokenID.BIGPR);
|
||||||
|
case 'w': return this.insertToken(TokenID.SMALLPR);
|
||||||
|
case 'e': return this.insertToken(TokenID.BOOLEAN);
|
||||||
|
case 'r': return this.insertToken(TokenID.REDUCE);
|
||||||
|
case 't': return this.insertToken(TokenID.NT_RECURSIVE_FULL);
|
||||||
|
case 'a': return this.insertToken(TokenID.INTERSECTION);
|
||||||
|
case 's': return this.insertToken(TokenID.UNION);
|
||||||
|
case 'd': return this.insertToken(TokenID.NT_DECLARATIVE_EXPR);
|
||||||
|
case 'f': return this.insertToken(TokenID.FILTER);
|
||||||
|
case 'g': return this.insertToken(TokenID.NT_IMPERATIVE_EXPR);
|
||||||
|
case 'z': return this.insertToken(TokenID.LIT_INTSET);
|
||||||
|
case 'x': return this.insertToken(TokenID.LIT_EMPTYSET);
|
||||||
|
case 'c': return this.insertToken(TokenID.CARD);
|
||||||
|
case 'v': return this.insertToken(TokenID.DEBOOL);
|
||||||
|
case 'b': return this.insertToken(TokenID.BOOL);
|
||||||
|
|
||||||
|
// `123456
|
||||||
|
// ~!@#$%^
|
||||||
|
case '`': return this.insertToken(TokenID.NOT);
|
||||||
|
case '~': return this.insertToken(TokenID.NOTEQUAL);
|
||||||
|
case '1': return this.insertToken(TokenID.IN);
|
||||||
|
case '!': return this.insertToken(TokenID.NOTIN); // Alt + 1
|
||||||
|
case '2': return this.insertToken(TokenID.SUBSET_OR_EQ);
|
||||||
|
case '@': return this.insertToken(TokenID.NOTSUBSET); // Alt + 2
|
||||||
|
case '3': return this.insertToken(TokenID.AND);
|
||||||
|
case '#': return this.insertToken(TokenID.OR); // Alt + 3
|
||||||
|
case '4': return this.insertToken(TokenID.IMPLICATION);
|
||||||
|
case '$': return this.insertToken(TokenID.EQUIVALENT); // Alt + 4
|
||||||
|
case '5': return this.insertToken(TokenID.SET_MINUS);
|
||||||
|
case '%': return this.insertToken(TokenID.SYMMINUS); // Alt + 5
|
||||||
|
case '6': return this.insertToken(TokenID.PUNC_ITERATE);
|
||||||
|
case '^': return this.insertToken(TokenID.PUNC_ASSIGN); // Alt + 6
|
||||||
|
case '7': return this.insertToken(TokenID.SUBSET);
|
||||||
|
case '&': return this.insertToken(TokenID.GREATER_OR_EQ); // Alt + 7
|
||||||
|
case '8': return this.insertToken(TokenID.MULTIPLY);
|
||||||
|
case '*': return this.insertToken(TokenID.LESSER_OR_EQ); // Alt + 8
|
||||||
|
case '(': return this.insertToken(TokenID.PUNC_PL); // Alt + 9
|
||||||
|
case '[': return this.insertToken(TokenID.PUNC_SL);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
|
@ -24,8 +24,8 @@ export function getTypeLabel(cst: IConstituenta) {
|
||||||
export function getRSButtonData(id: TokenID): IRSButtonData {
|
export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
switch(id) {
|
switch(id) {
|
||||||
case TokenID.BOOLEAN: return {
|
case TokenID.BOOLEAN: return {
|
||||||
text: 'ℬ',
|
text: 'ℬ()',
|
||||||
tooltip: 'Булеан [Shift + B]',
|
tooltip: 'Булеан [Alt + E]',
|
||||||
};
|
};
|
||||||
case TokenID.DECART: return {
|
case TokenID.DECART: return {
|
||||||
text: '×',
|
text: '×',
|
||||||
|
@ -33,11 +33,11 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
};
|
};
|
||||||
case TokenID.PUNC_PL: return {
|
case TokenID.PUNC_PL: return {
|
||||||
text: '( )',
|
text: '( )',
|
||||||
tooltip: 'Скобки вокруг выражения',
|
tooltip: 'Скобки вокруг выражения [ Alt + Shift + 9 ]',
|
||||||
};
|
};
|
||||||
case TokenID.PUNC_SL: return {
|
case TokenID.PUNC_SL: return {
|
||||||
text: '[ ]',
|
text: '[ ]',
|
||||||
tooltip: 'Скобки вокруг выражения',
|
tooltip: 'Скобки вокруг выражения [ Alt + [ ]',
|
||||||
};
|
};
|
||||||
case TokenID.FORALL: return {
|
case TokenID.FORALL: return {
|
||||||
text: '∀',
|
text: '∀',
|
||||||
|
@ -49,31 +49,31 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
};
|
};
|
||||||
case TokenID.NOT: return {
|
case TokenID.NOT: return {
|
||||||
text: '¬',
|
text: '¬',
|
||||||
tooltip: 'Отрицание [Alt + Shift + -]',
|
tooltip: 'Отрицание [Alt + `]',
|
||||||
};
|
};
|
||||||
case TokenID.AND: return {
|
case TokenID.AND: return {
|
||||||
text: '&',
|
text: '&',
|
||||||
tooltip: 'Конъюнкция [Shift + 7]',
|
tooltip: 'Конъюнкция [Alt + 3 ~ Shift + 7]',
|
||||||
};
|
};
|
||||||
case TokenID.OR: return {
|
case TokenID.OR: return {
|
||||||
text: '∨',
|
text: '∨',
|
||||||
tooltip: 'дизъюнкция [Alt + Shift + 7]',
|
tooltip: 'дизъюнкция [Alt + Shift + 3]',
|
||||||
};
|
};
|
||||||
case TokenID.IMPLICATION: return {
|
case TokenID.IMPLICATION: return {
|
||||||
text: '⇒',
|
text: '⇒',
|
||||||
tooltip: 'импликация [Alt + Shift + ]]',
|
tooltip: 'импликация [Alt + 4]',
|
||||||
};
|
};
|
||||||
case TokenID.EQUIVALENT: return {
|
case TokenID.EQUIVALENT: return {
|
||||||
text: '⇔',
|
text: '⇔',
|
||||||
tooltip: 'эквивалентность [Alt + Shift + []',
|
tooltip: 'эквивалентность [Alt + Shift + 4]',
|
||||||
};
|
};
|
||||||
case TokenID.LIT_EMPTYSET: return {
|
case TokenID.LIT_EMPTYSET: return {
|
||||||
text: '∅',
|
text: '∅',
|
||||||
tooltip: 'пустое множество [Alt + Shift + 0]',
|
tooltip: 'пустое множество [Alt + X]',
|
||||||
};
|
};
|
||||||
case TokenID.LIT_INTSET: return {
|
case TokenID.LIT_INTSET: return {
|
||||||
text: 'Z',
|
text: 'Z',
|
||||||
tooltip: 'целые числа',
|
tooltip: 'целые числа [Alt + Z]',
|
||||||
};
|
};
|
||||||
case TokenID.EQUAL: return {
|
case TokenID.EQUAL: return {
|
||||||
text: '=',
|
text: '=',
|
||||||
|
@ -81,7 +81,7 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
};
|
};
|
||||||
case TokenID.NOTEQUAL: return {
|
case TokenID.NOTEQUAL: return {
|
||||||
text: '≠',
|
text: '≠',
|
||||||
tooltip: 'неравенство [Alt + Shift + =]',
|
tooltip: 'неравенство [Alt + Shift + `]',
|
||||||
};
|
};
|
||||||
case TokenID.GREATER_OR_EQ: return {
|
case TokenID.GREATER_OR_EQ: return {
|
||||||
text: '≥',
|
text: '≥',
|
||||||
|
@ -93,7 +93,7 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
};
|
};
|
||||||
case TokenID.IN: return {
|
case TokenID.IN: return {
|
||||||
text: '∈',
|
text: '∈',
|
||||||
tooltip: 'принадлежит [Alt + \']',
|
tooltip: 'быть элементом (принадлежит) [Alt + \']',
|
||||||
};
|
};
|
||||||
case TokenID.NOTIN: return {
|
case TokenID.NOTIN: return {
|
||||||
text: '∉',
|
text: '∉',
|
||||||
|
@ -101,7 +101,7 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
};
|
};
|
||||||
case TokenID.SUBSET_OR_EQ: return {
|
case TokenID.SUBSET_OR_EQ: return {
|
||||||
text: '⊆',
|
text: '⊆',
|
||||||
tooltip: 'быть частью (нестрогое подмножество) [Alt + Shift + L]',
|
tooltip: 'быть частью (нестрогое подмножество) [Alt + 2]',
|
||||||
};
|
};
|
||||||
case TokenID.SUBSET: return {
|
case TokenID.SUBSET: return {
|
||||||
text: '⊂',
|
text: '⊂',
|
||||||
|
@ -109,7 +109,7 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
};
|
};
|
||||||
case TokenID.NOTSUBSET: return {
|
case TokenID.NOTSUBSET: return {
|
||||||
text: '⊄',
|
text: '⊄',
|
||||||
tooltip: 'не подмножество [Alt + Shift + ;]',
|
tooltip: 'не подмножество [Alt + Shift + 2]',
|
||||||
};
|
};
|
||||||
case TokenID.INTERSECTION: return {
|
case TokenID.INTERSECTION: return {
|
||||||
text: '∩',
|
text: '∩',
|
||||||
|
@ -119,36 +119,36 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
text: '∪',
|
text: '∪',
|
||||||
tooltip: 'объединение [Alt + U]',
|
tooltip: 'объединение [Alt + U]',
|
||||||
};
|
};
|
||||||
case TokenID.MINUS: return {
|
case TokenID.SET_MINUS: return {
|
||||||
text: '\\',
|
text: '\\',
|
||||||
tooltip: 'Разность множеств',
|
tooltip: 'Разность множеств [Alt + 5]',
|
||||||
};
|
};
|
||||||
case TokenID.SYMMINUS: return {
|
case TokenID.SYMMINUS: return {
|
||||||
text: '∆',
|
text: '∆',
|
||||||
tooltip: 'Симметрическая разность',
|
tooltip: 'Симметрическая разность [Alt + Shift + 5]',
|
||||||
};
|
};
|
||||||
case TokenID.NT_DECLARATIVE_EXPR: return {
|
case TokenID.NT_DECLARATIVE_EXPR: return {
|
||||||
text: 'D{}',
|
text: 'D{}',
|
||||||
tooltip: 'Декларативная форма определения терма',
|
tooltip: 'Декларативная форма определения терма [Alt + D]',
|
||||||
};
|
};
|
||||||
case TokenID.NT_IMPERATIVE_EXPR: return {
|
case TokenID.NT_IMPERATIVE_EXPR: return {
|
||||||
text: 'I{}',
|
text: 'I{}',
|
||||||
tooltip: 'императивная форма определения терма',
|
tooltip: 'императивная форма определения терма [Alt + G]',
|
||||||
};
|
};
|
||||||
case TokenID.NT_RECURSIVE_FULL: return {
|
case TokenID.NT_RECURSIVE_FULL: return {
|
||||||
text: 'R{}',
|
text: 'R{}',
|
||||||
tooltip: 'рекурсивная (цикличная) форма определения терма',
|
tooltip: 'рекурсивная (цикличная) форма определения терма [Alt + T]',
|
||||||
};
|
};
|
||||||
case TokenID.BIGPR: return {
|
case TokenID.BIGPR: return {
|
||||||
text: 'Pr1()',
|
text: 'Pr1()',
|
||||||
tooltip: 'большая проекция [Alt + W]',
|
tooltip: 'большая проекция [Alt + Q]',
|
||||||
};
|
};
|
||||||
case TokenID.SMALLPR: return {
|
case TokenID.SMALLPR: return {
|
||||||
text: 'pr1()',
|
text: 'pr1()',
|
||||||
tooltip: 'малая проекция [Alt + Q]',
|
tooltip: 'малая проекция [Alt + W]',
|
||||||
};
|
};
|
||||||
case TokenID.FILTER: return {
|
case TokenID.FILTER: return {
|
||||||
text: 'Fi[]()',
|
text: 'Fi1[]()',
|
||||||
tooltip: 'фильтр [Alt + F]',
|
tooltip: 'фильтр [Alt + F]',
|
||||||
};
|
};
|
||||||
case TokenID.REDUCE: return {
|
case TokenID.REDUCE: return {
|
||||||
|
@ -165,7 +165,7 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
};
|
};
|
||||||
case TokenID.DEBOOL: return {
|
case TokenID.DEBOOL: return {
|
||||||
text: 'debool()',
|
text: 'debool()',
|
||||||
tooltip: 'десинглетон [Alt + D]',
|
tooltip: 'десинглетон [Alt + V]',
|
||||||
};
|
};
|
||||||
case TokenID.PUNC_ASSIGN: return {
|
case TokenID.PUNC_ASSIGN: return {
|
||||||
text: ':=',
|
text: ':=',
|
||||||
|
@ -248,6 +248,6 @@ export function getStatusInfo(status?: ExpressionStatus): IStatusInfo {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractGlobals(expression: string) {
|
export function extractGlobals(expression: string): Set<string> {
|
||||||
return expression.match(/[XCSADFPT]\d+/g) || [];
|
return new Set(expression.match(/[XCSADFPT]\d+/g) || []);
|
||||||
}
|
}
|
|
@ -1,118 +0,0 @@
|
||||||
// Formatted text editing helpers
|
|
||||||
|
|
||||||
import { TokenID } from './models'
|
|
||||||
|
|
||||||
export interface IManagedText {
|
|
||||||
value: string
|
|
||||||
selStart: number
|
|
||||||
selEnd: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Wrapper class for textareafield.
|
|
||||||
// WARNING! Manipulations on value do not support UNDO browser
|
|
||||||
// WARNING! No checks for selection out of text boundaries
|
|
||||||
export class TextWrapper implements IManagedText {
|
|
||||||
value: string
|
|
||||||
selStart: number
|
|
||||||
selEnd: number
|
|
||||||
object: HTMLTextAreaElement
|
|
||||||
|
|
||||||
constructor(element: HTMLTextAreaElement) {
|
|
||||||
this.object = element;
|
|
||||||
this.value = this.object.value;
|
|
||||||
this.selStart = this.object.selectionStart;
|
|
||||||
this.selEnd = this.object.selectionEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
focus() {
|
|
||||||
this.object.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh() {
|
|
||||||
this.value = this.object.value;
|
|
||||||
this.selStart = this.object.selectionStart;
|
|
||||||
this.selEnd = this.object.selectionEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize() {
|
|
||||||
this.object.value = this.value;
|
|
||||||
this.object.selectionStart = this.selStart;
|
|
||||||
this.object.selectionEnd = this.selEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceWith(data: string) {
|
|
||||||
this.value = this.value.substring(0, this.selStart) + data + this.value.substring(this.selEnd, this.value.length);
|
|
||||||
this.selEnd += data.length - this.selEnd + this.selStart;
|
|
||||||
this.selStart = this.selEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
envelopeWith(left: string, right: string) {
|
|
||||||
this.value = this.value.substring(0, this.selStart) + left +
|
|
||||||
this.value.substring(this.selStart, this.selEnd) + right +
|
|
||||||
this.value.substring(this.selEnd, this.value.length);
|
|
||||||
this.selEnd += left.length + right.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
moveSel(shift: number) {
|
|
||||||
this.selStart += shift;
|
|
||||||
this.selEnd += shift;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSel(start: number, end: number) {
|
|
||||||
this.selStart = start;
|
|
||||||
this.selEnd = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertToken(tokenID: TokenID) {
|
|
||||||
switch(tokenID) {
|
|
||||||
case TokenID.NT_DECLARATIVE_EXPR: this.envelopeWith('D{ξ∈X1 | ', '}'); return;
|
|
||||||
case TokenID.NT_IMPERATIVE_EXPR: this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; ', '}'); return;
|
|
||||||
case TokenID.NT_RECURSIVE_FULL: this.envelopeWith('R{ ξ:=D1 | 1=1 | ', '}'); return;
|
|
||||||
case TokenID.BIGPR: this.envelopeWith('Pr1(', ')'); return;
|
|
||||||
case TokenID.SMALLPR: this.envelopeWith('pr1(', ')'); return;
|
|
||||||
case TokenID.FILTER: this.envelopeWith('Fi1[α](', ')'); return;
|
|
||||||
case TokenID.REDUCE: this.envelopeWith('red(', ')'); return;
|
|
||||||
case TokenID.CARD: this.envelopeWith('card(', ')'); return;
|
|
||||||
case TokenID.BOOL: this.envelopeWith('bool(', ')'); return;
|
|
||||||
case TokenID.DEBOOL: this.envelopeWith('debool(', ')'); return;
|
|
||||||
case TokenID.PUNC_PL: this.envelopeWith('(', ')'); return;
|
|
||||||
case TokenID.PUNC_SL: this.envelopeWith('[', ']'); return;
|
|
||||||
|
|
||||||
case TokenID.BOOLEAN: {
|
|
||||||
if (this.selEnd !== this.selStart && this.value.at(this.selStart) === 'ℬ') {
|
|
||||||
this.envelopeWith('ℬ', '');
|
|
||||||
} else {
|
|
||||||
this.envelopeWith('ℬ(', ')');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case TokenID.DECART: this.replaceWith('×'); return;
|
|
||||||
case TokenID.FORALL: this.replaceWith('∀'); return;
|
|
||||||
case TokenID.EXISTS: this.replaceWith('∃'); return;
|
|
||||||
case TokenID.IN: this.replaceWith('∈'); return;
|
|
||||||
case TokenID.NOTIN: this.replaceWith('∉'); return;
|
|
||||||
case TokenID.OR: this.replaceWith('∨'); return;
|
|
||||||
case TokenID.AND: this.replaceWith('&'); return;
|
|
||||||
case TokenID.SUBSET_OR_EQ: this.replaceWith('⊆'); return;
|
|
||||||
case TokenID.IMPLICATION: this.replaceWith('⇒'); return;
|
|
||||||
case TokenID.INTERSECTION: this.replaceWith('∩'); return;
|
|
||||||
case TokenID.UNION: this.replaceWith('∪'); return;
|
|
||||||
case TokenID.MINUS: this.replaceWith('\\'); return;
|
|
||||||
case TokenID.SYMMINUS: this.replaceWith('∆'); return;
|
|
||||||
case TokenID.LIT_EMPTYSET: this.replaceWith('∅'); return;
|
|
||||||
case TokenID.LIT_INTSET: this.replaceWith('Z'); return;
|
|
||||||
case TokenID.SUBSET: this.replaceWith('⊂'); return;
|
|
||||||
case TokenID.NOTSUBSET: this.replaceWith('⊄'); return;
|
|
||||||
case TokenID.EQUAL: this.replaceWith('='); return;
|
|
||||||
case TokenID.NOTEQUAL: this.replaceWith('≠'); return;
|
|
||||||
case TokenID.NOT: this.replaceWith('¬'); return;
|
|
||||||
case TokenID.EQUIVALENT: this.replaceWith('⇔'); return;
|
|
||||||
case TokenID.GREATER_OR_EQ: this.replaceWith('≥'); return;
|
|
||||||
case TokenID.LESSER_OR_EQ: this.replaceWith('≤'); return;
|
|
||||||
case TokenID.PUNC_ASSIGN: this.replaceWith(':='); return;
|
|
||||||
case TokenID.PUNC_ITERATE: this.replaceWith(':∈'); return;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user