This commit is contained in:
Ulle9 2023-08-16 17:55:06 +03:00
commit 1f9e761565
25 changed files with 964 additions and 251 deletions

View File

@ -6,7 +6,6 @@ For more specific TODOs see comments in code
- home page
- manuals
- текстовый модуль для разрешения отсылок
- компонент для форматирования в редакторе текста (формальное выражения + отсылки в тексте)
- блок нотификаций пользователей
- блок синтеза
- блок организации библиотеки моделей
@ -17,8 +16,10 @@ For more specific TODOs see comments in code
- Use migtation/fixtures to provide initial data for testing
- USe migtation/fixtures to load example common data
- create custom Select component
- reload react-data-table-component
[deployment]
- HTTPS
- database backup daemon
- logs collection
- status dashboard for servers

View File

@ -22,14 +22,14 @@ function App () {
const scrollWindowSize = useMemo(
() => {
return !noNavigation ?
'max-h-[calc(100vh-4.5rem)]'
: 'max-h-[100vh]';
'calc(100vh - 4.5rem)'
: '100vh';
}, [noNavigation]);
const mainSize = useMemo(
() => {
return !noNavigation ?
'min-h-[calc(100vh-12rem)]'
: 'min-h-[calc(100vh-8rem)] ';
'calc(100vh - 12rem)'
: '100vh';
}, [noNavigation]);
return (
@ -42,8 +42,8 @@ function App () {
pauseOnFocusLoss={false}
/>
<div className={`${scrollWindowSize} overflow-auto`}>
<main className={`${mainSize} px-2`}>
<div className='overflow-auto' style={{maxHeight: scrollWindowSize}}>
<main className='px-2' style={{minHeight: mainSize}}>
<Routes>
<Route path='/' element={ <HomePage/>} />
@ -60,7 +60,7 @@ function App () {
<Route path='*' element={ <NotFoundPage/>} />
</Routes>
</main>
<Footer />
{!noNavigation && <Footer />}
</div>
</div>
);

View File

@ -27,7 +27,7 @@ function Button({
disabled={disabled ?? loading}
onClick={onClick}
title={tooltip}
className={`inline-flex items-center gap-2 align-middle justify-center ${padding} ${borderClass} ${colorClass} ${widthClass} ${cursor}`}
className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${borderClass} ${colorClass} ${widthClass} ${cursor}`}
{...props}
>
{icon && <span>{icon}</span>}

View File

@ -8,7 +8,7 @@ interface SubmitButtonProps {
function SubmitButton({ text = 'ОК', icon, disabled, loading = false }: SubmitButtonProps) {
return (
<button type='submit'
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-bold disabled:cursor-not-allowed border rounded clr-btn-primary ${loading ? ' cursor-progress' : ''}`}
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-bold select-none disabled:cursor-not-allowed border rounded clr-btn-primary ${loading ? ' cursor-progress' : ''}`}
disabled={disabled ?? loading}
>
{icon && <span>{icon}</span>}

View File

@ -10,7 +10,7 @@ function Footer() {
<div className='px-4 underline whitespace-nowrap'>
<Link to='/manuals' tabIndex={-1}>Справка</Link> <br/>
<Link to='/library?filter=common' tabIndex={-1}>Библиотека КС</Link> <br/>
<a href={urls.gitrepo} className='flex'>
<a href={urls.gitrepo} className='flex' tabIndex={-1}>
<GithubIcon />
<span className='ml-1'>Репозиторий</span>
</a>

View File

@ -0,0 +1,22 @@
import { IConstituenta } from '../../utils/models';
import { getCstTypificationLabel } from '../../utils/staticUI';
interface InfoConstituentaProps
extends React.HTMLAttributes<HTMLDivElement> {
data: IConstituenta
}
function InfoConstituenta({ data, ...props }: InfoConstituentaProps) {
return (
<div {...props}>
<h1>Конституента {data.alias}</h1>
<p><b>Типизация: </b>{getCstTypificationLabel(data)}</p>
<p><b>Термин: </b>{data.term.resolved || data.term.raw}</p>
{data.definition.formal && <p><b>Выражение: </b>{data.definition.formal}</p>}
{data.definition.text.resolved && <p><b>Определение: </b>{data.definition.text.resolved}</p>}
{data.convention && <p><b>Конвенция: </b>{data.convention}</p>}
</div>
);
}
export default InfoConstituenta;

View File

@ -0,0 +1,29 @@
import { prefixes } from '../../utils/constants';
import { mapCstClassInfo } from '../../utils/staticUI';
interface InfoCstClassProps {
title?: string
}
function InfoCstClass({ title }: InfoCstClassProps) {
return (
<div className='flex flex-col gap-1'>
{ title && <h1>{title}</h1>}
{ [... mapCstClassInfo.values()].map(
(info, index) => {
return (
<p key={`${prefixes.cst_status_list}${index}`}>
<span className={`px-1 inline-block font-semibold min-w-[6.5rem] text-center border ${info.color}`}>
{info.text}
</span>
<span> - </span>
<span>
{info.tooltip}
</span>
</p>);
})}
</div>
);
}
export default InfoCstClass;

View File

@ -0,0 +1,29 @@
import { prefixes } from '../../utils/constants';
import { mapStatusInfo } from '../../utils/staticUI';
interface InfoCstStatusProps {
title?: string
}
function InfoCstStatus({ title }: InfoCstStatusProps) {
return (
<div className='flex flex-col gap-1'>
{ title && <h1>{title}</h1>}
{ [... mapStatusInfo.values()].map(
(info, index) => {
return (
<p key={`${prefixes.cst_status_list}${index}`}>
<span className={`px-1 inline-block font-semibold min-w-[4rem] text-center border ${info.color}`}>
{info.text}
</span>
<span> - </span>
<span>
{info.tooltip}
</span>
</p>);
})}
</div>
);
}
export default InfoCstStatus;

View File

@ -1,6 +1,5 @@
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext';
import { useConceptTheme } from '../../context/ThemeContext';
import { EducationIcon, LibraryIcon } from '../Icons';
import Logo from './Logo'
@ -10,7 +9,6 @@ import UserMenu from './UserMenu';
import UserTools from './UserTools';
function Navigation () {
const { user } = useAuth();
const navigate = useNavigate();
const { noNavigation, toggleNoNavigation } = useConceptTheme();
@ -37,12 +35,12 @@ function Navigation () {
</button>}
{!noNavigation &&
<div className='pr-6 pl-2 py-2.5 h-[4rem] flex items-center justify-between border-b-2 clr-nav rounded-none'>
<div className='flex items-start justify-start '>
<div className='flex items-start justify-start select-none'>
<Logo title='КонцептПортал' />
<TopSearch />
</div>
<div className='flex items-center'>
{user && <UserTools/>}
<UserTools/>
<div className='flex items-center pl-2'>
<NavigationButton icon={<LibraryIcon />} description='Общие схемы' onClick={navigateCommon} />
<NavigationButton icon={<EducationIcon />} description='Справка' onClick={navigateHelp} />

View File

@ -1,15 +1,17 @@
interface NavigationButtonProps {
id?: string
icon: React.ReactNode
description: string
description?: string
colorClass?: string
onClick: () => void
onClick?: () => void
}
const defaultColors = 'text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white'
function NavigationButton({ icon, description, colorClass = defaultColors, onClick }: NavigationButtonProps) {
function NavigationButton({ id, icon, description, colorClass = defaultColors, onClick }: NavigationButtonProps) {
return (
<button title={description}
<button id={id}
title={description}
type='button'
onClick={onClick}
className={'min-w-fit p-2 mr-1 focus:ring-4 rounded-lg focus:ring-gray-300 dark:focus:ring-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 ' + colorClass}

View File

@ -2,6 +2,8 @@ import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
import { useAuth } from '../../context/AuthContext';
import ConceptTooltip from '../Common/ConceptTooltip';
import TextURL from '../Common/TextURL';
import { BellIcon, PlusIcon, SquaresIcon } from '../Icons';
import NavigationButton from './NavigationButton';
@ -19,13 +21,30 @@ function UserTools() {
return (
<div className='flex items-center px-2 border-r-2 border-gray-400 dark:border-gray-300'>
<span>
<NavigationButton description='Новая схема'
{ user &&
<NavigationButton
description='Новая схема'
icon={<PlusIcon />}
onClick={navigateCreateRSForm}
colorClass='text-blue-500 hover:text-blue-700 dark:text-orange-500 dark:hover:text-orange-300'
/>
/>}
{ !user &&
<NavigationButton id='items-nav-help'
description='Невозможно создать новую схему. Войдите в систему'
icon={<PlusIcon />}
/>}
<ConceptTooltip anchorSelect='#items-nav-help' clickable>
<div className='flex flex-col cursor-default'>
<p>Создание и редактирование концептуальных схем</p>
<p>доступно только <b>зарегистрированным пользователям</b></p>
<div className='flex flex-col self-center'>
<TextURL text='Войти в систему' href='/login'/>
<TextURL text='Зарегистрироваться' href='/signup'/>
</div>
</div>
</ConceptTooltip>
</span>
<NavigationButton icon={<SquaresIcon />} description='Мои схемы' onClick={navigateMyWork} />
{ user && <NavigationButton icon={<SquaresIcon />} description='Мои схемы' onClick={navigateMyWork} /> }
{ user && user.is_staff && <NavigationButton icon={<BellIcon />} description='Уведомления' onClick={handleNotifications} />}
</div>
);

View File

@ -76,10 +76,10 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
const isEditable = useMemo(
() => {
return (
!loading && !isReadonly &&
!loading && !processing && !isReadonly &&
((isOwned || (isForceAdmin && user?.is_staff)) ?? false)
)
}, [user?.is_staff, isReadonly, isForceAdmin, isOwned, loading])
}, [user?.is_staff, isReadonly, isForceAdmin, isOwned, loading, processing])
const isTracking = useMemo(
() => {

View File

@ -0,0 +1,153 @@
import { useLayoutEffect, useState } from 'react';
import Checkbox from '../../components/Common/Checkbox';
import Modal from '../../components/Common/Modal';
import { CstType } from '../../utils/models';
import { getCstTypeLabel } from '../../utils/staticUI';
import { GraphEditorParams } from './EditorTermGraph';
interface DlgGraphOptionsProps {
hideWindow: () => void
initial: GraphEditorParams
onConfirm: (params: GraphEditorParams) => void
}
function DlgGraphOptions({ hideWindow, initial, onConfirm }:DlgGraphOptionsProps) {
const [ noHermits, setNoHermits ] = useState(true);
const [ noTransitive, setNoTransitive ] = useState(false);
const [ noTemplates, setNoTemplates ] = useState(true);
const [ noTerms, setNoTerms ] = useState(true);
const [ allowBase, setAllowBase ] = useState(true);
const [ allowStruct, setAllowStruct ] = useState(true);
const [ allowTerm, setAllowTerm ] = useState(true);
const [ allowAxiom, setAllowAxiom ] = useState(true);
const [ allowFunction, setAllowFunction ] = useState(true);
const [ allowPredicate, setAllowPredicate ] = useState(true);
const [ allowConstant, setAllowConstant ] = useState(true);
const [ allowTheorem, setAllowTheorem ] = useState(true);
function getParams() {
return {
noHermits: noHermits,
noTransitive: noTransitive,
noTemplates: noTemplates,
noTerms: noTerms,
allowBase: allowBase,
allowStruct: allowStruct,
allowTerm: allowTerm,
allowAxiom: allowAxiom,
allowFunction: allowFunction,
allowPredicate: allowPredicate,
allowConstant: allowConstant,
allowTheorem: allowTheorem
}
}
const handleSubmit = () => {
hideWindow();
onConfirm(getParams());
};
useLayoutEffect(() => {
setNoHermits(initial.noHermits);
setNoTransitive(initial.noTransitive);
setNoTemplates(initial.noTemplates);
setNoTerms(initial.noTerms);
setAllowBase(initial.allowBase);
setAllowStruct(initial.allowStruct);
setAllowTerm(initial.allowTerm);
setAllowAxiom(initial.allowAxiom);
setAllowFunction(initial.allowFunction);
setAllowPredicate(initial.allowPredicate);
setAllowConstant(initial.allowConstant);
setAllowTheorem(initial.allowTheorem);
}, [initial]);
return (
<Modal
hideWindow={hideWindow}
title='Настройки графа термов'
onSubmit={handleSubmit}
canSubmit
submitText='Применить'
>
<div className='flex gap-2'>
<div className='flex flex-col'>
<h1>Преобразования</h1>
<Checkbox
label='Скрыть текст'
tooltip='Не отображать термины'
value={noTerms}
onChange={ event => setNoTerms(event.target.checked) }
/>
<Checkbox
label='Скрыть несвязанные'
tooltip='Неиспользуемые конституенты'
value={noHermits}
onChange={ event => setNoHermits(event.target.checked) }
/>
<Checkbox
label='Скрыть шаблоны'
tooltip='Терм-функции и предикат-функции с параметризованными аргументами'
value={noTemplates}
onChange={ event => setNoTemplates(event.target.checked) }
/>
<Checkbox
label='Транзитивная редукция'
tooltip='Удалить связи, образующие транзитивные пути в графе'
value={noTransitive}
onChange={ event => setNoTransitive(event.target.checked) }
/>
</div>
<div className='flex flex-col'>
<h1>Типы конституент</h1>
<Checkbox
label={getCstTypeLabel(CstType.BASE)}
value={allowBase}
onChange={ event => setAllowBase(event.target.checked) }
/>
<Checkbox
label={getCstTypeLabel(CstType.STRUCTURED)}
value={allowStruct}
onChange={ event => setAllowStruct(event.target.checked) }
/>
<Checkbox
label={getCstTypeLabel(CstType.TERM)}
value={allowTerm}
onChange={ event => setAllowTerm(event.target.checked) }
/>
<Checkbox
label={getCstTypeLabel(CstType.AXIOM)}
value={allowAxiom}
onChange={ event => setAllowAxiom(event.target.checked) }
/>
<Checkbox
label={getCstTypeLabel(CstType.FUNCTION)}
value={allowFunction}
onChange={ event => setAllowFunction(event.target.checked) }
/>
<Checkbox
label={getCstTypeLabel(CstType.PREDICATE)}
value={allowPredicate}
onChange={ event => setAllowPredicate(event.target.checked) }
/>
<Checkbox
label={getCstTypeLabel(CstType.CONSTANT)}
value={allowConstant}
onChange={ event => setAllowConstant(event.target.checked) }
/>
<Checkbox
label={getCstTypeLabel(CstType.THEOREM)}
value={allowTheorem}
onChange={ event => setAllowTheorem(event.target.checked) }
/>
</div>
</div>
</Modal>
);
}
export default DlgGraphOptions;

View File

@ -6,10 +6,11 @@ import Divider from '../../components/Common/Divider';
import MiniButton from '../../components/Common/MiniButton';
import SubmitButton from '../../components/Common/SubmitButton';
import TextArea from '../../components/Common/TextArea';
import CstStatusInfo from '../../components/Help/InfoCstStatus';
import { DumpBinIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext';
import { type CstType, EditMode, ICstUpdateData, SyntaxTree } from '../../utils/models';
import { getCstTypeLabel, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI';
import { getCstTypeLabel, getCstTypificationLabel } from '../../utils/staticUI';
import EditorRSExpression from './EditorRSExpression';
import ViewSideConstituents from './elements/ViewSideConstituents';
@ -145,19 +146,19 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDe
</span>
</div>
<div className='flex justify-end'>
<MiniButton
tooltip='Создать конституенты после данной'
disabled={!isEnabled}
onClick={handleCreateCst}
icon={<SmallPlusIcon size={5} color={isEnabled ? 'text-green' : ''} />}
/>
<MiniButton
tooltip='Удалить редактируемую конституенту'
disabled={!isEnabled}
onClick={handleDelete}
icon={<DumpBinIcon size={5} color={isEnabled ? 'text-red' : ''} />}
/>
<div id='cst-help' className='flex items-center ml-[0.25rem]'>
<MiniButton
tooltip='Создать конституенты после данной'
disabled={!isEnabled}
onClick={handleCreateCst}
icon={<SmallPlusIcon size={5} color={isEnabled ? 'text-green' : ''} />}
/>
<div id='cst-help' className='flex items-center ml-[6px]'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#cst-help'>
@ -173,18 +174,7 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDe
<p>- при наведении на ID конституенты отображаются ее атрибуты</p>
<p>- столбец "Описание" содержит один из непустых текстовых атрибутов</p>
<Divider margins='mt-2' />
<h1>Статусы</h1>
{ [... mapStatusInfo.values()].map((info, index) => {
return (<p className='py-1' key={`status-info-${index}`}>
<span className={`inline-block font-semibold min-w-[4rem] text-center border ${info.color}`}>
{info.text}
</span>
<span> - </span>
<span>
{info.tooltip}
</span>
</p>);
})}
<CstStatusInfo title='Статусы' />
</div>
</ConceptTooltip>
</div>
@ -241,7 +231,7 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDe
/>
</div>
</form>
<div className='self-stretch border w-full pb-1'>
<div className='self-stretch w-full pb-1 border'>
<ViewSideConstituents
expression={expression}
baseHeight={UNFOLDED_HEIGHT}

View File

@ -5,6 +5,7 @@ import Button from '../../components/Common/Button';
import ConceptDataTable from '../../components/Common/ConceptDataTable';
import ConceptTooltip from '../../components/Common/ConceptTooltip';
import Divider from '../../components/Common/Divider';
import CstStatusInfo from '../../components/Help/InfoCstStatus';
import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext';
@ -156,8 +157,8 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
setSelected(selectedRows.map(cst => cst.id));
}, [setSelected]);
const columns = useMemo(() =>
[
const columns = useMemo(
() => [
{
name: 'ID',
id: 'id',
@ -241,13 +242,12 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
reorder: true,
hide: 1800
}
], []
);
], []);
return (
<div className='w-full'>
<div
className={'flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] clr-app' +
className={'flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] select-none clr-app' +
(!noNavigation ? ' sticky z-10 top-[0rem]' : ' sticky z-10 top-[0rem]')}
>
<div className='mr-3 whitespace-nowrap'>
@ -316,18 +316,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
<p><b>Delete</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>);
})}
<CstStatusInfo title='Статусы' />
</div>
</ConceptTooltip>
</div>

View File

@ -1,29 +1,117 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { darkTheme, GraphCanvas, GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, lightTheme, Sphere, useSelection } from 'reagraph';
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { darkTheme, GraphCanvas, GraphCanvasRef, GraphEdge,
GraphNode, LayoutTypes, lightTheme, Sphere, useSelection
} from 'reagraph';
import Button from '../../components/Common/Button';
import Checkbox from '../../components/Common/Checkbox';
import ConceptSelect from '../../components/Common/ConceptSelect';
import { ArrowsRotateIcon } from '../../components/Icons';
import ConceptTooltip from '../../components/Common/ConceptTooltip';
import Divider from '../../components/Common/Divider';
import MiniButton from '../../components/Common/MiniButton';
import InfoConstituenta from '../../components/Help/InfoConstituenta';
import InfoCstClass from '../../components/Help/InfoCstClass';
import CstStatusInfo from '../../components/Help/InfoCstStatus';
import { ArrowsRotateIcon, DumpBinIcon, FilterCogIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext';
import useLocalStorage from '../../hooks/useLocalStorage';
import { resources } from '../../utils/constants';
import { prefixes, resources } from '../../utils/constants';
import { Graph } from '../../utils/Graph';
import { GraphLayoutSelector,mapLayoutLabels } from '../../utils/staticUI';
import { CstType, IConstituenta } from '../../utils/models';
import { getCstClassColor, getCstStatusColor,
GraphColoringSelector, GraphLayoutSelector,
mapColoringLabels, mapLayoutLabels
} from '../../utils/staticUI';
import DlgGraphOptions from './DlgGraphOptions';
import ConstituentaTooltip from './elements/ConstituentaTooltip';
function EditorTermGraph() {
const { schema } = useRSForm();
export type ColoringScheme = 'none' | 'status' | 'type';
const TREE_SIZE_MILESTONE = 50;
function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, darkMode: boolean): string {
if (coloringScheme === 'type') {
return getCstClassColor(cst.cstClass, darkMode);
}
if (coloringScheme === 'status') {
return getCstStatusColor(cst.status, darkMode);
}
return (darkMode ? '#7a8c9e' :'#7ca0ab');
}
export interface GraphEditorParams {
noHermits: boolean
noTransitive: boolean
noTemplates: boolean
noTerms: boolean
allowBase: boolean
allowStruct: boolean
allowTerm: boolean
allowAxiom: boolean
allowFunction: boolean
allowPredicate: boolean
allowConstant: boolean
allowTheorem: boolean
}
interface EditorTermGraphProps {
onOpenEdit: (cstID: number) => void
onCreateCst: (selectedID: number | undefined, type: CstType | undefined, skipDialog?: boolean) => void
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
}
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
const { schema, isEditable } = useRSForm();
const { darkMode, noNavigation } = useConceptTheme();
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d');
const [ filtered, setFiltered ] = useState<Graph>(new Graph());
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d');
const [ coloringScheme, setColoringScheme ] = useLocalStorage<ColoringScheme>('graph_coloring', 'none');
const [ orbit, setOrbit ] = useState(false);
const [ noHermits, setNoHermits ] = useLocalStorage('graph_no_hermits', true);
const [ noTransitive, setNoTransitive ] = useLocalStorage('graph_no_transitive', false);
const graphRef = useRef<GraphCanvasRef | null>(null);
const [ noTemplates, setNoTemplates ] = useLocalStorage('graph_no_templates', false);
const [ noTerms, setNoTerms ] = useLocalStorage('graph_no_terms', false);
const [ allowBase, setAllowBase ] = useLocalStorage('graph_allow_base', true);
const [ allowStruct, setAllowStruct ] = useLocalStorage('graph_allow_struct', true);
const [ allowTerm, setAllowTerm ] = useLocalStorage('graph_allow_term', true);
const [ allowAxiom, setAllowAxiom ] = useLocalStorage('graph_allow_axiom', true);
const [ allowFunction, setAllowFunction ] = useLocalStorage('function', true);
const [ allowPredicate, setAllowPredicate ] = useLocalStorage('graph_allow_predicate', true);
const [ allowConstant, setAllowConstant ] = useLocalStorage('graph_allow_constant', true);
const [ allowTheorem, setAllowTheorem ] = useLocalStorage('graph_allow_theorem', true);
useEffect(() => {
const [ filtered, setFiltered ] = useState<Graph>(new Graph());
const [ dismissed, setDismissed ] = useState<number[]>([]);
const [ selectedDismissed, setSelectedDismissed ] = useState<number[]>([]);
const graphRef = useRef<GraphCanvasRef | null>(null);
const [showOptions, setShowOptions] = useState(false);
const [toggleUpdate, setToggleUpdate] = useState(false);
const [hoverID, setHoverID] = useState<string | undefined>(undefined);
const hoverCst = useMemo(
() => {
return schema?.items.find(cst => String(cst.id) == hoverID);
}, [schema?.items, hoverID]);
const is3D = useMemo(() => layout.includes('3d'), [layout]);
const allowedTypes: CstType[] = useMemo(
() => {
const result: CstType[] = [];
if (allowBase) result.push(CstType.BASE);
if (allowStruct) result.push(CstType.STRUCTURED);
if (allowTerm) result.push(CstType.TERM);
if (allowAxiom) result.push(CstType.AXIOM);
if (allowFunction) result.push(CstType.FUNCTION);
if (allowPredicate) result.push(CstType.PREDICATE);
if (allowConstant) result.push(CstType.CONSTANT);
if (allowTheorem) result.push(CstType.THEOREM);
return result;
}, [allowBase, allowStruct, allowTerm, allowAxiom, allowFunction, allowPredicate, allowConstant, allowTheorem]);
useLayoutEffect(
() => {
if (!schema) {
setFiltered(new Graph());
return;
@ -35,10 +123,46 @@ function EditorTermGraph() {
if (noTransitive) {
graph.transitiveReduction();
}
if (noTemplates) {
schema.items.forEach(cst => {
if (cst.isTemplate) {
graph.foldNode(cst.id);
}
});
}
if (allowedTypes.length < Object.values(CstType).length) {
schema.items.forEach(cst => {
if (!allowedTypes.includes(cst.cstType)) {
graph.foldNode(cst.id);
}
});
}
const newDismissed: number[] = [];
schema.items.forEach(cst => {
if (!graph.nodes.has(cst.id)) {
newDismissed.push(cst.id);
}
});
setFiltered(graph);
}, [schema, noHermits, noTransitive]);
setDismissed(newDismissed);
setSelectedDismissed([]);
setHoverID(undefined);
}, [schema, noHermits, noTransitive, noTemplates, allowedTypes, toggleUpdate]);
const nodes: GraphNode[] = useMemo(() => {
function toggleDismissed(cstID: number) {
setSelectedDismissed(prev => {
const index = prev.findIndex(id => cstID == id);
if (index !== -1) {
prev.splice(index, 1);
} else {
prev.push(cstID);
}
return [... prev];
});
}
const nodes: GraphNode[] = useMemo(
() => {
const result: GraphNode[] = [];
if (!schema) {
return result;
@ -48,14 +172,16 @@ function EditorTermGraph() {
if (cst) {
result.push({
id: String(node.id),
label: cst.term.resolved ? `${cst.alias}: ${cst.term.resolved}` : cst.alias
fill: getCstNodeColor(cst, coloringScheme, darkMode),
label: cst.term.resolved && !noTerms ? `${cst.alias}: ${cst.term.resolved}` : cst.alias
});
}
});
return result;
}, [schema, filtered.nodes]);
}, [schema, coloringScheme, filtered.nodes, darkMode, noTerms]);
const edges: GraphEdge[] = useMemo(() => {
const edges: GraphEdge[] = useMemo(
() => {
const result: GraphEdge[] = [];
let edgeID = 1;
filtered.nodes.forEach(source => {
@ -71,14 +197,10 @@ function EditorTermGraph() {
return result;
}, [filtered.nodes]);
const handleCenter = useCallback(() => {
graphRef.current?.resetControls();
graphRef.current?.centerGraph();
}, []);
const {
selections, actives,
onNodeClick,
clearSelections,
onCanvasClick,
onNodePointerOver,
onNodePointerOut
@ -87,52 +209,292 @@ function EditorTermGraph() {
nodes,
edges,
type: 'multi', // 'single' | 'multi' | 'multiModifier'
pathSelectionType: 'all',
pathSelectionType: 'out',
pathHoverType: 'all',
focusOnSelect: false
});
const canvasSize = !noNavigation ?
'w-[1240px] h-[736px] 2xl:w-[1880px] 2xl:h-[746px]'
: 'w-[1240px] h-[800px] 2xl:w-[1880px] 2xl:h-[810px]';
const allSelected: string[] = useMemo(
() => {
return [ ... selectedDismissed.map(id => String(id)), ... selections];
}, [selectedDismissed, selections]);
const nothingSelected = useMemo(() => allSelected.length === 0, [allSelected]);
return (<>
<div className='relative w-full'>
<div className='absolute top-0 left-0 z-20 py-2 w-[12rem] flex flex-col'>
<div className='flex items-center gap-1 w-[15rem]'>
const handleRecreate = useCallback(
() => {
graphRef.current?.resetControls();
graphRef.current?.centerGraph();
}, []);
const handleHoverIn = useCallback(
(node: GraphNode) => {
setHoverID(node.id);
if (onNodePointerOver) onNodePointerOver(node);
}, [onNodePointerOver]);
const handleHoverOut = useCallback(
(node: GraphNode) => {
setHoverID(undefined);
if (onNodePointerOut) onNodePointerOut(node);
}, [onNodePointerOut]);
const handleNodeClick = useCallback(
(node: GraphNode) => {
if (selections.includes(node.id)) {
onOpenEdit(Number(node.id));
return;
}
if (onNodeClick) onNodeClick(node);
}, [onNodeClick, selections, onOpenEdit]);
const handleCanvasClick = useCallback(
(event: MouseEvent) => {
setSelectedDismissed([]);
if (onCanvasClick) onCanvasClick(event);
}, [onCanvasClick]);
// Implement hotkeys for editing
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
console.log(event);
if (!isEditable) {
return;
}
if (event.key === 'Delete' && allSelected.length > 0) {
event.preventDefault();
handleDeleteCst();
return;
}
}
function handleCreateCst() {
if (!schema) {
return;
}
const selectedPosition = allSelected.reduce((prev, cstID) => {
const position = schema.items.findIndex(cst => cst.id === Number(cstID));
return Math.max(position, prev);
}, -1);
const insert_where = selectedPosition >= 0 ? schema.items[selectedPosition].id : undefined;
onCreateCst(insert_where, undefined);
}
function handleDeleteCst() {
if (!schema) {
return;
}
onDeleteCst([... allSelected.map(id => Number(id))], () => {
clearSelections();
setDismissed([]);
setSelectedDismissed([]);
setToggleUpdate(prev => !prev);
});
}
function getOptions() {
return {
noHermits: noHermits,
noTemplates: noTemplates,
noTransitive: noTransitive,
noTerms: noTerms,
allowBase: allowBase,
allowStruct: allowStruct,
allowTerm: allowTerm,
allowAxiom: allowAxiom,
allowFunction: allowFunction,
allowPredicate: allowPredicate,
allowConstant: allowConstant,
allowTheorem: allowTheorem
}
}
const handleChangeOptions = useCallback(
(params: GraphEditorParams) => {
setNoHermits(params.noHermits);
setNoTransitive(params.noTransitive);
setNoTemplates(params.noTemplates);
setNoTerms(params.noTerms);
setAllowBase(params.allowBase);
setAllowStruct(params.allowStruct);
setAllowTerm(params.allowTerm);
setAllowAxiom(params.allowAxiom);
setAllowFunction(params.allowFunction);
setAllowPredicate(params.allowPredicate);
setAllowConstant(params.allowConstant);
setAllowTheorem(params.allowTheorem);
}, [setNoHermits, setNoTransitive, setNoTemplates,
setAllowBase, setAllowStruct, setAllowTerm, setAllowAxiom, setAllowFunction,
setAllowPredicate, setAllowConstant, setAllowTheorem, setNoTerms]);
const canvasWidth = useMemo(
() => {
return 'calc(100vw - 14.6rem)';
}, []);
const canvasHeight = useMemo(
() => {
return !noNavigation ?
'calc(100vh - 13rem)'
: 'calc(100vh - 2rem)';
}, [noNavigation]);
const dismissedStyle = useCallback(
(cstID: number) => {
return selectedDismissed.includes(cstID) ? {outlineWidth: '2px', outlineStyle: 'solid'}: {};
}, [selectedDismissed]);
return (
<div className='flex justify-between w-full outline-none' tabIndex={0} onKeyDown={handleKeyDown}>
{showOptions &&
<DlgGraphOptions
hideWindow={() => setShowOptions(false)}
initial={getOptions()}
onConfirm={handleChangeOptions}
/>}
<div className='flex flex-col py-2 border-t border-r max-w-[12.44rem] pr-2 text-sm select-none' style={{height: canvasHeight}}>
{hoverCst &&
<div className='relative'>
<InfoConstituenta
data={hoverCst}
className='absolute top-0 left-0 z-50 w-[25rem] min-h-[11rem] overflow-y-auto border h-fit clr-app px-3'
/>
</div>}
<div className='flex items-center justify-between'>
<div className='mr-3 whitespace-nowrap'>
Выбраны
<span className='ml-1'>
<b>{allSelected.length}</b> из {schema?.stats?.count_all ?? 0}
</span>
</div>
<div>
<MiniButton
tooltip='Удалить выбранные'
icon={<DumpBinIcon color={!nothingSelected ? 'text-red' : ''} size={5}/>}
disabled={!isEditable || nothingSelected}
onClick={handleDeleteCst}
/>
<MiniButton
tooltip='Новая конституента'
icon={<SmallPlusIcon color='text-green' size={5}/>}
disabled={!isEditable}
onClick={handleCreateCst}
/>
</div>
</div>
<div className='flex items-center w-full gap-1'>
<Button
icon={<ArrowsRotateIcon size={8} />}
icon={<FilterCogIcon size={7} />}
dense
tooltip='Центрировать изображение'
tooltip='Настройки фильтрации узлов и связей'
widthClass='h-full'
onClick={handleCenter}
onClick={() => setShowOptions(true)}
/>
<ConceptSelect
className='min-w-[9.5rem]'
className='min-w-[9.3rem]'
options={GraphColoringSelector}
searchable={false}
placeholder='Выберите цвет'
values={coloringScheme ? [{ value: coloringScheme, label: mapColoringLabels.get(coloringScheme) }] : []}
onChange={data => { setColoringScheme(data.length > 0 ? data[0].value : GraphColoringSelector[0].value); }}
/>
</div>
<ConceptSelect
className='mt-1 w-fit'
options={GraphLayoutSelector}
searchable={false}
placeholder='Выберите тип'
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []}
onChange={data => { setLayout(data.length > 0 ? data[0].value : GraphLayoutSelector[0].value); }}
/>
</div>
<Checkbox
label='Анимация вращения'
value={orbit}
onChange={ event => setOrbit(event.target.checked) }
/>
<Checkbox
label='Удалить несвязанные'
value={noHermits}
onChange={ event => setNoHermits(event.target.checked) }
label='Скрыть текст'
value={noTerms}
onChange={ event => setNoTerms(event.target.checked) }
/>
<Checkbox
label='Транзитивная редукция'
value={noTransitive}
onChange={ event => setNoTransitive(event.target.checked) }
/>
<Checkbox
disabled={!is3D}
label='Анимация вращения'
value={orbit}
onChange={ event => setOrbit(event.target.checked) }
/>
<Divider margins='mt-3 mb-2' />
<div className='flex flex-col overflow-y-auto'>
<p className='text-center'><b>Скрытые конституенты</b></p>
<div className='flex flex-wrap justify-center gap-2 py-2'>
{dismissed.map(cstID => {
const cst = schema!.items.find(cst => cst.id === cstID)!;
const adjustedColoring = coloringScheme === 'none' ? 'status': coloringScheme;
return (<>
<div
key={`${cst.alias}`}
id={`${prefixes.cst_list}${cst.alias}`}
className='w-fit min-w-[3rem] rounded-md text-center cursor-pointer'
style={ { backgroundColor: getCstNodeColor(cst, adjustedColoring, darkMode), ...dismissedStyle(cstID) }}
onClick={() => toggleDismissed(cstID)}
onDoubleClick={() => onOpenEdit(cstID)}
>
{cst.alias}
</div>
<ConstituentaTooltip
data={cst}
anchor={`#${prefixes.cst_list}${cst.alias}`}
/>
</>);
})}
</div>
</div>
<div className='flex-wrap w-full h-full overflow-auto'>
<div className={`relative border-t border-r ${canvasSize}`}>
</div>
<div className='w-full h-full overflow-auto'>
<div
className='relative border-t border-r'
style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}}
>
<div className='relative top-0 right-0 z-10 flex m-2 flex-start'>
<div className='px-1 py-1' id='items-graph-help' >
<HelpIcon color='text-primary' size={5} />
</div>
<MiniButton
icon={<ArrowsRotateIcon size={5} />}
tooltip='Пересоздать граф'
onClick={handleRecreate}
/>
</div>
<ConceptTooltip anchorSelect='#items-graph-help'>
<div className='flex'>
<div>
<h1>Настройка графа</h1>
<p><b>Цвет</b> - выбор правила покраски узлов</p>
<p><b>Граф</b> - выбор модели расположения узлов</p>
<p><b>Удалить несвязанные</b> - скрыть одинокие вершины</p>
<p><b>Транзитивная редукция</b> - скрыть транзитивные пути</p>
<Divider margins='mt-2' />
<CstStatusInfo title='Статусы конституент' />
</div>
<Divider vertical margins='mx-3' />
<div>
<h1>Горячие клавиши</h1>
<p><b>Клик на конституенту</b> - выделение, включая скрытые конституенты</p>
<p><b>Довйной клик</b> - редактирование конституенты</p>
<p><b>Delete</b> - удалить выбранные</p>
<Divider margins='mt-2' />
<InfoCstClass title='Классы конституент' />
</div>
</div>
</ConceptTooltip>
<GraphCanvas
draggable
ref={graphRef}
@ -141,13 +503,15 @@ function EditorTermGraph() {
layoutType={layout}
selections={selections}
actives={actives}
onNodeClick={onNodeClick}
onCanvasClick={onCanvasClick}
defaultNodeSize={5}
onNodePointerOver={onNodePointerOver}
onNodePointerOut={onNodePointerOut}
cameraMode={ orbit ? 'orbit' : layout.includes('3d') ? 'rotate' : 'pan'}
layoutOverrides={ layout.includes('tree') ? { nodeLevelRatio: schema && schema?.items.length < 50 ? 3 : 1 } : undefined }
onNodeClick={handleNodeClick}
onCanvasClick={handleCanvasClick}
onNodePointerOver={handleHoverIn}
onNodePointerOut={handleHoverOut}
cameraMode={ orbit ? 'orbit' : is3D ? 'rotate' : 'pan'}
layoutOverrides={
layout.includes('tree') ? { nodeLevelRatio: filtered.nodes.size < TREE_SIZE_MILESTONE ? 3 : 1 }
: undefined
}
labelFontUrl={resources.graph_font}
theme={darkMode ? darkTheme : lightTheme}
renderNode={({ node, ...rest }) => (
@ -156,7 +520,7 @@ function EditorTermGraph() {
/>
</div>
</div>
</>);
</div>);
}

View File

@ -40,7 +40,7 @@ function RSTabs() {
const { destroySchema } = useLibrary();
const [activeTab, setActiveTab] = useState(RSTabsList.CARD);
const [activeID, setActiveID] = useState<number | undefined>(undefined)
const [activeID, setActiveID] = useState<number | undefined>(undefined);
const [showUpload, setShowUpload] = useState(false);
const [showClone, setShowClone] = useState(false);
@ -223,7 +223,7 @@ function RSTabs() {
defaultFocus={true}
selectedTabClassName='font-bold'
>
<TabList className='flex items-start w-fit clr-bg-pop'>
<TabList className='flex items-start select-none w-fit clr-bg-pop'>
<RSTabsMenu
onDestroy={onDestroySchema}
showCloneDialog={() => setShowClone(true)}
@ -264,7 +264,11 @@ function RSTabs() {
</TabPanel>
<TabPanel>
<EditorTermGraph />
<EditorTermGraph
onOpenEdit={onOpenCst}
onCreateCst={promptCreateCst}
onDeleteCst={promptDeleteCst}
/>
</TabPanel>
</Tabs>
</>}

View File

@ -1,6 +1,6 @@
import ConceptTooltip from '../../../components/Common/ConceptTooltip';
import InfoConstituenta from '../../../components/Help/InfoConstituenta';
import { IConstituenta } from '../../../utils/models';
import { getCstTypificationLabel } from '../../../utils/staticUI';
interface ConstituentaTooltipProps {
data: IConstituenta
@ -13,12 +13,7 @@ function ConstituentaTooltip({ data, anchor }: ConstituentaTooltipProps) {
anchorSelect={anchor}
className='max-w-[25rem] min-w-[25rem]'
>
<h1>Конституента {data.alias}</h1>
<p><b>Типизация: </b>{getCstTypificationLabel(data)}</p>
<p><b>Термин: </b>{data.term.resolved || data.term.raw}</p>
{data.definition.formal && <p><b>Выражение: </b>{data.definition.formal}</p>}
{data.definition.text.resolved && <p><b>Определение: </b>{data.definition.text.resolved}</p>}
{data.convention && <p><b>Конвенция: </b>{data.convention}</p>}
<InfoConstituenta data={data} />
</ConceptTooltip>
);
}

View File

@ -24,7 +24,7 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
const data = mapStatusInfo.get(status)!;
return (
<div title={data.tooltip}
className={`text-sm h-[1.6rem] w-[10rem] font-semibold inline-flex border items-center justify-center align-middle ${data.color}`}>
className={`text-sm h-[1.6rem] w-[10rem] font-semibold inline-flex border items-center select-none justify-center align-middle ${data.color}`}>
Статус: [ {data.text} ]
</div>
)

View File

@ -158,7 +158,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
onChange={setFilterMatch}
/>
<input type='text'
className='w-full px-2 bg-white outline-none hover:text-clip clr-bg-pop clr-border'
className='w-full px-2 bg-white outline-none select-none hover:text-clip clr-bg-pop clr-border'
placeholder='наберите текст фильтра'
value={filterText}
onChange={event => setFilterText(event.target.value)}

View File

@ -45,6 +45,16 @@ describe('Testing Graph editing', () => {
expect(graph.hasEdge(4, 1)).toBeFalsy();
});
test('folding node redirectes edges', () => {
const graph = new Graph([[1, 3], [2, 3], [3, 4], [3, 5], [3, 3]]);
graph.foldNode(3);
expect(graph.hasNode(3)).toBeFalsy();
expect(graph.hasEdge(1, 4)).toBeTruthy();
expect(graph.hasEdge(1, 5)).toBeTruthy();
expect(graph.hasEdge(2, 4)).toBeTruthy();
expect(graph.hasEdge(2, 5)).toBeTruthy();
});
test('removing isolated nodes', () => {
const graph = new Graph([[9, 1], [9, 2], [2, 1], [4, 3], [5, 9], [7], [8]]);
graph.removeIsolated()
@ -53,7 +63,7 @@ describe('Testing Graph editing', () => {
test('transitive reduction', () => {
const graph = new Graph([[1, 3], [1, 2], [2, 3]]);
graph.transitiveReduction()
graph.transitiveReduction();
expect(graph.hasEdge(1, 2)).toBeTruthy();
expect(graph.hasEdge(2, 3)).toBeTruthy();
expect(graph.hasEdge(1, 3)).toBeFalsy();

View File

@ -67,6 +67,10 @@ export class Graph {
return node;
}
hasNode(target: number): boolean {
return !!this.nodes.get(target);
}
removeNode(target: number): GraphNode | null {
const nodeToRemove = this.nodes.get(target);
if (!nodeToRemove) {
@ -80,6 +84,19 @@ export class Graph {
return nodeToRemove;
}
foldNode(target: number): GraphNode | null {
const nodeToRemove = this.nodes.get(target);
if (!nodeToRemove) {
return null;
}
nodeToRemove.inputs.forEach(input => {
nodeToRemove.outputs.forEach(output => {
this.addEdge(input, output);
})
});
return this.removeNode(target);
}
removeIsolated(): GraphNode[] {
const result: GraphNode[] = [];
this.nodes.forEach(node => {

View File

@ -28,5 +28,6 @@ export const resources = {
}
export const prefixes = {
cst_list: 'cst-list-'
cst_list: 'cst-list-',
cst_status_list: 'cst-status-list-'
}

View File

@ -101,6 +101,13 @@ export enum CstType {
THEOREM = 'theorem'
}
export enum CstClass {
BASIC = 'basic',
DERIVED = 'derived',
STATEMENT = 'statement',
TEMPLATE = 'template'
}
export interface IConstituenta {
id: number
alias: string
@ -118,7 +125,9 @@ export interface IConstituenta {
resolved: string
}
}
cstClass: CstClass
status: ExpressionStatus
isTemplate: boolean
parse: {
status: ParsingStatus
valueClass: ValueClass
@ -230,12 +239,12 @@ export enum EditMode {
// RSExpression status
export enum ExpressionStatus {
UNDEFINED = 0,
UNKNOWN,
INCORRECT,
INCALCULABLE,
PROPERTY,
VERIFIED
UNDEFINED = 'undefined',
UNKNOWN = 'unknown',
INCORRECT = 'incorrect',
INCALCULABLE = 'incalculable',
PROPERTY = 'property',
VERIFIED = 'verified'
}
// Dependency mode for schema analysis
@ -274,7 +283,28 @@ export function inferStatus(parse?: ParsingStatus, value?: ValueClass): Expressi
if (value === ValueClass.PROPERTY) {
return ExpressionStatus.PROPERTY;
}
return ExpressionStatus.VERIFIED
return ExpressionStatus.VERIFIED;
}
export function inferTemplate(expression: string): boolean {
const match = expression.match(/R\d+/g);
return (match && match?.length > 0) ?? false;
}
export function inferClass(type: CstType, isTemplate: boolean): CstClass {
if (isTemplate) {
return CstClass.TEMPLATE;
}
switch (type) {
case CstType.BASE: return CstClass.BASIC;
case CstType.CONSTANT: return CstClass.BASIC;
case CstType.STRUCTURED: return CstClass.BASIC;
case CstType.TERM: return CstClass.DERIVED;
case CstType.FUNCTION: return CstClass.DERIVED;
case CstType.AXIOM: return CstClass.STATEMENT;
case CstType.PREDICATE: return CstClass.DERIVED;
case CstType.THEOREM: return CstClass.STATEMENT;
}
}
export function extractGlobals(expression: string): Set<string> {
@ -336,6 +366,8 @@ export function LoadRSFormData(schema: IRSFormData): IRSForm {
}
result.items.forEach(cst => {
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
cst.isTemplate = inferTemplate(cst.definition.formal);
cst.cstClass = inferClass(cst.cstType, cst.isTemplate);
result.graph.addNode(cst.id);
const dependencies = extractGlobals(cst.definition.formal);
dependencies.forEach(value => {

View File

@ -1,7 +1,8 @@
import { LayoutTypes } from 'reagraph';
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
import { resolveErrorClass,RSErrorClass, RSErrorType, TokenID } from './enums';
import { CstMatchMode, CstType, DependencyMode,ExpressionStatus, IConstituenta,
import { CstClass, CstMatchMode, CstType, DependencyMode, ExpressionStatus, IConstituenta,
IFunctionArg,IRSErrorDescription, IRSForm,
ISyntaxTreeNode, ParsingStatus, ValueClass
} from './models';
@ -11,7 +12,7 @@ export interface IRSButtonData {
tooltip: string
}
export interface IStatusInfo {
export interface IFormatInfo {
text: string
color: string
tooltip: string
@ -250,22 +251,6 @@ export const CstTypeSelector = (Object.values(CstType)).map(
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 function getCstCompareLabel(mode: CstMatchMode): string {
switch(mode) {
case CstMatchMode.ALL: return 'везде';
@ -288,22 +273,61 @@ export function getDependencyLabel(mode: DependencyMode): string {
}
export const GraphLayoutSelector: {value: LayoutTypes, label: string}[] = [
{ value: 'forceatlas2', label: 'Атлас 2D'},
{ value: 'forceDirected2d', label: 'Силы 2D'},
{ value: 'forceDirected3d', label: 'Силы 3D'},
{ value: 'treeTd2d', label: 'ДеревоВ 2D'},
{ value: 'treeTd3d', label: 'ДеревоВ 3D'},
{ value: 'treeLr2d', label: 'ДеревоГ 2D'},
{ value: 'treeLr3d', label: 'ДеревоГ 3D'},
{ value: 'radialOut2d', label: 'Радиальная 2D'},
{ value: 'radialOut3d', label: 'Радиальная 3D'},
{ value: 'treeTd2d', label: 'Граф: ДеревоВ 2D'},
{ value: 'treeTd3d', label: 'Граф: ДеревоВ 3D'},
{ value: 'forceatlas2', label: 'Граф: Атлас 2D'},
{ value: 'forceDirected2d', label: 'Граф: Силы 2D'},
{ value: 'forceDirected3d', label: 'Граф: Силы 3D'},
{ value: 'treeLr2d', label: 'Граф: ДеревоГ 2D'},
{ value: 'treeLr3d', label: 'Граф: ДеревоГ 3D'},
{ value: 'radialOut2d', label: 'Граф: Радиальная 2D'},
{ value: 'radialOut3d', label: 'Граф: Радиальная 3D'},
// { value: 'circular2d', label: 'circular2d'},
// { value: 'nooverlap', label: 'nooverlap'},
// { value: 'hierarchicalTd', label: 'hierarchicalTd'},
// { value: 'hierarchicalLr', label: 'hierarchicalLr'}
];
export const mapStatusInfo: Map<ExpressionStatus, IStatusInfo> = new Map([
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 mapColoringLabels: Map<string, string> = new Map([
['none', 'Цвет: моно'],
['status', 'Цвет: статус'],
['type', 'Цвет: класс'],
]);
export const GraphColoringSelector: {value: ColoringScheme, label: string}[] = [
{ value: 'none', label: 'Цвет: моно'},
{ value: 'status', label: 'Цвет: статус'},
{ value: 'type', label: 'Цвет: класс'},
];
export function getCstStatusColor(status: ExpressionStatus, darkMode: boolean): string {
switch (status) {
case ExpressionStatus.VERIFIED: return darkMode ? '#2b8000': '#aaff80';
case ExpressionStatus.INCORRECT: return darkMode ? '#592b2b': '#ffc9c9';
case ExpressionStatus.INCALCULABLE: return darkMode ? '#964600': '#ffbb80';
case ExpressionStatus.PROPERTY: return darkMode ? '#36899e': '#a5e9fa';
case ExpressionStatus.UNKNOWN: return darkMode ? '#1e00b3': '#b3bdff';
case ExpressionStatus.UNDEFINED: return darkMode ? '#1e00b3': '#b3bdff';
}
}
export const mapStatusInfo: Map<ExpressionStatus, IFormatInfo> = new Map([
[ ExpressionStatus.VERIFIED, {
text: 'ок',
color: 'bg-[#aaff80] dark:bg-[#2b8000]',
@ -317,7 +341,7 @@ export const mapStatusInfo: Map<ExpressionStatus, IStatusInfo> = new Map([
[ ExpressionStatus.INCALCULABLE, {
text: 'невыч',
color: 'bg-[#ffbb80] dark:bg-[#964600]',
tooltip: 'выражение не вычислимо (экспоненциальная сложность)'
tooltip: 'выражение не вычислимо'
}],
[ ExpressionStatus.PROPERTY, {
text: 'св-во',
@ -336,6 +360,38 @@ export const mapStatusInfo: Map<ExpressionStatus, IStatusInfo> = new Map([
}],
]);
export function getCstClassColor(cstClass: CstClass, darkMode: boolean): string {
switch (cstClass) {
case CstClass.TEMPLATE: return darkMode ? '#36899e': '#a5e9fa';
case CstClass.BASIC: return darkMode ? '#2b8000': '#aaff80';
case CstClass.DERIVED: return darkMode ? '#1e00b3': '#b3bdff';
case CstClass.STATEMENT: return darkMode ? '#592b2b': '#ffc9c9';
}
}
export const mapCstClassInfo: Map<CstClass, IFormatInfo> = new Map([
[ CstClass.BASIC, {
text: 'базовый',
color: 'bg-[#aaff80] dark:bg-[#2b8000]',
tooltip: 'неопределяемое понятие, требует конвенции'
}],
[ CstClass.DERIVED, {
text: 'производный',
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
tooltip: 'выводимое понятие, задаваемое определением'
}],
[ CstClass.STATEMENT, {
text: 'утверждение',
color: 'bg-[#ffc9c9] dark:bg-[#592b2b]',
tooltip: 'неопределяемое понятие, требует конвенции'
}],
[ CstClass.TEMPLATE, {
text: 'шаблон',
color: 'bg-[#a5e9fa] dark:bg-[#36899e]',
tooltip: 'параметризованный шаблон определения'
}],
]);
export function createAliasFor(type: CstType, schema: IRSForm): string {
const prefix = getCstTypePrefix(type);
if (!schema.items || schema.items.length <= 0) {
@ -370,6 +426,8 @@ export function getMockConstituenta(id: number, alias: string, type: CstType, co
}
},
status: ExpressionStatus.INCORRECT,
isTemplate: false,
cstClass: CstClass.DERIVED,
parse: {
status: ParsingStatus.INCORRECT,
valueClass: ValueClass.INVALID,