mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Merge branch 'main' of https://github.com/IRBorisov/ConceptPortal
This commit is contained in:
commit
1f9e761565
5
TODO.txt
5
TODO.txt
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>}
|
||||
|
|
|
@ -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>}
|
||||
|
|
|
@ -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>
|
||||
|
|
22
rsconcept/frontend/src/components/Help/InfoConstituenta.tsx
Normal file
22
rsconcept/frontend/src/components/Help/InfoConstituenta.tsx
Normal 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;
|
29
rsconcept/frontend/src/components/Help/InfoCstClass.tsx
Normal file
29
rsconcept/frontend/src/components/Help/InfoCstClass.tsx
Normal 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;
|
29
rsconcept/frontend/src/components/Help/InfoCstStatus.tsx
Normal file
29
rsconcept/frontend/src/components/Help/InfoCstStatus.tsx
Normal 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;
|
|
@ -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} />
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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(
|
||||
() => {
|
||||
|
|
153
rsconcept/frontend/src/pages/RSFormPage/DlgGraphOptions.tsx
Normal file
153
rsconcept/frontend/src/pages/RSFormPage/DlgGraphOptions.tsx
Normal 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;
|
|
@ -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}
|
||||
|
|
|
@ -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';
|
||||
|
@ -89,7 +90,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
}
|
||||
|
||||
// Add new constituenta
|
||||
function handleCreateCst(type?: CstType){
|
||||
function handleCreateCst(type?: CstType) {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
|
@ -156,98 +157,97 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
setSelected(selectedRows.map(cst => cst.id));
|
||||
}, [setSelected]);
|
||||
|
||||
const columns = useMemo(() =>
|
||||
[
|
||||
{
|
||||
name: 'ID',
|
||||
id: 'id',
|
||||
selector: (cst: IConstituenta) => cst.id,
|
||||
omit: true
|
||||
},
|
||||
{
|
||||
name: 'Имя',
|
||||
id: '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',
|
||||
maxWidth: '65px',
|
||||
reorder: true,
|
||||
},
|
||||
{
|
||||
name: 'Тип',
|
||||
id: 'type',
|
||||
cell: (cst: IConstituenta) => <div style={{ fontSize: 12 }}>{getCstTypificationLabel(cst)}</div>,
|
||||
width: '175px',
|
||||
maxWidth: '175px',
|
||||
wrap: true,
|
||||
reorder: true,
|
||||
hide: 1600
|
||||
},
|
||||
{
|
||||
name: 'Термин',
|
||||
id: 'term',
|
||||
selector: (cst: IConstituenta) => cst.term?.resolved ?? cst.term?.raw ?? '',
|
||||
width: '350px',
|
||||
minWidth: '150px',
|
||||
maxWidth: '350px',
|
||||
wrap: true,
|
||||
reorder: true
|
||||
},
|
||||
{
|
||||
name: 'Формальное определение',
|
||||
id: 'expression',
|
||||
selector: (cst: IConstituenta) => cst.definition?.formal ?? '',
|
||||
minWidth: '300px',
|
||||
maxWidth: '500px',
|
||||
grow: 2,
|
||||
wrap: true,
|
||||
reorder: true
|
||||
},
|
||||
{
|
||||
name: 'Текстовое определение',
|
||||
id: 'definition',
|
||||
cell: (cst: IConstituenta) => (
|
||||
<div style={{ fontSize: 12 }}>
|
||||
{cst.definition?.text.resolved ?? cst.definition?.text.raw ?? ''}
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'ID',
|
||||
id: 'id',
|
||||
selector: (cst: IConstituenta) => cst.id,
|
||||
omit: true
|
||||
},
|
||||
{
|
||||
name: 'Имя',
|
||||
id: '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>
|
||||
),
|
||||
minWidth: '200px',
|
||||
grow: 2,
|
||||
wrap: true,
|
||||
reorder: true
|
||||
<ConceptTooltip
|
||||
anchorSelect={`#${prefixes.cst_list}${cst.alias}`}
|
||||
place='right'
|
||||
>
|
||||
<p><b>Статус: </b> {info.tooltip}</p>
|
||||
</ConceptTooltip>
|
||||
</>);
|
||||
},
|
||||
{
|
||||
name: 'Конвенция / Комментарий',
|
||||
id: 'convention',
|
||||
cell: (cst: IConstituenta) => <div style={{ fontSize: 12 }}>{cst.convention ?? ''}</div>,
|
||||
minWidth: '100px',
|
||||
wrap: true,
|
||||
reorder: true,
|
||||
hide: 1800
|
||||
}
|
||||
], []
|
||||
);
|
||||
width: '65px',
|
||||
maxWidth: '65px',
|
||||
reorder: true,
|
||||
},
|
||||
{
|
||||
name: 'Тип',
|
||||
id: 'type',
|
||||
cell: (cst: IConstituenta) => <div style={{ fontSize: 12 }}>{getCstTypificationLabel(cst)}</div>,
|
||||
width: '175px',
|
||||
maxWidth: '175px',
|
||||
wrap: true,
|
||||
reorder: true,
|
||||
hide: 1600
|
||||
},
|
||||
{
|
||||
name: 'Термин',
|
||||
id: 'term',
|
||||
selector: (cst: IConstituenta) => cst.term?.resolved ?? cst.term?.raw ?? '',
|
||||
width: '350px',
|
||||
minWidth: '150px',
|
||||
maxWidth: '350px',
|
||||
wrap: true,
|
||||
reorder: true
|
||||
},
|
||||
{
|
||||
name: 'Формальное определение',
|
||||
id: 'expression',
|
||||
selector: (cst: IConstituenta) => cst.definition?.formal ?? '',
|
||||
minWidth: '300px',
|
||||
maxWidth: '500px',
|
||||
grow: 2,
|
||||
wrap: true,
|
||||
reorder: true
|
||||
},
|
||||
{
|
||||
name: 'Текстовое определение',
|
||||
id: 'definition',
|
||||
cell: (cst: IConstituenta) => (
|
||||
<div style={{ fontSize: 12 }}>
|
||||
{cst.definition?.text.resolved ?? cst.definition?.text.raw ?? ''}
|
||||
</div>
|
||||
),
|
||||
minWidth: '200px',
|
||||
grow: 2,
|
||||
wrap: true,
|
||||
reorder: true
|
||||
},
|
||||
{
|
||||
name: 'Конвенция / Комментарий',
|
||||
id: 'convention',
|
||||
cell: (cst: IConstituenta) => <div style={{ fontSize: 12 }}>{cst.convention ?? ''}</div>,
|
||||
minWidth: '100px',
|
||||
wrap: true,
|
||||
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'>
|
||||
|
@ -314,20 +314,9 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
<p><b>Клик на квадрат слева</b> - выделение конституенты</p>
|
||||
<p><b>Alt + вверх/вниз</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>);
|
||||
})}
|
||||
<CstStatusInfo title='Статусы' />
|
||||
</div>
|
||||
</ConceptTooltip>
|
||||
</div>
|
||||
|
|
|
@ -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 [ 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 [ 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);
|
||||
|
||||
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]);
|
||||
|
||||
useEffect(() => {
|
||||
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 => {
|
||||
|
@ -70,15 +196,11 @@ 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]'
|
||||
options={GraphLayoutSelector}
|
||||
placeholder='Выберите тип'
|
||||
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []}
|
||||
onChange={data => { setLayout(data.length > 0 ? data[0].value : GraphLayoutSelector[0].value); }}
|
||||
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>
|
||||
<Checkbox
|
||||
label='Анимация вращения'
|
||||
value={orbit}
|
||||
onChange={ event => setOrbit(event.target.checked) }
|
||||
<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); }}
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
<div className='flex-wrap w-full h-full overflow-auto'>
|
||||
<div className={`relative border-t border-r ${canvasSize}`}>
|
||||
<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>);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
</>}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -28,5 +28,6 @@ export const resources = {
|
|||
}
|
||||
|
||||
export const prefixes = {
|
||||
cst_list: 'cst-list-'
|
||||
cst_list: 'cst-list-',
|
||||
cst_status_list: 'cst-status-list-'
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue
Block a user