mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
Refactoring and multiple minor UI improvements
This commit is contained in:
parent
19a02b435f
commit
f03e61e3c1
|
@ -6,15 +6,17 @@ export interface CheckboxProps {
|
||||||
required?: boolean
|
required?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
widthClass?: string
|
widthClass?: string
|
||||||
|
tooltip?: string
|
||||||
value?: boolean
|
value?: boolean
|
||||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function Checkbox({ id, required, disabled, label, widthClass = 'w-full', value, onChange }: CheckboxProps) {
|
// TODO: implement disabled={disabled}
|
||||||
|
function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-full', value, onChange }: CheckboxProps) {
|
||||||
return (
|
return (
|
||||||
<div className={'flex gap-2 [&:not(:first-child)]:mt-3 ' + widthClass}>
|
<div className={'flex gap-2 [&:not(:first-child)]:mt-3 ' + widthClass} title={tooltip}>
|
||||||
<input id={id} type='checkbox'
|
<input id={id} type='checkbox'
|
||||||
className='relative cursor-pointer peer w-4 h-4 shrink-0 mt-0.5 border rounded-sm appearance-none clr-checkbox'
|
className='relative cursor-pointer disabled:cursor-not-allowed peer w-4 h-4 shrink-0 mt-0.5 border rounded-sm appearance-none clr-checkbox'
|
||||||
required={required}
|
required={required}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
checked={value}
|
checked={value}
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { Ref } from 'react';
|
|
||||||
import { darkTheme, GraphCanvas, GraphCanvasProps, GraphCanvasRef, lightTheme } from 'reagraph';
|
|
||||||
|
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
|
||||||
import { resources } from '../../utils/constants';
|
|
||||||
|
|
||||||
interface ConceptGraphProps
|
|
||||||
extends Omit<GraphCanvasProps, 'theme' | 'labelFontUrl'> {
|
|
||||||
ref?: Ref<GraphCanvasRef>
|
|
||||||
sizeClass: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function ConceptGraph({ sizeClass, ...props }: ConceptGraphProps) {
|
|
||||||
const { darkMode } = useConceptTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='flex-wrap w-full h-full overflow-auto'>
|
|
||||||
<div className={`relative border ${sizeClass}`}>
|
|
||||||
<GraphCanvas
|
|
||||||
theme={darkMode ? darkTheme : lightTheme}
|
|
||||||
labelFontUrl={resources.graph_font}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ConceptGraph;
|
|
|
@ -6,9 +6,10 @@ extends Omit<PropsWithRef<SelectProps<T>>, 'noDataLabel'> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConceptSelect<T extends object | string>({ ...props }: ConceptSelectProps<T>) {
|
function ConceptSelect<T extends object | string>({ className, ...props }: ConceptSelectProps<T>) {
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
className={`overflow-ellipsis whitespace-nowrap ${className}`}
|
||||||
{...props}
|
{...props}
|
||||||
noDataLabel='Список пуст'
|
noDataLabel='Список пуст'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,18 +3,18 @@ import { ITooltip, Tooltip } from 'react-tooltip';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
|
||||||
interface ConceptTooltipProps
|
interface ConceptTooltipProps
|
||||||
extends Omit<ITooltip, 'variant' | 'place'> {
|
extends Omit<ITooltip, 'variant'> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConceptTooltip({ className, ...props }: ConceptTooltipProps) {
|
function ConceptTooltip({ className, place='bottom', ...props }: ConceptTooltipProps) {
|
||||||
const { darkMode } = useConceptTheme();
|
const { darkMode } = useConceptTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
className={`overflow-auto border shadow-md z-20 ${className}`}
|
className={`overflow-auto border shadow-md z-20 ${className}`}
|
||||||
variant={(darkMode ? 'dark' : 'light')}
|
variant={(darkMode ? 'dark' : 'light')}
|
||||||
place='bottom'
|
place={place}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,11 +3,11 @@ interface DividerProps {
|
||||||
margins?: string
|
margins?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function Divider({ vertical, margins = '2' }: DividerProps) {
|
function Divider({ vertical, margins = 'mx-2' }: DividerProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{vertical && <div className={`mx-${margins} border-x-2`} />}
|
{vertical && <div className={`${margins} border-x-2`} />}
|
||||||
{!vertical && <div className={`my-${margins} border-y-2`} />}
|
{!vertical && <div className={`${margins} border-y-2`} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ interface DropdownProps {
|
||||||
function Dropdown({ children, widthClass = 'w-fit', stretchLeft }: DropdownProps) {
|
function Dropdown({ children, widthClass = 'w-fit', stretchLeft }: DropdownProps) {
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<div className={`absolute ${stretchLeft ? 'right-0' : 'left-0'} py-2 z-10 flex flex-col items-stretch justify-start px-2 mt-2 text-sm origin-top-right bg-white border border-gray-100 divide-y rounded-md shadow-lg dark:border-gray-500 dark:bg-gray-900 ${widthClass}`}>
|
<div className={`absolute ${stretchLeft ? 'right-0' : 'left-0'} py-2 z-40 flex flex-col items-stretch justify-start px-2 mt-2 text-sm origin-top-right bg-white border border-gray-100 divide-y rounded-md shadow-lg dark:border-gray-500 dark:bg-gray-900 ${widthClass}`}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
import useClickedOutside from '../../hooks/useClickedOutside';
|
|
||||||
import useEscapeKey from '../../hooks/useEscapeKey';
|
import useEscapeKey from '../../hooks/useEscapeKey';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
|
||||||
|
@ -16,7 +15,6 @@ interface ModalProps {
|
||||||
|
|
||||||
function Modal({ title, hideWindow, onSubmit, onCancel, canSubmit, children, submitText = 'Продолжить' }: ModalProps) {
|
function Modal({ title, hideWindow, onSubmit, onCancel, canSubmit, children, submitText = 'Продолжить' }: ModalProps) {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
useClickedOutside({ ref, callback: hideWindow });
|
|
||||||
useEscapeKey(hideWindow);
|
useEscapeKey(hideWindow);
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
|
@ -34,7 +32,7 @@ function Modal({ title, hideWindow, onSubmit, onCancel, canSubmit, children, sub
|
||||||
<div className='fixed top-0 left-0 z-50 w-full h-full opacity-50 clr-modal'>
|
<div className='fixed top-0 left-0 z-50 w-full h-full opacity-50 clr-modal'>
|
||||||
</div>
|
</div>
|
||||||
<div ref={ref} className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col w-fit h-fit z-[60] clr-card border shadow-md mb-[5rem]'>
|
<div ref={ref} className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col w-fit h-fit z-[60] clr-card border shadow-md mb-[5rem]'>
|
||||||
{ title && <h1 className='mb-4 text-xl font-bold text-center'>{title}</h1> }
|
{ title && <h1 className='mb-2 text-xl font-bold text-center'>{title}</h1> }
|
||||||
<div className='py-2'>
|
<div className='py-2'>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,7 +16,6 @@ export function useRSFormDetails({ target }: { target?: string }) {
|
||||||
}
|
}
|
||||||
const schema = LoadRSFormData(data);
|
const schema = LoadRSFormData(data);
|
||||||
setInnerSchema(schema);
|
setInnerSchema(schema);
|
||||||
console.log('Loaded schema: ', schema);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const reload = useCallback(
|
const reload = useCallback(
|
||||||
|
|
|
@ -9,7 +9,7 @@ export function useUserProfile() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
const fetchUser = useCallback(
|
const reload = useCallback(
|
||||||
() => {
|
() => {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setUser(undefined);
|
setUser(undefined);
|
||||||
|
@ -29,14 +29,17 @@ export function useUserProfile() {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setLoading,
|
setLoading: setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => { setError(error); },
|
||||||
onSuccess: newData => { setUser(newData); if (callback) callback(newData) }
|
onSuccess: newData => {
|
||||||
|
setUser(newData);
|
||||||
|
if (callback) callback(newData);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [setUser]
|
}, [setUser]
|
||||||
)
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUser();
|
reload();
|
||||||
}, [fetchUser])
|
}, [reload])
|
||||||
|
|
||||||
return { user, fetchUser, updateUser, error, loading };
|
return { user, updateUser, error, loading };
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ function DlgCreateCst({ hideWindow, defaultType, onCreate }: DlgCreateCstProps)
|
||||||
options={CstTypeSelector}
|
options={CstTypeSelector}
|
||||||
placeholder='Выберите тип'
|
placeholder='Выберите тип'
|
||||||
values={selectedType ? [{ value: selectedType, label: getCstTypeLabel(selectedType) }] : []}
|
values={selectedType ? [{ value: selectedType, label: getCstTypeLabel(selectedType) }] : []}
|
||||||
onChange={data => { setSelectedType(data[0].value); }}
|
onChange={data => { setSelectedType(data.length > 0 ? data[0].value : undefined); }}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -38,17 +38,20 @@ function DlgUploadRSForm({ hideWindow }: DlgUploadRSFormProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title='Загрузка схемы из Экстеор'
|
title='Импорт схемы из Экстеора'
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
canSubmit={!!file}
|
canSubmit={!!file}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
submitText='Загрузить'
|
||||||
>
|
>
|
||||||
<div className='max-w-[20rem]'>
|
<div className='max-w-[20rem]'>
|
||||||
<FileInput label='Загрузить файл'
|
<FileInput
|
||||||
|
label='Выбрать файл'
|
||||||
acceptType='.trs'
|
acceptType='.trs'
|
||||||
onChange={handleFile}
|
onChange={handleFile}
|
||||||
/>
|
/>
|
||||||
<Checkbox label='Загружать метаданные'
|
<Checkbox
|
||||||
|
label='Загружать название и комментарий'
|
||||||
value={loadMetadata}
|
value={loadMetadata}
|
||||||
onChange={event => { setLoadMetadata(event.target.checked); }}
|
onChange={event => { setLoadMetadata(event.target.checked); }}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { RSTabsList } from './RSTabs';
|
||||||
|
|
||||||
interface EditorConstituentaProps {
|
interface EditorConstituentaProps {
|
||||||
onShowAST: (ast: SyntaxTree) => void
|
onShowAST: (ast: SyntaxTree) => void
|
||||||
onShowCreateCst: (position: number | undefined, type: CstType | undefined) => void
|
onShowCreateCst: (selectedID: number | undefined, type: CstType | undefined) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorConstituenta({ onShowAST, onShowCreateCst }: EditorConstituentaProps) {
|
function EditorConstituenta({ onShowAST, onShowCreateCst }: EditorConstituentaProps) {
|
||||||
|
|
|
@ -8,12 +8,13 @@ import Divider from '../../components/Common/Divider';
|
||||||
import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons';
|
import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import { CstType, type IConstituenta, ICstMovetoData, inferStatus, ParsingStatus, ValueClass } from '../../utils/models'
|
import { prefixes } from '../../utils/constants';
|
||||||
import { getCstTypePrefix, getCstTypeShortcut, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
|
import { CstType, IConstituenta, ICstMovetoData } from '../../utils/models'
|
||||||
|
import { getCstTypePrefix, getCstTypeShortcut, getTypeLabel, mapStatusInfo } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface EditorItemsProps {
|
interface EditorItemsProps {
|
||||||
onOpenEdit: (cst: IConstituenta) => void
|
onOpenEdit: (cst: IConstituenta) => void
|
||||||
onShowCreateCst: (position: number | undefined, type: CstType | undefined, skipDialog?: boolean) => void
|
onShowCreateCst: (selectedID: number | undefined, type: CstType | undefined, skipDialog?: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorItems({ onOpenEdit, onShowCreateCst }: EditorItemsProps) {
|
function EditorItems({ onOpenEdit, onShowCreateCst }: EditorItemsProps) {
|
||||||
|
@ -121,8 +122,8 @@ function EditorItems({ onOpenEdit, onShowCreateCst }: EditorItemsProps) {
|
||||||
const selectedPosition = selected.reduce((prev, cstID) => {
|
const selectedPosition = selected.reduce((prev, cstID) => {
|
||||||
const position = schema.items.findIndex(cst => cst.id === cstID);
|
const position = schema.items.findIndex(cst => cst.id === cstID);
|
||||||
return Math.max(position, prev);
|
return Math.max(position, prev);
|
||||||
}, -1) + 1;
|
}, -1);
|
||||||
const insert_where = selectedPosition > 0 ? selectedPosition : undefined;
|
const insert_where = selectedPosition >= 0 ? schema.items[selectedPosition].id : undefined;
|
||||||
onShowCreateCst(insert_where, type, type !== undefined);
|
onShowCreateCst(insert_where, type, type !== undefined);
|
||||||
}, [schema, onShowCreateCst, selected]);
|
}, [schema, onShowCreateCst, selected]);
|
||||||
|
|
||||||
|
@ -173,53 +174,30 @@ function EditorItems({ onOpenEdit, onShowCreateCst }: EditorItemsProps) {
|
||||||
selector: (cst: IConstituenta) => cst.id,
|
selector: (cst: IConstituenta) => cst.id,
|
||||||
omit: true
|
omit: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Статус',
|
|
||||||
id: 'status',
|
|
||||||
cell: (cst: IConstituenta) =>
|
|
||||||
<div style={{ fontSize: 12 }}>
|
|
||||||
{getStatusInfo(inferStatus(cst.parse?.status, cst.parse?.valueClass)).text}
|
|
||||||
</div>,
|
|
||||||
width: '80px',
|
|
||||||
maxWidth: '80px',
|
|
||||||
reorder: true,
|
|
||||||
hide: 1280,
|
|
||||||
conditionalCellStyles: [
|
|
||||||
{
|
|
||||||
when: (cst: IConstituenta) => cst.parse?.status !== ParsingStatus.VERIFIED,
|
|
||||||
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.INVALID,
|
|
||||||
classNames: ['bg-[#beeefa]', 'dark:bg-[#286675]']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.PROPERTY,
|
|
||||||
classNames: ['bg-[#a5e9fa]', 'dark:bg-[#36899e]']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Имя',
|
name: 'Имя',
|
||||||
id: 'alias',
|
id: 'alias',
|
||||||
selector: (cst: IConstituenta) => cst.alias,
|
selector: (cst: IConstituenta) => cst.alias,
|
||||||
|
cell: (cst: IConstituenta) => {
|
||||||
|
const info = mapStatusInfo.get(cst.status)!;
|
||||||
|
return (<>
|
||||||
|
<div
|
||||||
|
id={`${prefixes.cst_list}${cst.alias}`}
|
||||||
|
className={`w-full rounded-md text-center ${info.color}`}
|
||||||
|
>
|
||||||
|
{cst.alias}
|
||||||
|
</div>
|
||||||
|
<ConceptTooltip
|
||||||
|
anchorSelect={`#${prefixes.cst_list}${cst.alias}`}
|
||||||
|
place='right'
|
||||||
|
>
|
||||||
|
<p><b>Статус: </b> {info.tooltip}</p>
|
||||||
|
</ConceptTooltip>
|
||||||
|
</>);
|
||||||
|
},
|
||||||
width: '65px',
|
width: '65px',
|
||||||
maxWidth: '65px',
|
maxWidth: '65px',
|
||||||
reorder: true,
|
reorder: true,
|
||||||
conditionalCellStyles: [
|
|
||||||
{
|
|
||||||
when: (cst: IConstituenta) => cst.parse?.status !== ParsingStatus.VERIFIED,
|
|
||||||
classNames: ['bg-[#ff8080]', 'dark:bg-[#800000]']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.INVALID,
|
|
||||||
classNames: ['bg-[#ffbb80]', 'dark:bg-[#964600]']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
when: (cst: IConstituenta) => cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.PROPERTY,
|
|
||||||
classNames: ['bg-[#a5e9fa]', 'dark:bg-[#36899e]']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Тип',
|
name: 'Тип',
|
||||||
|
@ -311,7 +289,7 @@ function EditorItems({ onOpenEdit, onShowCreateCst }: EditorItemsProps) {
|
||||||
dense
|
dense
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
/>
|
/>
|
||||||
<Divider vertical margins='1' />
|
<Divider vertical margins='my-1' />
|
||||||
<Button
|
<Button
|
||||||
tooltip='Переиндексировать имена'
|
tooltip='Переиндексировать имена'
|
||||||
icon={<ArrowsRotateIcon color='text-primary' size={6}/>}
|
icon={<ArrowsRotateIcon color='text-primary' size={6}/>}
|
||||||
|
@ -341,9 +319,23 @@ function EditorItems({ onOpenEdit, onShowCreateCst }: EditorItemsProps) {
|
||||||
<div>
|
<div>
|
||||||
<h1>Горячие клавиши</h1>
|
<h1>Горячие клавиши</h1>
|
||||||
<p><b>Двойной клик / Alt + клик</b> - редактирование конституенты</p>
|
<p><b>Двойной клик / Alt + клик</b> - редактирование конституенты</p>
|
||||||
|
<p><b>Клик на квадрат слева</b> - выделение конституенты</p>
|
||||||
<p><b>Alt + вверх/вниз</b> - движение конституент</p>
|
<p><b>Alt + вверх/вниз</b> - движение конституент</p>
|
||||||
<p><b>Delete</b> - удаление конституент</p>
|
<p><b>Delete</b> - удаление конституент</p>
|
||||||
<p><b>Alt + 1-6, Q,W</b> - добавление конституент</p>
|
<p><b>Alt + 1-6, Q,W</b> - добавление конституент</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>
|
</div>
|
||||||
</ConceptTooltip>
|
</ConceptTooltip>
|
||||||
</div>}
|
</div>}
|
||||||
|
|
|
@ -7,7 +7,8 @@ import { Loader } from '../../components/Common/Loader';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import useCheckExpression from '../../hooks/useCheckExpression';
|
import useCheckExpression from '../../hooks/useCheckExpression';
|
||||||
import { TokenID } from '../../utils/enums';
|
import { TokenID } from '../../utils/enums';
|
||||||
import { CstType, SyntaxTree } from '../../utils/models';
|
import { IRSErrorDescription, SyntaxTree } from '../../utils/models';
|
||||||
|
import { getCstExpressionPrefix } from '../../utils/staticUI';
|
||||||
import ParsingResult from './elements/ParsingResult';
|
import ParsingResult from './elements/ParsingResult';
|
||||||
import RSLocalButton from './elements/RSLocalButton';
|
import RSLocalButton from './elements/RSLocalButton';
|
||||||
import RSTokenButton from './elements/RSTokenButton';
|
import RSTokenButton from './elements/RSTokenButton';
|
||||||
|
@ -42,23 +43,43 @@ function EditorRSExpression({
|
||||||
resetParse();
|
resetParse();
|
||||||
}, [activeCst, resetParse]);
|
}, [activeCst, resetParse]);
|
||||||
|
|
||||||
const handleCheckExpression = useCallback(() => {
|
function handleFocusIn() {
|
||||||
|
toggleEditMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||||
|
onChange(event);
|
||||||
|
setIsModified(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheckExpression() {
|
||||||
if (!activeCst) {
|
if (!activeCst) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const prefix = activeCst?.alias + (activeCst?.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
const prefix = getCstExpressionPrefix(activeCst);
|
||||||
const expression = prefix + value;
|
const expression = prefix + value;
|
||||||
checkExpression(expression, parse => {
|
checkExpression(expression, parse => {
|
||||||
if (!parse.parseResult && parse.errors.length > 0) {
|
if (parse.errors.length > 0) {
|
||||||
const errorPosition = parse.errors[0].position - prefix.length
|
const errorPosition = parse.errors[0].position - prefix.length
|
||||||
expressionCtrl.current!.selectionStart = errorPosition;
|
expressionCtrl.current!.selectionStart = errorPosition;
|
||||||
expressionCtrl.current!.selectionEnd = errorPosition;
|
expressionCtrl.current!.selectionEnd = errorPosition;
|
||||||
expressionCtrl.current!.focus();
|
|
||||||
}
|
}
|
||||||
|
expressionCtrl.current!.focus();
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
setTypification(parse.typification);
|
setTypification(parse.typification);
|
||||||
});
|
});
|
||||||
}, [value, checkExpression, activeCst, setTypification]);
|
}
|
||||||
|
|
||||||
|
const onShowError = useCallback(
|
||||||
|
(error: IRSErrorDescription) => {
|
||||||
|
if (!activeCst || !expressionCtrl.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const errorPosition = error.position - getCstExpressionPrefix(activeCst).length
|
||||||
|
expressionCtrl.current.selectionStart = errorPosition;
|
||||||
|
expressionCtrl.current.selectionEnd = errorPosition;
|
||||||
|
expressionCtrl.current.focus();
|
||||||
|
}, [activeCst]);
|
||||||
|
|
||||||
const handleEdit = useCallback((id: TokenID, key?: string) => {
|
const handleEdit = useCallback((id: TokenID, key?: string) => {
|
||||||
if (!expressionCtrl.current) {
|
if (!expressionCtrl.current) {
|
||||||
|
@ -77,12 +98,8 @@ function EditorRSExpression({
|
||||||
setIsModified(true);
|
setIsModified(true);
|
||||||
}, [setValue]);
|
}, [setValue]);
|
||||||
|
|
||||||
const handleChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleInput = useCallback(
|
||||||
onChange(event);
|
(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
setIsModified(true);
|
|
||||||
}, [setIsModified, onChange]);
|
|
||||||
|
|
||||||
const handleInput = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
||||||
if (!expressionCtrl.current) {
|
if (!expressionCtrl.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -108,10 +125,6 @@ function EditorRSExpression({
|
||||||
setIsModified(true);
|
setIsModified(true);
|
||||||
}, [expressionCtrl, setValue]);
|
}, [expressionCtrl, setValue]);
|
||||||
|
|
||||||
const handleFocusIn = useCallback(() => {
|
|
||||||
toggleEditMode()
|
|
||||||
}, [toggleEditMode]);
|
|
||||||
|
|
||||||
const EditButtons = useMemo(() => {
|
const EditButtons = useMemo(() => {
|
||||||
return (<div className='flex items-center justify-between w-full'>
|
return (<div className='flex items-center justify-between w-full'>
|
||||||
<div className='text-sm w-fit'>
|
<div className='text-sm w-fit'>
|
||||||
|
@ -188,7 +201,7 @@ function EditorRSExpression({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
}, [handleEdit])
|
}, [handleEdit]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full'>
|
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full'>
|
||||||
|
@ -206,6 +219,7 @@ function EditorRSExpression({
|
||||||
onFocus={handleFocusIn}
|
onFocus={handleFocusIn}
|
||||||
onKeyDown={handleInput}
|
onKeyDown={handleInput}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
<div className='flex w-full gap-4 py-1 mt-1 justify-stretch'>
|
<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'>
|
||||||
|
@ -230,7 +244,12 @@ function EditorRSExpression({
|
||||||
{ (loading || parseData) &&
|
{ (loading || parseData) &&
|
||||||
<div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[7rem]'>
|
<div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[7rem]'>
|
||||||
{ loading && <Loader />}
|
{ loading && <Loader />}
|
||||||
{ !loading && parseData && <ParsingResult data={parseData} onShowAST={onShowAST} />}
|
{ !loading && parseData &&
|
||||||
|
<ParsingResult
|
||||||
|
data={parseData}
|
||||||
|
onShowAST={onShowAST}
|
||||||
|
onShowError={onShowError}
|
||||||
|
/>}
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
import { useMemo, useRef } from 'react';
|
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, useSelection } from 'reagraph';
|
import { darkTheme, GraphCanvas, GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, lightTheme, useSelection } from 'reagraph';
|
||||||
|
|
||||||
import ConceptGraph from '../../components/Common/ConceptGraph';
|
import Button from '../../components/Common/Button';
|
||||||
|
import Checkbox from '../../components/Common/Checkbox';
|
||||||
import ConceptSelect from '../../components/Common/ConceptSelect';
|
import ConceptSelect from '../../components/Common/ConceptSelect';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||||
import { GraphLayoutSelector } from '../../utils/staticUI';
|
import { resources } from '../../utils/constants';
|
||||||
|
import { GraphLayoutSelector,mapLayoutLabels } from '../../utils/staticUI';
|
||||||
|
|
||||||
function EditorTermGraph() {
|
function EditorTermGraph() {
|
||||||
const { schema } = useRSForm();
|
const { schema } = useRSForm();
|
||||||
|
const { darkMode } = useConceptTheme();
|
||||||
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'forceatlas2');
|
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'forceatlas2');
|
||||||
|
const [ orbit, setOrbit ] = useState(false);
|
||||||
const graphRef = useRef<GraphCanvasRef | null>(null);
|
const graphRef = useRef<GraphCanvasRef | null>(null);
|
||||||
|
|
||||||
const nodes: GraphNode[] = useMemo(() => {
|
const nodes: GraphNode[] = useMemo(() => {
|
||||||
|
@ -37,6 +42,11 @@ function EditorTermGraph() {
|
||||||
return result;
|
return result;
|
||||||
}, [schema?.graph]);
|
}, [schema?.graph]);
|
||||||
|
|
||||||
|
const handleCenter = useCallback(() => {
|
||||||
|
graphRef.current?.resetControls();
|
||||||
|
graphRef.current?.centerGraph();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
selections, actives,
|
selections, actives,
|
||||||
onNodeClick,
|
onNodeClick,
|
||||||
|
@ -44,44 +54,58 @@ function EditorTermGraph() {
|
||||||
onNodePointerOver,
|
onNodePointerOver,
|
||||||
onNodePointerOut
|
onNodePointerOut
|
||||||
} = useSelection({
|
} = useSelection({
|
||||||
type: 'multi', // 'single' | 'multi' | 'multiModifier'
|
|
||||||
ref: graphRef,
|
ref: graphRef,
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
|
type: 'multi', // 'single' | 'multi' | 'multiModifier'
|
||||||
pathSelectionType: 'all',
|
pathSelectionType: 'all',
|
||||||
focusOnSelect: 'singleOnly'
|
focusOnSelect: false
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (<>
|
||||||
<div>
|
|
||||||
<div className='relative w-full'>
|
<div className='relative w-full'>
|
||||||
<div className='absolute top-0 left-0 z-20 px-3 py-2'>
|
<div className='absolute top-0 left-0 z-20 px-3 py-2 w-[12rem] flex flex-col gap-2'>
|
||||||
<ConceptSelect
|
<ConceptSelect
|
||||||
className='w-[10rem]'
|
|
||||||
options={GraphLayoutSelector}
|
options={GraphLayoutSelector}
|
||||||
placeholder='Выберите тип'
|
placeholder='Выберите тип'
|
||||||
values={layout ? [{ value: layout, label: String(layout) }] : []}
|
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []}
|
||||||
onChange={data => { data && setLayout(data[0].value); }}
|
onChange={data => { setLayout(data.length > 0 ? data[0].value : GraphLayoutSelector[0].value); }}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='Анимация вращения'
|
||||||
|
widthClass='w-full'
|
||||||
|
value={orbit}
|
||||||
|
onChange={ event => setOrbit(event.target.checked) }/>
|
||||||
|
<Button
|
||||||
|
text='Центрировать'
|
||||||
|
dense
|
||||||
|
onClick={handleCenter}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ConceptGraph ref={graphRef}
|
<div className='flex-wrap w-full h-full overflow-auto'>
|
||||||
sizeClass='w-[1240px] h-[800px] 2xl:w-[1880px] 2xl:h-[800px]'
|
<div className='relative w-[1240px] h-[800px] 2xl:w-[1880px] 2xl:h-[800px]'>
|
||||||
|
<GraphCanvas
|
||||||
|
draggable
|
||||||
|
ref={graphRef}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
draggable
|
|
||||||
layoutType={layout}
|
layoutType={layout}
|
||||||
selections={selections}
|
selections={selections}
|
||||||
actives={actives}
|
actives={actives}
|
||||||
onCanvasClick={onCanvasClick}
|
|
||||||
onNodeClick={onNodeClick}
|
onNodeClick={onNodeClick}
|
||||||
|
onCanvasClick={onCanvasClick}
|
||||||
|
defaultNodeSize={5}
|
||||||
onNodePointerOver={onNodePointerOver}
|
onNodePointerOver={onNodePointerOver}
|
||||||
onNodePointerOut={onNodePointerOut}
|
onNodePointerOut={onNodePointerOut}
|
||||||
// sizingType="default" // 'none' | 'pagerank' | 'centrality' | 'attribute' | 'default';
|
cameraMode={ orbit ? 'orbit' : layout.includes('3d') ? 'rotate' : 'pan'}
|
||||||
cameraMode={ layout.includes('3d') ? 'rotate' : 'pan'}
|
layoutOverrides={ layout.includes('tree') ? { nodeLevelRatio: 1 } : undefined }
|
||||||
|
labelFontUrl={resources.graph_font}
|
||||||
|
theme={darkMode ? darkTheme : lightTheme}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
</>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import ConceptTab from '../../components/Common/ConceptTab';
|
||||||
import { Loader } from '../../components/Common/Loader';
|
import { Loader } from '../../components/Common/Loader';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||||
|
import { prefixes, timeout_updateUI } from '../../utils/constants';
|
||||||
import { CstType,type IConstituenta, ICstCreateData, SyntaxTree } from '../../utils/models';
|
import { CstType,type IConstituenta, ICstCreateData, SyntaxTree } from '../../utils/models';
|
||||||
import { createAliasFor } from '../../utils/staticUI';
|
import { createAliasFor } from '../../utils/staticUI';
|
||||||
import DlgCloneRSForm from './DlgCloneRSForm';
|
import DlgCloneRSForm from './DlgCloneRSForm';
|
||||||
|
@ -42,34 +43,44 @@ function RSTabs() {
|
||||||
const [showAST, setShowAST] = useState(false);
|
const [showAST, setShowAST] = useState(false);
|
||||||
|
|
||||||
const [defaultType, setDefaultType] = useState<CstType | undefined>(undefined);
|
const [defaultType, setDefaultType] = useState<CstType | undefined>(undefined);
|
||||||
const [insertPosition, setInsertPosition] = useState<number | undefined>(undefined);
|
const [insertWhere, setInsertWhere] = useState<number | undefined>(undefined);
|
||||||
const [showCreateCst, setShowCreateCst] = useState(false);
|
const [showCreateCst, setShowCreateCst] = useState(false);
|
||||||
|
|
||||||
const handleAddNew = useCallback(
|
const handleAddNew = useCallback(
|
||||||
(type: CstType) => {
|
(type: CstType, selectedCst?: number) => {
|
||||||
if (!schema?.items) {
|
if (!schema?.items) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data: ICstCreateData = {
|
const data: ICstCreateData = {
|
||||||
cst_type: type,
|
cst_type: type,
|
||||||
alias: createAliasFor(type, schema),
|
alias: createAliasFor(type, schema),
|
||||||
insert_after: insertPosition ?? null
|
insert_after: selectedCst ?? insertWhere ?? null
|
||||||
}
|
}
|
||||||
cstCreate(data, newCst => {
|
cstCreate(data, newCst => {
|
||||||
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
||||||
if (activeTab === RSTabsList.CST_EDIT) {
|
navigate(`/rsforms/${schema.id}?tab=${activeTab}&active=${newCst.id}`);
|
||||||
navigate(`/rsforms/${schema.id}?tab=${RSTabsList.CST_EDIT}&active=${newCst.id}`);
|
if (activeTab === RSTabsList.CST_EDIT || activeTab == RSTabsList.CST_LIST) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: "end",
|
||||||
|
inline: "nearest"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, timeout_updateUI);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [schema, cstCreate, insertPosition, navigate, activeTab]);
|
}, [schema, cstCreate, insertWhere, navigate, activeTab]);
|
||||||
|
|
||||||
const onShowCreateCst = useCallback(
|
const onShowCreateCst = useCallback(
|
||||||
(position: number | undefined, type: CstType | undefined, skipDialog?: boolean) => {
|
(selectedID: number | undefined, type: CstType | undefined, skipDialog?: boolean) => {
|
||||||
if (skipDialog && type) {
|
if (skipDialog && type) {
|
||||||
handleAddNew(type);
|
handleAddNew(type, selectedID);
|
||||||
} else {
|
} else {
|
||||||
setDefaultType(type);
|
setDefaultType(type);
|
||||||
setInsertPosition(position);
|
setInsertWhere(selectedID);
|
||||||
setShowCreateCst(true);
|
setShowCreateCst(true);
|
||||||
}
|
}
|
||||||
}, [handleAddNew]);
|
}, [handleAddNew]);
|
||||||
|
|
|
@ -87,7 +87,7 @@ function RSTabsMenu({showUploadDialog, showCloneDialog}: RSTabsMenuProps) {
|
||||||
<DropdownButton onClick={handleDownload}>
|
<DropdownButton onClick={handleDownload}>
|
||||||
<div className='inline-flex items-center justify-start gap-2'>
|
<div className='inline-flex items-center justify-start gap-2'>
|
||||||
<DownloadIcon color='text-primary' size={4}/>
|
<DownloadIcon color='text-primary' size={4}/>
|
||||||
<p>Выгрузить файл Экстеор</p>
|
<p>Выгрузить в Экстеор</p>
|
||||||
</div>
|
</div>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
<DropdownButton disabled={!isEditable} onClick={handleUpload}>
|
<DropdownButton disabled={!isEditable} onClick={handleUpload}>
|
||||||
|
@ -123,9 +123,14 @@ function RSTabsMenu({showUploadDialog, showCloneDialog}: RSTabsMenuProps) {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
|
{(isOwned || user?.is_staff) &&
|
||||||
<DropdownButton onClick={toggleReadonly}>
|
<DropdownButton onClick={toggleReadonly}>
|
||||||
<Checkbox value={readonly} label='только чтение'/>
|
<Checkbox
|
||||||
</DropdownButton>
|
value={readonly}
|
||||||
|
label='Я — читатель!'
|
||||||
|
tooltip='Режим чтения'
|
||||||
|
/>
|
||||||
|
</DropdownButton>}
|
||||||
{user?.is_staff &&
|
{user?.is_staff &&
|
||||||
<DropdownButton onClick={toggleForceAdmin}>
|
<DropdownButton onClick={toggleForceAdmin}>
|
||||||
<Checkbox value={forceAdmin} label='режим администратора'/>
|
<Checkbox value={forceAdmin} label='режим администратора'/>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import ConceptTooltip from '../../../components/Common/ConceptTooltip';
|
import ConceptTooltip from '../../../components/Common/ConceptTooltip';
|
||||||
import { IConstituenta } from '../../../utils/models';
|
import { IConstituenta } from '../../../utils/models';
|
||||||
|
import { getTypeLabel } from '../../../utils/staticUI';
|
||||||
|
|
||||||
interface ConstituentaTooltipProps {
|
interface ConstituentaTooltipProps {
|
||||||
data: IConstituenta
|
data: IConstituenta
|
||||||
|
@ -10,11 +11,11 @@ function ConstituentaTooltip({ data, anchor }: ConstituentaTooltipProps) {
|
||||||
return (
|
return (
|
||||||
<ConceptTooltip
|
<ConceptTooltip
|
||||||
anchorSelect={anchor}
|
anchorSelect={anchor}
|
||||||
className='max-w-[20rem] min-w-[20rem]'
|
className='max-w-[25rem] min-w-[25rem]'
|
||||||
>
|
>
|
||||||
<h1>Конституента {data.alias}</h1>
|
<h1>Конституента {data.alias}</h1>
|
||||||
<p><b>Типизация: </b>{data.parse.typification}</p>
|
<p><b>Типизация: </b>{getTypeLabel(data)}</p>
|
||||||
<p><b>Тремин: </b>{data.term.resolved || data.term.raw}</p>
|
<p><b>Термин: </b>{data.term.resolved || data.term.raw}</p>
|
||||||
{data.definition.formal && <p><b>Выражение: </b>{data.definition.formal}</p>}
|
{data.definition.formal && <p><b>Выражение: </b>{data.definition.formal}</p>}
|
||||||
{data.definition.text.resolved && <p><b>Определение: </b>{data.definition.text.resolved}</p>}
|
{data.definition.text.resolved && <p><b>Определение: </b>{data.definition.text.resolved}</p>}
|
||||||
{data.convention && <p><b>Конвенция: </b>{data.convention}</p>}
|
{data.convention && <p><b>Конвенция: </b>{data.convention}</p>}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { IExpressionParse, SyntaxTree } from '../../../utils/models';
|
import { IExpressionParse, IRSErrorDescription, SyntaxTree } from '../../../utils/models';
|
||||||
import { getRSErrorMessage, getRSErrorPrefix } from '../../../utils/staticUI';
|
import { getRSErrorMessage, getRSErrorPrefix } from '../../../utils/staticUI';
|
||||||
|
|
||||||
interface ParsingResultProps {
|
interface ParsingResultProps {
|
||||||
data: IExpressionParse
|
data: IExpressionParse
|
||||||
onShowAST: (ast: SyntaxTree) => void
|
onShowAST: (ast: SyntaxTree) => void
|
||||||
|
onShowError: (error: IRSErrorDescription) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function ParsingResult({ data, onShowAST }: ParsingResultProps) {
|
function ParsingResult({ data, onShowAST, onShowError }: ParsingResultProps) {
|
||||||
const errorCount = data.errors.reduce((total, error) => (error.isCritical ? total + 1 : total), 0);
|
const errorCount = data.errors.reduce((total, error) => (error.isCritical ? total + 1 : total), 0);
|
||||||
const warningsCount = data.errors.length - errorCount;
|
const warningsCount = data.errors.length - errorCount;
|
||||||
|
|
||||||
|
@ -19,9 +20,9 @@ function ParsingResult({ data, onShowAST }: ParsingResultProps) {
|
||||||
<p>Ошибок: <b>{errorCount}</b> | Предупреждений: <b>{warningsCount}</b></p>
|
<p>Ошибок: <b>{errorCount}</b> | Предупреждений: <b>{warningsCount}</b></p>
|
||||||
{data.errors.map(error => {
|
{data.errors.map(error => {
|
||||||
return (
|
return (
|
||||||
<p className='text-red'>
|
<p className='cursor-pointer text-red' onClick={() => onShowError(error)}>
|
||||||
<span className='font-semibold'>{error.isCritical ? 'Ошибка' : 'Предупреждение'} {getRSErrorPrefix(error)}: </span>
|
<span className='mr-1 font-semibold underline'>{error.isCritical ? 'Ошибка' : 'Предупреждение'} {getRSErrorPrefix(error)}:</span>
|
||||||
<span>{getRSErrorMessage(error)}</span>
|
<span> {getRSErrorMessage(error)}</span>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models';
|
import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models';
|
||||||
import { getStatusInfo } from '../../../utils/staticUI';
|
import { mapStatusInfo } from '../../../utils/staticUI';
|
||||||
|
|
||||||
interface StatusBarProps {
|
interface StatusBarProps {
|
||||||
isModified?: boolean
|
isModified?: boolean
|
||||||
|
@ -21,7 +21,7 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
|
||||||
return inferStatus(constituenta?.parse?.status, constituenta?.parse?.valueClass);
|
return inferStatus(constituenta?.parse?.status, constituenta?.parse?.valueClass);
|
||||||
}, [isModified, constituenta, parseData]);
|
}, [isModified, constituenta, parseData]);
|
||||||
|
|
||||||
const data = getStatusInfo(status);
|
const data = mapStatusInfo.get(status)!;
|
||||||
return (
|
return (
|
||||||
<div title={data.tooltip}
|
<div title={data.tooltip}
|
||||||
className={'min-h-[2rem] min-w-[6rem] font-semibold inline-flex border rounded-lg items-center justify-center align-middle ' + data.color}>
|
className={'min-h-[2rem] min-w-[6rem] font-semibold inline-flex border rounded-lg items-center justify-center align-middle ' + data.color}>
|
||||||
|
|
|
@ -3,9 +3,11 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import Checkbox from '../../../components/Common/Checkbox';
|
import Checkbox from '../../../components/Common/Checkbox';
|
||||||
import ConceptDataTable from '../../../components/Common/ConceptDataTable';
|
import ConceptDataTable from '../../../components/Common/ConceptDataTable';
|
||||||
import { useRSForm } from '../../../context/RSFormContext';
|
import { useRSForm } from '../../../context/RSFormContext';
|
||||||
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||||
import useLocalStorage from '../../../hooks/useLocalStorage';
|
import useLocalStorage from '../../../hooks/useLocalStorage';
|
||||||
|
import { prefixes } from '../../../utils/constants';
|
||||||
import { CstType, extractGlobals,type IConstituenta, matchConstituenta } from '../../../utils/models';
|
import { CstType, extractGlobals,type IConstituenta, matchConstituenta } from '../../../utils/models';
|
||||||
import { getMockConstituenta } from '../../../utils/staticUI';
|
import { getMockConstituenta, mapStatusInfo } from '../../../utils/staticUI';
|
||||||
import ConstituentaTooltip from './ConstituentaTooltip';
|
import ConstituentaTooltip from './ConstituentaTooltip';
|
||||||
|
|
||||||
interface ViewSideConstituentsProps {
|
interface ViewSideConstituentsProps {
|
||||||
|
@ -13,7 +15,8 @@ interface ViewSideConstituentsProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ViewSideConstituents({ expression }: ViewSideConstituentsProps) {
|
function ViewSideConstituents({ expression }: ViewSideConstituentsProps) {
|
||||||
const { schema, setActiveID } = useRSForm();
|
const { darkMode } = useConceptTheme();
|
||||||
|
const { schema, setActiveID, activeID } = useRSForm();
|
||||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
||||||
const [filterText, setFilterText] = useLocalStorage('side-filter-text', '')
|
const [filterText, setFilterText] = useLocalStorage('side-filter-text', '')
|
||||||
const [onlyExpression, setOnlyExpression] = useLocalStorage('side-filter-flag', false);
|
const [onlyExpression, setOnlyExpression] = useLocalStorage('side-filter-flag', false);
|
||||||
|
@ -52,6 +55,16 @@ function ViewSideConstituents({ expression }: ViewSideConstituentsProps) {
|
||||||
if (cst.id > 0) setActiveID(cst.id);
|
if (cst.id > 0) setActiveID(cst.id);
|
||||||
}, [setActiveID]);
|
}, [setActiveID]);
|
||||||
|
|
||||||
|
const conditionalRowStyles = useMemo(() =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
when: (cst: IConstituenta) => cst.id === activeID,
|
||||||
|
style: {
|
||||||
|
backgroundColor: darkMode ? '#0068b3' : '#def1ff',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
], [activeID, darkMode]);
|
||||||
|
|
||||||
const columns = useMemo(() =>
|
const columns = useMemo(() =>
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -63,13 +76,19 @@ function ViewSideConstituents({ expression }: ViewSideConstituentsProps) {
|
||||||
name: 'ID',
|
name: 'ID',
|
||||||
id: 'alias',
|
id: 'alias',
|
||||||
cell: (cst: IConstituenta) => {
|
cell: (cst: IConstituenta) => {
|
||||||
return (<div>
|
const info = mapStatusInfo.get(cst.status)!;
|
||||||
<span id={cst.alias}>{cst.alias}</span>
|
return (<>
|
||||||
<ConstituentaTooltip data={cst} anchor={`#${cst.alias}`} />
|
<div
|
||||||
</div>);
|
id={`${prefixes.cst_list}${cst.alias}`}
|
||||||
|
className={`w-full rounded-md text-center ${info.color}`}
|
||||||
|
>
|
||||||
|
{cst.alias}
|
||||||
|
</div>
|
||||||
|
<ConstituentaTooltip data={cst} anchor={`#${prefixes.cst_list}${cst.alias}`} />
|
||||||
|
</>);
|
||||||
},
|
},
|
||||||
width: '62px',
|
width: '65px',
|
||||||
maxWidth: '62px',
|
maxWidth: '65px',
|
||||||
conditionalCellStyles: [
|
conditionalCellStyles: [
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.id <= 0,
|
when: (cst: IConstituenta) => cst.id <= 0,
|
||||||
|
@ -132,6 +151,7 @@ function ViewSideConstituents({ expression }: ViewSideConstituentsProps) {
|
||||||
<ConceptDataTable
|
<ConceptDataTable
|
||||||
data={filteredData}
|
data={filteredData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
conditionalRowStyles={conditionalRowStyles}
|
||||||
keyField='id'
|
keyField='id'
|
||||||
noContextMenu
|
noContextMenu
|
||||||
noDataComponent={<span className='flex flex-col justify-center p-2 text-center'>
|
noDataComponent={<span className='flex flex-col justify-center p-2 text-center'>
|
||||||
|
|
|
@ -101,9 +101,9 @@ export class TextWrapper implements IManagedText {
|
||||||
|
|
||||||
insertToken(tokenID: TokenID): boolean {
|
insertToken(tokenID: TokenID): boolean {
|
||||||
switch (tokenID) {
|
switch (tokenID) {
|
||||||
case TokenID.NT_DECLARATIVE_EXPR: this.envelopeWith('D{ξ∈X1 | ', '}'); return true;
|
case TokenID.NT_DECLARATIVE_EXPR: this.envelopeWith('D{ξ∈X1 | P1[ξ]', '}'); return true;
|
||||||
case TokenID.NT_IMPERATIVE_EXPR: this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; ', '}'); return true;
|
case TokenID.NT_IMPERATIVE_EXPR: this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; P1[σ, γ]', '}'); return true;
|
||||||
case TokenID.NT_RECURSIVE_FULL: this.envelopeWith('R{ ξ:=D1 | 1=1 | ', '}'); return true;
|
case TokenID.NT_RECURSIVE_FULL: this.envelopeWith('R{ ξ:=D1 | F1[ξ]≠∅ | ξ∪F1[ξ]', '}'); return true;
|
||||||
case TokenID.BIGPR: this.envelopeWith('Pr1(', ')'); return true;
|
case TokenID.BIGPR: this.envelopeWith('Pr1(', ')'); return true;
|
||||||
case TokenID.SMALLPR: this.envelopeWith('pr1(', ')'); return true;
|
case TokenID.SMALLPR: this.envelopeWith('pr1(', ')'); return true;
|
||||||
case TokenID.FILTER: this.envelopeWith('Fi1[α](', ')'); return true;
|
case TokenID.FILTER: this.envelopeWith('Fi1[α](', ')'); return true;
|
||||||
|
|
|
@ -71,7 +71,7 @@ export function postLogout(request: FrontAction) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postSignup(request: IFrontRequest<IUserSignupData, IUserProfile>) {
|
export function postSignup(request: FrontExchange<IUserSignupData, IUserProfile>) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
title: 'Register user',
|
title: 'Register user',
|
||||||
endpoint: `${config.url.AUTH}signup`,
|
endpoint: `${config.url.AUTH}signup`,
|
||||||
|
|
|
@ -13,6 +13,9 @@ const dev = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const config = process.env.NODE_ENV === 'production' ? prod : dev;
|
||||||
|
export const timeout_updateUI = 100;
|
||||||
|
|
||||||
export const urls = {
|
export const urls = {
|
||||||
concept: 'https://www.acconcept.ru/',
|
concept: 'https://www.acconcept.ru/',
|
||||||
exteor32: 'https://drive.google.com/open?id=1IHlMMwaYlAUBRSxU1RU_hXM5mFU9-oyK&usp=drive_fs',
|
exteor32: 'https://drive.google.com/open?id=1IHlMMwaYlAUBRSxU1RU_hXM5mFU9-oyK&usp=drive_fs',
|
||||||
|
@ -26,4 +29,6 @@ export const resources = {
|
||||||
graph_font: 'https://ey2pz3.csb.app/NotoSansSC-Regular.ttf'
|
graph_font: 'https://ey2pz3.csb.app/NotoSansSC-Regular.ttf'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = process.env.NODE_ENV === 'production' ? prod : dev;
|
export const prefixes = {
|
||||||
|
cst_list: 'cst-list-'
|
||||||
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ export interface IUserSignupData extends Omit<IUser, 'is_staff' | 'id'> {
|
||||||
password: string
|
password: string
|
||||||
password2: string
|
password2: string
|
||||||
}
|
}
|
||||||
export interface IUserProfile extends Omit<IUser, 'is_staff'> {}
|
|
||||||
export interface IUserUpdateData extends Omit<IUser, 'is_staff' | 'id'> {}
|
export interface IUserUpdateData extends Omit<IUser, 'is_staff' | 'id'> {}
|
||||||
|
export interface IUserProfile extends Omit<IUser, 'is_staff'> {}
|
||||||
export interface IUserInfo extends Omit<IUserProfile, 'email'> {}
|
export interface IUserInfo extends Omit<IUserProfile, 'email'> {}
|
||||||
|
|
||||||
// ======== RS Parsing ============
|
// ======== RS Parsing ============
|
||||||
|
@ -104,6 +104,7 @@ export interface IConstituenta {
|
||||||
resolved: string
|
resolved: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
status: ExpressionStatus
|
||||||
parse: {
|
parse: {
|
||||||
status: ParsingStatus
|
status: ParsingStatus
|
||||||
valueClass: ValueClass
|
valueClass: ValueClass
|
||||||
|
@ -216,10 +217,6 @@ export enum ExpressionStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Model functions =================
|
// ========== Model functions =================
|
||||||
export function extractGlobals(expression: string): Set<string> {
|
|
||||||
return new Set(expression.match(/[XCSADFPT]\d+/g) ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function inferStatus(parse?: ParsingStatus, value?: ValueClass): ExpressionStatus {
|
export function inferStatus(parse?: ParsingStatus, value?: ValueClass): ExpressionStatus {
|
||||||
if (!parse || !value) {
|
if (!parse || !value) {
|
||||||
return ExpressionStatus.UNDEFINED;
|
return ExpressionStatus.UNDEFINED;
|
||||||
|
@ -239,6 +236,10 @@ export function inferStatus(parse?: ParsingStatus, value?: ValueClass): Expressi
|
||||||
return ExpressionStatus.VERIFIED
|
return ExpressionStatus.VERIFIED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractGlobals(expression: string): Set<string> {
|
||||||
|
return new Set(expression.match(/[XCSADFPT]\d+/g) ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
export function LoadRSFormData(schema: IRSFormData): IRSForm {
|
export function LoadRSFormData(schema: IRSFormData): IRSForm {
|
||||||
const result = schema as IRSForm
|
const result = schema as IRSForm
|
||||||
result.graph = new Graph;
|
result.graph = new Graph;
|
||||||
|
@ -293,6 +294,7 @@ export function LoadRSFormData(schema: IRSFormData): IRSForm {
|
||||||
(sum, cst) => sum + (cst.cstType === CstType.THEOREM ? 1 : 0), 0)
|
(sum, cst) => sum + (cst.cstType === CstType.THEOREM ? 1 : 0), 0)
|
||||||
}
|
}
|
||||||
result.items.forEach(cst => {
|
result.items.forEach(cst => {
|
||||||
|
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
|
||||||
result.graph.addNode(cst.id);
|
result.graph.addNode(cst.id);
|
||||||
const dependencies = extractGlobals(cst.definition.formal);
|
const dependencies = extractGlobals(cst.definition.formal);
|
||||||
dependencies.forEach(value => {
|
dependencies.forEach(value => {
|
||||||
|
|
|
@ -24,6 +24,23 @@ export function getTypeLabel(cst: IConstituenta) {
|
||||||
return 'Логический';
|
return 'Логический';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCstTypePrefix(type: CstType) {
|
||||||
|
switch (type) {
|
||||||
|
case CstType.BASE: return 'X';
|
||||||
|
case CstType.CONSTANT: return 'C';
|
||||||
|
case CstType.STRUCTURED: return 'S';
|
||||||
|
case CstType.AXIOM: return 'A';
|
||||||
|
case CstType.TERM: return 'D';
|
||||||
|
case CstType.FUNCTION: return 'F';
|
||||||
|
case CstType.PREDICATE: return 'P';
|
||||||
|
case CstType.THEOREM: return 'T';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCstExpressionPrefix(cst: IConstituenta): string {
|
||||||
|
return cst.alias + (cst.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
||||||
|
}
|
||||||
|
|
||||||
export function getRSButtonData(id: TokenID): IRSButtonData {
|
export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case TokenID.BOOLEAN: return {
|
case TokenID.BOOLEAN: return {
|
||||||
|
@ -96,11 +113,11 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
};
|
};
|
||||||
case TokenID.IN: return {
|
case TokenID.IN: return {
|
||||||
text: '∈',
|
text: '∈',
|
||||||
tooltip: 'быть элементом (принадлежит) [Alt + \']'
|
tooltip: 'быть элементом (принадлежит) [Alt + 1]'
|
||||||
};
|
};
|
||||||
case TokenID.NOTIN: return {
|
case TokenID.NOTIN: return {
|
||||||
text: '∉',
|
text: '∉',
|
||||||
tooltip: 'не принадлежит [Alt + Shift + \']'
|
tooltip: 'не принадлежит [Alt + Shift + 1]'
|
||||||
};
|
};
|
||||||
case TokenID.SUBSET_OR_EQ: return {
|
case TokenID.SUBSET_OR_EQ: return {
|
||||||
text: '⊆',
|
text: '⊆',
|
||||||
|
@ -172,11 +189,11 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
||||||
};
|
};
|
||||||
case TokenID.PUNC_ASSIGN: return {
|
case TokenID.PUNC_ASSIGN: return {
|
||||||
text: ':=',
|
text: ':=',
|
||||||
tooltip: 'присвоение (императивный синтаксис)'
|
tooltip: 'присвоение (императивный синтаксис) [Alt + Shift + 6]'
|
||||||
};
|
};
|
||||||
case TokenID.PUNC_ITERATE: return {
|
case TokenID.PUNC_ITERATE: return {
|
||||||
text: ':∈',
|
text: ':∈',
|
||||||
tooltip: 'перебор элементов множества (императивный синтаксис)'
|
tooltip: 'перебор элементов множества (императивный синтаксис) [Alt + 6]'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@ -218,74 +235,70 @@ export const CstTypeSelector = (Object.values(CstType)).map(
|
||||||
return { value: type, label: getCstTypeLabel(type) };
|
return { value: type, label: getCstTypeLabel(type) };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const mapLayoutLabels: Map<string, string> = new Map([
|
||||||
|
['forceatlas2', 'Атлас 2D'],
|
||||||
|
['forceDirected2d', 'Силы 2D'],
|
||||||
|
['forceDirected3d', 'Силы 3D'],
|
||||||
|
['treeTd2d', 'ДеревоВерт 2D'],
|
||||||
|
['treeTd3d', 'ДеревоВерт 3D'],
|
||||||
|
['treeLr2d', 'ДеревоГор 2D'],
|
||||||
|
['treeLr3d', 'ДеревоГор 3D'],
|
||||||
|
['radialOut2d', 'Радиальная 2D'],
|
||||||
|
['radialOut3d', 'Радиальная 3D'],
|
||||||
|
['circular2d', 'Круговая'],
|
||||||
|
['hierarchicalTd', 'ИерархияВерт'],
|
||||||
|
['hierarchicalLr', 'ИерархияГор'],
|
||||||
|
['nooverlap', 'Без перекрытия']
|
||||||
|
]);
|
||||||
|
|
||||||
export const GraphLayoutSelector: {value: LayoutTypes, label: string}[] = [
|
export const GraphLayoutSelector: {value: LayoutTypes, label: string}[] = [
|
||||||
{ value: 'forceatlas2', label: 'forceatlas2'},
|
{ value: 'forceatlas2', label: 'Атлас 2D'},
|
||||||
{ value: 'nooverlap', label: 'nooverlap'},
|
{ value: 'forceDirected2d', label: 'Силы 2D'},
|
||||||
{ value: 'forceDirected2d', label: 'forceDirected2d'},
|
{ value: 'forceDirected3d', label: 'Силы 3D'},
|
||||||
{ value: 'forceDirected3d', label: 'forceDirected3d'},
|
{ value: 'treeTd2d', label: 'ДеревоВ 2D'},
|
||||||
{ value: 'circular2d', label: 'circular2d'},
|
{ value: 'treeTd3d', label: 'ДеревоВ 3D'},
|
||||||
{ value: 'treeTd2d', label: 'treeTd2d'},
|
{ value: 'treeLr2d', label: 'ДеревоГ 2D'},
|
||||||
{ value: 'treeTd3d', label: 'treeTd3d'},
|
{ value: 'treeLr3d', label: 'ДеревоГ 3D'},
|
||||||
{ value: 'treeLr2d', label: 'treeLr2d'},
|
{ value: 'radialOut2d', label: 'Радиальная 2D'},
|
||||||
{ value: 'treeLr3d', label: 'treeLr3d'},
|
{ value: 'radialOut3d', label: 'Радиальная 3D'},
|
||||||
{ value: 'radialOut2d', label: 'radialOut2d'},
|
// { value: 'circular2d', label: 'circular2d'},
|
||||||
{ value: 'radialOut3d', label: 'radialOut3d'},
|
// { value: 'nooverlap', label: 'nooverlap'},
|
||||||
// { value: 'hierarchicalTd', label: 'hierarchicalTd'},
|
// { value: 'hierarchicalTd', label: 'hierarchicalTd'},
|
||||||
// { value: 'hierarchicalLr', label: 'hierarchicalLr'}
|
// { value: 'hierarchicalLr', label: 'hierarchicalLr'}
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getCstTypePrefix(type: CstType) {
|
export const mapStatusInfo: Map<ExpressionStatus, IStatusInfo> = new Map([
|
||||||
switch (type) {
|
[ ExpressionStatus.VERIFIED, {
|
||||||
case CstType.BASE: return 'X';
|
|
||||||
case CstType.CONSTANT: return 'C';
|
|
||||||
case CstType.STRUCTURED: return 'S';
|
|
||||||
case CstType.AXIOM: return 'A';
|
|
||||||
case CstType.TERM: return 'D';
|
|
||||||
case CstType.FUNCTION: return 'F';
|
|
||||||
case CstType.PREDICATE: return 'P';
|
|
||||||
case CstType.THEOREM: return 'T';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStatusInfo(status?: ExpressionStatus): IStatusInfo {
|
|
||||||
switch (status) {
|
|
||||||
case ExpressionStatus.UNDEFINED: return {
|
|
||||||
text: 'N/A',
|
|
||||||
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
|
|
||||||
tooltip: 'произошла ошибка при проверке выражения'
|
|
||||||
};
|
|
||||||
case ExpressionStatus.UNKNOWN: return {
|
|
||||||
text: 'неизв',
|
|
||||||
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
|
|
||||||
tooltip: 'требует проверки выражения'
|
|
||||||
};
|
|
||||||
case ExpressionStatus.INCORRECT: return {
|
|
||||||
text: 'ошибка',
|
|
||||||
color: 'bg-[#ff8080] dark:bg-[#800000]',
|
|
||||||
tooltip: 'ошибка в выражении'
|
|
||||||
};
|
|
||||||
case ExpressionStatus.INCALCULABLE: return {
|
|
||||||
text: 'невыч',
|
|
||||||
color: 'bg-[#ffbb80] dark:bg-[#964600]',
|
|
||||||
tooltip: 'выражение не вычислимо (экспоненциальная сложность)'
|
|
||||||
};
|
|
||||||
case ExpressionStatus.PROPERTY: return {
|
|
||||||
text: 'св-во',
|
|
||||||
color: 'bg-[#a5e9fa] dark:bg-[#36899e]',
|
|
||||||
tooltip: 'можно проверить принадлежность, но нельзя получить значение'
|
|
||||||
};
|
|
||||||
case ExpressionStatus.VERIFIED: return {
|
|
||||||
text: 'ок',
|
text: 'ок',
|
||||||
color: 'bg-[#aaff80] dark:bg-[#2b8000]',
|
color: 'bg-[#aaff80] dark:bg-[#2b8000]',
|
||||||
tooltip: 'выражение корректно и вычислимо'
|
tooltip: 'выражение корректно и вычислимо'
|
||||||
};
|
}],
|
||||||
}
|
[ ExpressionStatus.INCORRECT, {
|
||||||
return {
|
text: 'ошибка',
|
||||||
text: 'undefined',
|
color: 'bg-[#ffc9c9] dark:bg-[#592b2b]',
|
||||||
color: '',
|
tooltip: 'ошибка в выражении'
|
||||||
tooltip: '!ERROR!'
|
}],
|
||||||
};
|
[ ExpressionStatus.INCALCULABLE, {
|
||||||
}
|
text: 'невыч',
|
||||||
|
color: 'bg-[#ffbb80] dark:bg-[#964600]',
|
||||||
|
tooltip: 'выражение не вычислимо (экспоненциальная сложность)'
|
||||||
|
}],
|
||||||
|
[ ExpressionStatus.PROPERTY, {
|
||||||
|
text: 'св-во',
|
||||||
|
color: 'bg-[#a5e9fa] dark:bg-[#36899e]',
|
||||||
|
tooltip: 'можно проверить принадлежность, но нельзя получить значение'
|
||||||
|
}],
|
||||||
|
[ ExpressionStatus.UNKNOWN, {
|
||||||
|
text: 'неизв',
|
||||||
|
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
|
||||||
|
tooltip: 'требует проверки выражения'
|
||||||
|
}],
|
||||||
|
[ ExpressionStatus.UNDEFINED, {
|
||||||
|
text: 'N/A',
|
||||||
|
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
|
||||||
|
tooltip: 'произошла ошибка при проверке выражения'
|
||||||
|
}],
|
||||||
|
]);
|
||||||
|
|
||||||
export function createAliasFor(type: CstType, schema: IRSForm): string {
|
export function createAliasFor(type: CstType, schema: IRSForm): string {
|
||||||
const prefix = getCstTypePrefix(type);
|
const prefix = getCstTypePrefix(type);
|
||||||
|
@ -320,6 +333,7 @@ export function getMockConstituenta(id: number, alias: string, type: CstType, co
|
||||||
resolved: ''
|
resolved: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
status: ExpressionStatus.INCORRECT,
|
||||||
parse: {
|
parse: {
|
||||||
status: ParsingStatus.INCORRECT,
|
status: ParsingStatus.INCORRECT,
|
||||||
valueClass: ValueClass.INVALID,
|
valueClass: ValueClass.INVALID,
|
||||||
|
@ -365,7 +379,7 @@ export function getRSErrorMessage(error: IRSErrorDescription): string {
|
||||||
case RSErrorType.localDoubleDeclare:
|
case RSErrorType.localDoubleDeclare:
|
||||||
return `Предупреждение! Повторное объявление локальной переменной ${error.params[0]}`;
|
return `Предупреждение! Повторное объявление локальной переменной ${error.params[0]}`;
|
||||||
case RSErrorType.localNotUsed:
|
case RSErrorType.localNotUsed:
|
||||||
return `Предупреждение! Переменная объявлена но не использована: ${error.params[0]}`;
|
return `Предупреждение! Переменная объявлена, но не использована: ${error.params[0]}`;
|
||||||
case RSErrorType.localShadowing:
|
case RSErrorType.localShadowing:
|
||||||
return `Повторное объявление переменной: ${error.params[0]}`;
|
return `Повторное объявление переменной: ${error.params[0]}`;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user