mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Refactor EditorTermGraph
decouple responsibilities
This commit is contained in:
parent
b804586394
commit
50de0176f6
|
@ -1,105 +0,0 @@
|
||||||
import Checkbox from '../components/Common/Checkbox';
|
|
||||||
import Modal, { ModalProps } from '../components/Common/Modal';
|
|
||||||
import usePartialUpdate from '../hooks/usePartialUpdate';
|
|
||||||
import { GraphEditorParams } from '../models/miscelanious';
|
|
||||||
import { CstType } from '../models/rsform';
|
|
||||||
import { labelCstType } from '../utils/labels';
|
|
||||||
|
|
||||||
interface DlgGraphOptionsProps
|
|
||||||
extends Pick<ModalProps, 'hideWindow'> {
|
|
||||||
initial: GraphEditorParams
|
|
||||||
onConfirm: (params: GraphEditorParams) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function DlgGraphOptions({ hideWindow, initial, onConfirm } : DlgGraphOptionsProps) {
|
|
||||||
const [params, updateParams] = usePartialUpdate(initial);
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
hideWindow();
|
|
||||||
onConfirm(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal canSubmit
|
|
||||||
hideWindow={hideWindow}
|
|
||||||
title='Настройки графа термов'
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
submitText='Применить'
|
|
||||||
>
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<div className='flex flex-col gap-1'>
|
|
||||||
<h1>Преобразования</h1>
|
|
||||||
<Checkbox
|
|
||||||
label='Скрыть текст'
|
|
||||||
tooltip='Не отображать термины'
|
|
||||||
value={params.noTerms}
|
|
||||||
setValue={value => updateParams({noTerms: value})}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label='Скрыть несвязанные'
|
|
||||||
tooltip='Неиспользуемые конституенты'
|
|
||||||
value={params.noHermits}
|
|
||||||
setValue={value => updateParams({ noHermits: value})}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label='Скрыть шаблоны'
|
|
||||||
tooltip='Терм-функции и предикат-функции с параметризованными аргументами'
|
|
||||||
value={params.noTemplates}
|
|
||||||
setValue={value => updateParams({ noTemplates: value})}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label='Транзитивная редукция'
|
|
||||||
tooltip='Удалить связи, образующие транзитивные пути в графе'
|
|
||||||
value={params.noTransitive}
|
|
||||||
setValue={value => updateParams({ noTransitive: value})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='flex flex-col gap-1'>
|
|
||||||
<h1>Типы конституент</h1>
|
|
||||||
<Checkbox
|
|
||||||
label={labelCstType(CstType.BASE)}
|
|
||||||
value={params.allowBase}
|
|
||||||
setValue={value => updateParams({ allowBase: value})}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={labelCstType(CstType.STRUCTURED)}
|
|
||||||
value={params.allowStruct}
|
|
||||||
setValue={value => updateParams({ allowStruct: value})}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={labelCstType(CstType.TERM)}
|
|
||||||
value={params.allowTerm}
|
|
||||||
setValue={value => updateParams({ allowTerm: value})}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={labelCstType(CstType.AXIOM)}
|
|
||||||
value={params.allowAxiom}
|
|
||||||
setValue={value => updateParams({ allowAxiom: value})}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={labelCstType(CstType.FUNCTION)}
|
|
||||||
value={params.allowFunction}
|
|
||||||
setValue={value => updateParams({ allowFunction: value})}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={labelCstType(CstType.PREDICATE)}
|
|
||||||
value={params.allowPredicate}
|
|
||||||
setValue={value => updateParams({ allowPredicate: value})}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={labelCstType(CstType.CONSTANT)}
|
|
||||||
value={params.allowConstant}
|
|
||||||
setValue={value => updateParams({ allowConstant: value})}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={labelCstType(CstType.THEOREM)}
|
|
||||||
value={params.allowTheorem}
|
|
||||||
setValue={value => updateParams({ allowTheorem: value})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DlgGraphOptions;
|
|
104
rsconcept/frontend/src/dialogs/DlgGraphParams.tsx
Normal file
104
rsconcept/frontend/src/dialogs/DlgGraphParams.tsx
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import Checkbox from '../components/Common/Checkbox';
|
||||||
|
import Modal, { ModalProps } from '../components/Common/Modal';
|
||||||
|
import usePartialUpdate from '../hooks/usePartialUpdate';
|
||||||
|
import { GraphFilterParams } from '../models/miscelanious';
|
||||||
|
import { CstType } from '../models/rsform';
|
||||||
|
import { labelCstType } from '../utils/labels';
|
||||||
|
|
||||||
|
interface DlgGraphParamsProps
|
||||||
|
extends Pick<ModalProps, 'hideWindow'> {
|
||||||
|
initial: GraphFilterParams
|
||||||
|
onConfirm: (params: GraphFilterParams) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function DlgGraphParams({ hideWindow, initial, onConfirm } : DlgGraphParamsProps) {
|
||||||
|
const [params, updateParams] = usePartialUpdate(initial);
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
hideWindow();
|
||||||
|
onConfirm(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal canSubmit
|
||||||
|
hideWindow={hideWindow}
|
||||||
|
title='Настройки графа термов'
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
submitText='Применить'
|
||||||
|
>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<div className='flex flex-col gap-1'>
|
||||||
|
<h1>Преобразования</h1>
|
||||||
|
<Checkbox
|
||||||
|
label='Скрыть текст'
|
||||||
|
tooltip='Не отображать термины'
|
||||||
|
value={params.noText}
|
||||||
|
setValue={value => updateParams({noText: value})}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='Скрыть несвязанные'
|
||||||
|
tooltip='Неиспользуемые конституенты'
|
||||||
|
value={params.noHermits}
|
||||||
|
setValue={value => updateParams({ noHermits: value})}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='Скрыть шаблоны'
|
||||||
|
tooltip='Терм-функции и предикат-функции с параметризованными аргументами'
|
||||||
|
value={params.noTemplates}
|
||||||
|
setValue={value => updateParams({ noTemplates: value})}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='Транзитивная редукция'
|
||||||
|
tooltip='Удалить связи, образующие транзитивные пути в графе'
|
||||||
|
value={params.noTransitive}
|
||||||
|
setValue={value => updateParams({ noTransitive: value})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-1'>
|
||||||
|
<h1>Типы конституент</h1>
|
||||||
|
<Checkbox
|
||||||
|
label={labelCstType(CstType.BASE)}
|
||||||
|
value={params.allowBase}
|
||||||
|
setValue={value => updateParams({ allowBase: value})}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={labelCstType(CstType.STRUCTURED)}
|
||||||
|
value={params.allowStruct}
|
||||||
|
setValue={value => updateParams({ allowStruct: value})}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={labelCstType(CstType.TERM)}
|
||||||
|
value={params.allowTerm}
|
||||||
|
setValue={value => updateParams({ allowTerm: value})}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={labelCstType(CstType.AXIOM)}
|
||||||
|
value={params.allowAxiom}
|
||||||
|
setValue={value => updateParams({ allowAxiom: value})}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={labelCstType(CstType.FUNCTION)}
|
||||||
|
value={params.allowFunction}
|
||||||
|
setValue={value => updateParams({ allowFunction: value})}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={labelCstType(CstType.PREDICATE)}
|
||||||
|
value={params.allowPredicate}
|
||||||
|
setValue={value => updateParams({ allowPredicate: value})}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={labelCstType(CstType.CONSTANT)}
|
||||||
|
value={params.allowConstant}
|
||||||
|
setValue={value => updateParams({ allowConstant: value})}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={labelCstType(CstType.THEOREM)}
|
||||||
|
value={params.allowTheorem}
|
||||||
|
setValue={value => updateParams({ allowTheorem: value})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DlgGraphParams;
|
|
@ -14,6 +14,11 @@ export enum DependencyMode {
|
||||||
EXPAND_INPUTS
|
EXPAND_INPUTS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents graph node coloring scheme.
|
||||||
|
*/
|
||||||
|
export type GraphColoringScheme = 'none' | 'status' | 'type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents manuals topic.
|
* Represents manuals topic.
|
||||||
*/
|
*/
|
||||||
|
@ -69,11 +74,11 @@ export enum LibraryFilterStrategy {
|
||||||
/**
|
/**
|
||||||
* Represents parameters for GraphEditor.
|
* Represents parameters for GraphEditor.
|
||||||
*/
|
*/
|
||||||
export interface GraphEditorParams {
|
export interface GraphFilterParams {
|
||||||
noHermits: boolean
|
noHermits: boolean
|
||||||
noTransitive: boolean
|
noTransitive: boolean
|
||||||
noTemplates: boolean
|
noTemplates: boolean
|
||||||
noTerms: boolean
|
noText: boolean
|
||||||
|
|
||||||
allowBase: boolean
|
allowBase: boolean
|
||||||
allowStruct: boolean
|
allowStruct: boolean
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { Dispatch, SetStateAction, useLayoutEffect, useMemo, useState } from 'react';
|
import { Dispatch, SetStateAction, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
import ConceptTooltip from '../../../components/Common/ConceptTooltip';
|
||||||
import MiniButton from '../../components/Common/MiniButton';
|
import MiniButton from '../../../components/Common/MiniButton';
|
||||||
import SubmitButton from '../../components/Common/SubmitButton';
|
import SubmitButton from '../../../components/Common/SubmitButton';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
import TextArea from '../../../components/Common/TextArea';
|
||||||
import HelpConstituenta from '../../components/Help/HelpConstituenta';
|
import HelpConstituenta from '../../../components/Help/HelpConstituenta';
|
||||||
import { ArrowsRotateIcon, CloneIcon, DumpBinIcon, EditIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
import { ArrowsRotateIcon, CloneIcon, DumpBinIcon, EditIcon, HelpIcon, SaveIcon, SmallPlusIcon } from '../../../components/Icons';
|
||||||
import RefsInput from '../../components/RefsInput';
|
import RefsInput from '../../../components/RefsInput';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../../context/RSFormContext';
|
||||||
import useWindowSize from '../../hooks/useWindowSize';
|
import useWindowSize from '../../../hooks/useWindowSize';
|
||||||
import { CstType, IConstituenta, ICstCreateData, ICstRenameData, ICstUpdateData } from '../../models/rsform';
|
import { CstType, IConstituenta, ICstCreateData, ICstRenameData, ICstUpdateData } from '../../../models/rsform';
|
||||||
import { SyntaxTree } from '../../models/rslang';
|
import { SyntaxTree } from '../../../models/rslang';
|
||||||
import { labelCstTypification } from '../../utils/labels';
|
import { labelCstTypification } from '../../../utils/labels';
|
||||||
import EditorRSExpression from './EditorRSExpression';
|
import EditorRSExpression from './EditorRSExpression';
|
||||||
import ViewSideConstituents from './elements/ViewSideConstituents';
|
import ViewSideConstituents from './ViewSideConstituents';
|
||||||
|
|
||||||
// Max height of content for left enditor pane
|
// Max height of content for left enditor pane
|
||||||
const UNFOLDED_HEIGHT = '59.1rem';
|
const UNFOLDED_HEIGHT = '59.1rem';
|
|
@ -2,22 +2,22 @@ import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import Button from '../../components/Common/Button';
|
import Button from '../../../components/Common/Button';
|
||||||
import { ConceptLoader } from '../../components/Common/ConceptLoader';
|
import { ConceptLoader } from '../../../components/Common/ConceptLoader';
|
||||||
import MiniButton from '../../components/Common/MiniButton';
|
import MiniButton from '../../../components/Common/MiniButton';
|
||||||
import { ASTNetworkIcon } from '../../components/Icons';
|
import { ASTNetworkIcon } from '../../../components/Icons';
|
||||||
import RSInput from '../../components/RSInput';
|
import RSInput from '../../../components/RSInput';
|
||||||
import { RSTextWrapper } from '../../components/RSInput/textEditing';
|
import { RSTextWrapper } from '../../../components/RSInput/textEditing';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../../context/RSFormContext';
|
||||||
import useCheckExpression from '../../hooks/useCheckExpression';
|
import useCheckExpression from '../../../hooks/useCheckExpression';
|
||||||
import { IConstituenta } from '../../models/rsform';
|
import { IConstituenta } from '../../../models/rsform';
|
||||||
import { IExpressionParse, IRSErrorDescription, SyntaxTree } from '../../models/rslang';
|
import { IExpressionParse, IRSErrorDescription, SyntaxTree } from '../../../models/rslang';
|
||||||
import { TokenID } from '../../models/rslang';
|
import { TokenID } from '../../../models/rslang';
|
||||||
import { labelTypification } from '../../utils/labels';
|
import { labelTypification } from '../../../utils/labels';
|
||||||
import { getCstExpressionPrefix } from '../../utils/misc';
|
import { getCstExpressionPrefix } from '../../../utils/misc';
|
||||||
import ParsingResult from './elements/ParsingResult';
|
import ParsingResult from './ParsingResult';
|
||||||
import RSEditorControls from './elements/RSEditorControls';
|
import RSEditorControls from './RSEditControls';
|
||||||
import StatusBar from './elements/StatusBar';
|
import StatusBar from './StatusBar';
|
||||||
|
|
||||||
interface EditorRSExpressionProps {
|
interface EditorRSExpressionProps {
|
||||||
id?: string
|
id?: string
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './EditorConstituenta';
|
|
@ -2,21 +2,21 @@ import { Dispatch, SetStateAction, useLayoutEffect, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import Checkbox from '../../components/Common/Checkbox';
|
import Checkbox from '../../../components/Common/Checkbox';
|
||||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
import ConceptTooltip from '../../../components/Common/ConceptTooltip';
|
||||||
import Divider from '../../components/Common/Divider';
|
import Divider from '../../../components/Common/Divider';
|
||||||
import MiniButton from '../../components/Common/MiniButton';
|
import MiniButton from '../../../components/Common/MiniButton';
|
||||||
import SubmitButton from '../../components/Common/SubmitButton';
|
import SubmitButton from '../../../components/Common/SubmitButton';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
import TextArea from '../../../components/Common/TextArea';
|
||||||
import TextInput from '../../components/Common/TextInput';
|
import TextInput from '../../../components/Common/TextInput';
|
||||||
import HelpRSFormMeta from '../../components/Help/HelpRSFormMeta';
|
import HelpRSFormMeta from '../../../components/Help/HelpRSFormMeta';
|
||||||
import { DownloadIcon, DumpBinIcon, HelpIcon, OwnerIcon, SaveIcon, ShareIcon } from '../../components/Icons';
|
import { DownloadIcon, DumpBinIcon, HelpIcon, OwnerIcon, SaveIcon, ShareIcon } from '../../../components/Icons';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../../context/AuthContext';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../../context/RSFormContext';
|
||||||
import { useUsers } from '../../context/UsersContext';
|
import { useUsers } from '../../../context/UsersContext';
|
||||||
import { LibraryItemType } from '../../models/library';
|
import { LibraryItemType } from '../../../models/library';
|
||||||
import { IRSFormCreateData } from '../../models/rsform';
|
import { IRSFormCreateData } from '../../../models/rsform';
|
||||||
import RSFormStats from './elements/RSFormStats';
|
import RSFormStats from './RSFormStats';
|
||||||
|
|
||||||
interface EditorRSFormProps {
|
interface EditorRSFormProps {
|
||||||
onDestroy: () => void
|
onDestroy: () => void
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './EditorRSForm';
|
|
@ -1,15 +1,15 @@
|
||||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../components/DataTable';
|
import DataTable, { createColumnHelper, type RowSelectionState,VisibilityState } from '../../../components/DataTable';
|
||||||
import ConstituentaBadge from '../../components/Shared/ConstituentaBadge';
|
import ConstituentaBadge from '../../../components/Shared/ConstituentaBadge';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||||
import useWindowSize from '../../hooks/useWindowSize';
|
import useWindowSize from '../../../hooks/useWindowSize';
|
||||||
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../models/rsform'
|
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../../models/rsform'
|
||||||
import { prefixes } from '../../utils/constants';
|
import { prefixes } from '../../../utils/constants';
|
||||||
import { labelCstTypification } from '../../utils/labels';
|
import { labelCstTypification } from '../../../utils/labels';
|
||||||
import RSItemsMenu from './elements/RSItemsMenu';
|
import RSItemsMenu from './RSListToolbar';
|
||||||
|
|
||||||
// Window width cutoff for columns
|
// Window width cutoff for columns
|
||||||
const COLUMN_DEFINITION_HIDE_THRESHOLD = 1000;
|
const COLUMN_DEFINITION_HIDE_THRESHOLD = 1000;
|
||||||
|
@ -18,14 +18,14 @@ const COLUMN_CONVENTION_HIDE_THRESHOLD = 1800;
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<IConstituenta>();
|
const columnHelper = createColumnHelper<IConstituenta>();
|
||||||
|
|
||||||
interface EditorItemsProps {
|
interface EditorRSListProps {
|
||||||
onOpenEdit: (cstID: number) => void
|
onOpenEdit: (cstID: number) => void
|
||||||
onTemplates: (selected: number[]) => void
|
onTemplates: (selected: number[]) => void
|
||||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
||||||
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: EditorItemsProps) {
|
function EditorRSList({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: EditorRSListProps) {
|
||||||
const { colors, mainHeight } = useConceptTheme();
|
const { colors, mainHeight } = useConceptTheme();
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm();
|
const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm();
|
||||||
|
@ -300,7 +300,8 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edit
|
||||||
Выбор {selected.length} из {schema?.stats?.count_all ?? 0}
|
Выбор {selected.length} из {schema?.stats?.count_all ?? 0}
|
||||||
</div>
|
</div>
|
||||||
<RSItemsMenu
|
<RSItemsMenu
|
||||||
selected={selected}
|
selectedCount={selected.length}
|
||||||
|
editorMode={isEditable}
|
||||||
onMoveUp={handleMoveUp}
|
onMoveUp={handleMoveUp}
|
||||||
onMoveDown={handleMoveDown}
|
onMoveDown={handleMoveDown}
|
||||||
onClone={handleClone}
|
onClone={handleClone}
|
||||||
|
@ -344,4 +345,4 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst, onTemplates }: Edit
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditorItems;
|
export default EditorRSList;
|
|
@ -6,7 +6,6 @@ import DropdownButton from '../../../components/Common/DropdownButton';
|
||||||
import MiniButton from '../../../components/Common/MiniButton';
|
import MiniButton from '../../../components/Common/MiniButton';
|
||||||
import HelpRSFormItems from '../../../components/Help/HelpRSFormItems';
|
import HelpRSFormItems from '../../../components/Help/HelpRSFormItems';
|
||||||
import { ArrowDownIcon, ArrowDropdownIcon, ArrowUpIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SmallPlusIcon,UpdateIcon } from '../../../components/Icons';
|
import { ArrowDownIcon, ArrowDropdownIcon, ArrowUpIcon, CloneIcon, DiamondIcon, DumpBinIcon, HelpIcon, SmallPlusIcon,UpdateIcon } from '../../../components/Icons';
|
||||||
import { useRSForm } from '../../../context/RSFormContext';
|
|
||||||
import useDropdown from '../../../hooks/useDropdown';
|
import useDropdown from '../../../hooks/useDropdown';
|
||||||
import { CstType } from '../../../models/rsform';
|
import { CstType } from '../../../models/rsform';
|
||||||
import { prefixes } from '../../../utils/constants';
|
import { prefixes } from '../../../utils/constants';
|
||||||
|
@ -14,7 +13,8 @@ import { labelCstType } from '../../../utils/labels';
|
||||||
import { getCstTypePrefix, getCstTypeShortcut } from '../../../utils/misc';
|
import { getCstTypePrefix, getCstTypeShortcut } from '../../../utils/misc';
|
||||||
|
|
||||||
interface RSItemsMenuProps {
|
interface RSItemsMenuProps {
|
||||||
selected: number[]
|
editorMode?: boolean
|
||||||
|
selectedCount: number
|
||||||
|
|
||||||
onMoveUp: () => void
|
onMoveUp: () => void
|
||||||
onMoveDown: () => void
|
onMoveDown: () => void
|
||||||
|
@ -26,51 +26,50 @@ interface RSItemsMenuProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSItemsMenu({
|
function RSItemsMenu({
|
||||||
selected,
|
selectedCount, editorMode,
|
||||||
onMoveUp, onMoveDown, onDelete, onClone, onCreate, onTemplates, onReindex
|
onMoveUp, onMoveDown, onDelete, onClone, onCreate, onTemplates, onReindex
|
||||||
}: RSItemsMenuProps) {
|
}: RSItemsMenuProps) {
|
||||||
const { isEditable } = useRSForm();
|
|
||||||
const insertMenu = useDropdown();
|
const insertMenu = useDropdown();
|
||||||
|
|
||||||
const nothingSelected = useMemo(() => selected.length === 0, [selected]);
|
const nothingSelected = useMemo(() => selectedCount === 0, [selectedCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center justify-center w-full pr-[9rem]'>
|
<div className='flex items-center justify-center w-full pr-[9rem]'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Переместить вверх'
|
tooltip='Переместить вверх'
|
||||||
icon={<ArrowUpIcon size={5}/>}
|
icon={<ArrowUpIcon size={5}/>}
|
||||||
disabled={!isEditable || nothingSelected}
|
disabled={!editorMode || nothingSelected}
|
||||||
onClick={onMoveUp}
|
onClick={onMoveUp}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Переместить вниз'
|
tooltip='Переместить вниз'
|
||||||
icon={<ArrowDownIcon size={5}/>}
|
icon={<ArrowDownIcon size={5}/>}
|
||||||
disabled={!isEditable || nothingSelected}
|
disabled={!editorMode || nothingSelected}
|
||||||
onClick={onMoveDown}
|
onClick={onMoveDown}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Удалить выбранные'
|
tooltip='Удалить выбранные'
|
||||||
icon={<DumpBinIcon color={isEditable && !nothingSelected ? 'text-warning' : ''} size={5}/>}
|
icon={<DumpBinIcon color={editorMode && !nothingSelected ? 'text-warning' : ''} size={5}/>}
|
||||||
disabled={!isEditable || nothingSelected}
|
disabled={!editorMode || nothingSelected}
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Клонировать конституенту'
|
tooltip='Клонировать конституенту'
|
||||||
icon={<CloneIcon color={isEditable && selected.length === 1 ? 'text-success': ''} size={5}/>}
|
icon={<CloneIcon color={editorMode && selectedCount === 1 ? 'text-success': ''} size={5}/>}
|
||||||
disabled={!isEditable || selected.length !== 1}
|
disabled={!editorMode || selectedCount !== 1}
|
||||||
onClick={onClone}
|
onClick={onClone}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Добавить новую конституенту...'
|
tooltip='Добавить новую конституенту...'
|
||||||
icon={<SmallPlusIcon color={isEditable ? 'text-success': ''} size={5}/>}
|
icon={<SmallPlusIcon color={editorMode ? 'text-success': ''} size={5}/>}
|
||||||
disabled={!isEditable}
|
disabled={!editorMode}
|
||||||
onClick={() => onCreate()}
|
onClick={() => onCreate()}
|
||||||
/>
|
/>
|
||||||
<div ref={insertMenu.ref} className='flex justify-center'>
|
<div ref={insertMenu.ref} className='flex justify-center'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Добавить пустую конституенту'
|
tooltip='Добавить пустую конституенту'
|
||||||
icon={<ArrowDropdownIcon color={isEditable ? 'text-success': ''} size={5}/>}
|
icon={<ArrowDropdownIcon color={editorMode ? 'text-success': ''} size={5}/>}
|
||||||
disabled={!isEditable}
|
disabled={!editorMode}
|
||||||
onClick={insertMenu.toggle}
|
onClick={insertMenu.toggle}
|
||||||
/>
|
/>
|
||||||
{ insertMenu.isActive &&
|
{ insertMenu.isActive &&
|
||||||
|
@ -92,15 +91,15 @@ function RSItemsMenu({
|
||||||
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Создать конституенту из шаблона'
|
tooltip='Создать конституенту из шаблона'
|
||||||
icon={<DiamondIcon color={isEditable ? 'text-primary': ''} size={5}/>}
|
icon={<DiamondIcon color={editorMode ? 'text-primary': ''} size={5}/>}
|
||||||
disabled={!isEditable}
|
disabled={!editorMode}
|
||||||
onClick={onTemplates}
|
onClick={onTemplates}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Сброс имен: присвоить порядковые имена'
|
tooltip='Сброс имен: присвоить порядковые имена'
|
||||||
icon={<UpdateIcon color={isEditable ? 'text-primary': ''} size={5}/>}
|
icon={<UpdateIcon color={editorMode ? 'text-primary': ''} size={5}/>}
|
||||||
disabled={!isEditable}
|
disabled={!editorMode}
|
||||||
onClick={onReindex}
|
onClick={onReindex}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './EditorRSList';
|
|
@ -1,510 +0,0 @@
|
||||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
||||||
import { GraphCanvas, GraphCanvasRef, GraphEdge,
|
|
||||||
GraphNode, LayoutTypes, Sphere, useSelection
|
|
||||||
} from 'reagraph';
|
|
||||||
|
|
||||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
|
||||||
import MiniButton from '../../components/Common/MiniButton';
|
|
||||||
import SelectSingle from '../../components/Common/SelectSingle';
|
|
||||||
import ConstituentaTooltip from '../../components/Help/ConstituentaTooltip';
|
|
||||||
import HelpTermGraph from '../../components/Help/HelpTermGraph';
|
|
||||||
import InfoConstituenta from '../../components/Help/InfoConstituenta';
|
|
||||||
import { ArrowsFocusIcon, DumpBinIcon, FilterIcon, HelpIcon, LetterAIcon, LetterALinesIcon, PlanetIcon, SmallPlusIcon } from '../../components/Icons';
|
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
|
||||||
import DlgGraphOptions from '../../dialogs/DlgGraphOptions';
|
|
||||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
|
||||||
import { GraphEditorParams } from '../../models/miscelanious';
|
|
||||||
import { CstType, IConstituenta, ICstCreateData } from '../../models/rsform';
|
|
||||||
import { graphDarkT, graphLightT, IColorTheme } from '../../utils/color';
|
|
||||||
import { colorbgCstClass } from '../../utils/color';
|
|
||||||
import { colorbgCstStatus } from '../../utils/color';
|
|
||||||
import { prefixes, resources, TIMEOUT_GRAPH_REFRESH } from '../../utils/constants';
|
|
||||||
import { Graph } from '../../utils/Graph';
|
|
||||||
import { mapLabelColoring } from '../../utils/labels';
|
|
||||||
import { mapLableLayout } from '../../utils/labels';
|
|
||||||
import { SelectorGraphLayout } from '../../utils/selectors';
|
|
||||||
import { SelectorGraphColoring } from '../../utils/selectors';
|
|
||||||
|
|
||||||
export type ColoringScheme = 'none' | 'status' | 'type';
|
|
||||||
const TREE_SIZE_MILESTONE = 50;
|
|
||||||
|
|
||||||
function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, colors: IColorTheme): string {
|
|
||||||
if (coloringScheme === 'type') {
|
|
||||||
return colorbgCstClass(cst.cst_class, colors);
|
|
||||||
}
|
|
||||||
if (coloringScheme === 'status') {
|
|
||||||
return colorbgCstStatus(cst.status, colors);
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EditorTermGraphProps {
|
|
||||||
onOpenEdit: (cstID: number) => void
|
|
||||||
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
|
||||||
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
|
||||||
const { schema, isEditable } = useRSForm();
|
|
||||||
const { darkMode, colors, noNavigation } = useConceptTheme();
|
|
||||||
|
|
||||||
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d');
|
|
||||||
const [ coloringScheme, setColoringScheme ] = useLocalStorage<ColoringScheme>('graph_coloring', 'type');
|
|
||||||
const [ orbit, setOrbit ] = useState(false);
|
|
||||||
|
|
||||||
const [ noHermits, setNoHermits ] = useLocalStorage('graph_no_hermits', true);
|
|
||||||
const [ noTransitive, setNoTransitive ] = useLocalStorage('graph_no_transitive', true);
|
|
||||||
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<number | undefined>(undefined);
|
|
||||||
const hoverCst = useMemo(
|
|
||||||
() => {
|
|
||||||
return schema?.items.find(cst => 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;
|
|
||||||
}
|
|
||||||
const graph = schema.graph.clone();
|
|
||||||
if (noHermits) {
|
|
||||||
graph.removeIsolated();
|
|
||||||
}
|
|
||||||
if (noTransitive) {
|
|
||||||
graph.transitiveReduction();
|
|
||||||
}
|
|
||||||
if (noTemplates) {
|
|
||||||
schema.items.forEach(cst => {
|
|
||||||
if (cst.is_template) {
|
|
||||||
graph.foldNode(cst.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (allowedTypes.length < Object.values(CstType).length) {
|
|
||||||
schema.items.forEach(cst => {
|
|
||||||
if (!allowedTypes.includes(cst.cst_type)) {
|
|
||||||
graph.foldNode(cst.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const newDismissed: number[] = [];
|
|
||||||
schema.items.forEach(cst => {
|
|
||||||
if (!graph.nodes.has(cst.id)) {
|
|
||||||
newDismissed.push(cst.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setFiltered(graph);
|
|
||||||
setDismissed(newDismissed);
|
|
||||||
setSelectedDismissed([]);
|
|
||||||
setHoverID(undefined);
|
|
||||||
}, [schema, noHermits, noTransitive, noTemplates, allowedTypes, toggleUpdate]);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
filtered.nodes.forEach(node => {
|
|
||||||
const cst = schema.items.find(cst => cst.id === node.id);
|
|
||||||
if (cst) {
|
|
||||||
result.push({
|
|
||||||
id: String(node.id),
|
|
||||||
fill: getCstNodeColor(cst, coloringScheme, colors),
|
|
||||||
label: cst.term_resolved && !noTerms ? `${cst.alias}: ${cst.term_resolved}` : cst.alias
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}, [schema, coloringScheme, filtered.nodes, noTerms, colors]);
|
|
||||||
|
|
||||||
const edges: GraphEdge[] = useMemo(
|
|
||||||
() => {
|
|
||||||
const result: GraphEdge[] = [];
|
|
||||||
let edgeID = 1;
|
|
||||||
filtered.nodes.forEach(source => {
|
|
||||||
source.outputs.forEach(target => {
|
|
||||||
result.push({
|
|
||||||
id: String(edgeID),
|
|
||||||
source: String(source.id),
|
|
||||||
target: String(target)
|
|
||||||
});
|
|
||||||
edgeID += 1;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}, [filtered.nodes]);
|
|
||||||
|
|
||||||
const {
|
|
||||||
selections, actives,
|
|
||||||
onNodeClick,
|
|
||||||
clearSelections,
|
|
||||||
onCanvasClick,
|
|
||||||
onNodePointerOver,
|
|
||||||
onNodePointerOut
|
|
||||||
} = useSelection({
|
|
||||||
ref: graphRef,
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
type: 'multi', // 'single' | 'multi' | 'multiModifier'
|
|
||||||
pathSelectionType: 'out',
|
|
||||||
pathHoverType: 'all',
|
|
||||||
focusOnSelect: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const allSelected: number[] = useMemo(
|
|
||||||
() => {
|
|
||||||
return [ ... selectedDismissed, ... selections.map(id => Number(id))];
|
|
||||||
}, [selectedDismissed, selections]);
|
|
||||||
const nothingSelected = useMemo(() => allSelected.length === 0, [allSelected]);
|
|
||||||
|
|
||||||
const handleResetViewpoint = useCallback(
|
|
||||||
() => {
|
|
||||||
graphRef.current?.resetControls(true);
|
|
||||||
graphRef.current?.centerGraph();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleHoverIn = useCallback(
|
|
||||||
(node: GraphNode) => {
|
|
||||||
setHoverID(Number(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>) {
|
|
||||||
if (!isEditable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.key === 'Delete' && allSelected.length > 0) {
|
|
||||||
event.preventDefault();
|
|
||||||
handleDeleteCst();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCreateCst() {
|
|
||||||
if (!schema) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data: ICstCreateData = {
|
|
||||||
insert_after: null,
|
|
||||||
cst_type: allSelected.length === 0 ? CstType.BASE: CstType.TERM,
|
|
||||||
alias: '',
|
|
||||||
term_raw: '',
|
|
||||||
definition_formal: allSelected.map(id => schema.items.find(cst => cst.id === id)!.alias).join(' '),
|
|
||||||
definition_raw: '',
|
|
||||||
convention: '',
|
|
||||||
term_forms: []
|
|
||||||
};
|
|
||||||
onCreateCst(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDeleteCst() {
|
|
||||||
if (!schema) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onDeleteCst(allSelected, () => {
|
|
||||||
clearSelections();
|
|
||||||
setDismissed([]);
|
|
||||||
setSelectedDismissed([]);
|
|
||||||
setToggleUpdate(prev => !prev);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleChangeLayout(newLayout: LayoutTypes) {
|
|
||||||
if (newLayout === layout) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLayout(newLayout);
|
|
||||||
setTimeout(() => {
|
|
||||||
handleResetViewpoint();
|
|
||||||
}, TIMEOUT_GRAPH_REFRESH);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 - 1.1rem)';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const canvasHeight = useMemo(
|
|
||||||
() => {
|
|
||||||
return !noNavigation ?
|
|
||||||
'calc(100vh - 9.8rem - 4px)'
|
|
||||||
: 'calc(100vh - 3rem - 4px)';
|
|
||||||
}, [noNavigation]);
|
|
||||||
|
|
||||||
const dismissedHeight = useMemo(
|
|
||||||
() => {
|
|
||||||
return !noNavigation ?
|
|
||||||
'calc(100vh - 28rem - 4px)'
|
|
||||||
: 'calc(100vh - 22.2rem - 4px)';
|
|
||||||
}, [noNavigation]);
|
|
||||||
|
|
||||||
const dismissedStyle = useCallback(
|
|
||||||
(cstID: number) => {
|
|
||||||
return selectedDismissed.includes(cstID) ? {outlineWidth: '2px', outlineStyle: 'solid'}: {};
|
|
||||||
}, [selectedDismissed]);
|
|
||||||
|
|
||||||
return (<>
|
|
||||||
{showOptions &&
|
|
||||||
<DlgGraphOptions
|
|
||||||
hideWindow={() => setShowOptions(false)}
|
|
||||||
initial={getOptions()}
|
|
||||||
onConfirm={handleChangeOptions}
|
|
||||||
/>}
|
|
||||||
|
|
||||||
{ allSelected.length > 0 &&
|
|
||||||
<div className='relative w-full z-pop'>
|
|
||||||
<div className='absolute top-0 left-0 px-2 select-none whitespace-nowrap small-caps clr-app'>
|
|
||||||
Выбор {allSelected.length} из {schema?.stats?.count_all ?? 0}
|
|
||||||
</div>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
<div className='relative w-full z-pop'>
|
|
||||||
<div className='absolute right-0 flex items-start justify-center w-full top-1'>
|
|
||||||
<MiniButton
|
|
||||||
tooltip='Настройки фильтрации узлов и связей'
|
|
||||||
icon={<FilterIcon color='text-primary' size={5}/>}
|
|
||||||
onClick={() => setShowOptions(true)}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
tooltip={ !noTerms ? 'Скрыть текст' : 'Отобразить текст' }
|
|
||||||
icon={ !noTerms ? <LetterALinesIcon color='text-success' size={5}/> : <LetterAIcon color='text-primary' size={5}/> }
|
|
||||||
onClick={() => setNoTerms(prev => !prev)}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
tooltip='Новая конституента'
|
|
||||||
icon={<SmallPlusIcon color={isEditable ? 'text-success': ''} size={5}/>}
|
|
||||||
disabled={!isEditable}
|
|
||||||
onClick={handleCreateCst}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
tooltip='Удалить выбранные'
|
|
||||||
icon={<DumpBinIcon color={isEditable && !nothingSelected ? 'text-warning' : ''} size={5}/>}
|
|
||||||
disabled={!isEditable || nothingSelected}
|
|
||||||
onClick={handleDeleteCst}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
icon={<ArrowsFocusIcon color='text-primary' size={5} />}
|
|
||||||
tooltip='Восстановить камеру'
|
|
||||||
onClick={handleResetViewpoint}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
icon={<PlanetIcon color={ !is3D ? '' : orbit ? 'text-success' : 'text-primary'} size={5} />}
|
|
||||||
tooltip='Анимация вращения'
|
|
||||||
disabled={!is3D}
|
|
||||||
onClick={() => setOrbit(prev => !prev) }
|
|
||||||
/>
|
|
||||||
<div className='px-1 py-1' id='items-graph-help' >
|
|
||||||
<HelpIcon color='text-primary' size={5} />
|
|
||||||
</div>
|
|
||||||
<ConceptTooltip anchorSelect='#items-graph-help'>
|
|
||||||
<div className='text-sm max-w-[calc(100vw-20rem)] z-tooltip'>
|
|
||||||
<HelpTermGraph />
|
|
||||||
</div>
|
|
||||||
</ConceptTooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{hoverCst &&
|
|
||||||
<div className='relative'>
|
|
||||||
<InfoConstituenta
|
|
||||||
data={hoverCst}
|
|
||||||
className='absolute top-[1.6rem] left-[2.6rem] z-tooltip w-[25rem] min-h-[11rem] shadow-md overflow-y-auto border h-fit clr-app px-3'
|
|
||||||
/>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
<div className='relative z-pop'>
|
|
||||||
<div className='absolute top-0 left-0 flex flex-col max-w-[13.5rem] min-w-[13.5rem]'>
|
|
||||||
<div className='flex flex-col px-2 pb-2 mt-8 text-sm select-none h-fit'>
|
|
||||||
<div className='flex items-center w-full gap-1 text-sm'>
|
|
||||||
<SelectSingle
|
|
||||||
options={SelectorGraphColoring}
|
|
||||||
isSearchable={false}
|
|
||||||
placeholder='Выберите цвет'
|
|
||||||
value={coloringScheme ? { value: coloringScheme, label: mapLabelColoring.get(coloringScheme) } : null}
|
|
||||||
onChange={data => setColoringScheme(data?.value ?? SelectorGraphColoring[0].value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<SelectSingle
|
|
||||||
className='w-full mt-1'
|
|
||||||
options={SelectorGraphLayout}
|
|
||||||
isSearchable={false}
|
|
||||||
placeholder='Способ расположения'
|
|
||||||
value={layout ? { value: layout, label: mapLableLayout.get(layout) } : null}
|
|
||||||
onChange={data => handleChangeLayout(data?.value ?? SelectorGraphLayout[0].value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{dismissed.length > 0 &&
|
|
||||||
<div className='flex flex-col text-sm ml-2 border clr-app max-w-[12.5rem] min-w-[12.5rem]'>
|
|
||||||
<p className='py-2 text-center'><b>Скрытые конституенты</b></p>
|
|
||||||
<div className='flex flex-wrap justify-center gap-2 pb-2 overflow-y-auto' style={{maxHeight: dismissedHeight}}>
|
|
||||||
{dismissed.map(cstID => {
|
|
||||||
const cst = schema!.items.find(cst => cst.id === cstID)!;
|
|
||||||
const adjustedColoring = coloringScheme === 'none' ? 'status': coloringScheme;
|
|
||||||
const id = `${prefixes.cst_hidden_list}${cst.alias}`
|
|
||||||
return (<div key={`wrap-${id}`}>
|
|
||||||
<div
|
|
||||||
key={id}
|
|
||||||
id={id}
|
|
||||||
className='w-fit min-w-[3rem] rounded-md text-center cursor-pointer select-none'
|
|
||||||
style={{
|
|
||||||
backgroundColor: getCstNodeColor(cst, adjustedColoring, colors),
|
|
||||||
...dismissedStyle(cstID)
|
|
||||||
}}
|
|
||||||
onClick={() => toggleDismissed(cstID)}
|
|
||||||
onDoubleClick={() => onOpenEdit(cstID)}
|
|
||||||
>
|
|
||||||
{cst.alias}
|
|
||||||
</div>
|
|
||||||
<ConstituentaTooltip
|
|
||||||
data={cst}
|
|
||||||
anchor={`#${id}`}
|
|
||||||
/>
|
|
||||||
</div>);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div tabIndex={-1}
|
|
||||||
className='w-full h-full overflow-auto outline-none'
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className='relative'
|
|
||||||
style={{width: canvasWidth, height: canvasHeight}}
|
|
||||||
>
|
|
||||||
<GraphCanvas
|
|
||||||
draggable
|
|
||||||
ref={graphRef}
|
|
||||||
nodes={nodes}
|
|
||||||
edges={edges}
|
|
||||||
layoutType={layout}
|
|
||||||
selections={selections}
|
|
||||||
actives={actives}
|
|
||||||
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 ? graphDarkT : graphLightT}
|
|
||||||
renderNode={({ node, ...rest }) => (
|
|
||||||
<Sphere {...rest} node={node} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div></>);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default EditorTermGraph;
|
|
|
@ -0,0 +1,272 @@
|
||||||
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
import { GraphEdge, GraphNode, LayoutTypes } from 'reagraph';
|
||||||
|
|
||||||
|
import InfoConstituenta from '../../../components/Help/InfoConstituenta';
|
||||||
|
import { useRSForm } from '../../../context/RSFormContext';
|
||||||
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||||
|
import DlgGraphParams from '../../../dialogs/DlgGraphParams';
|
||||||
|
import useLocalStorage from '../../../hooks/useLocalStorage';
|
||||||
|
import { GraphColoringScheme, GraphFilterParams } from '../../../models/miscelanious';
|
||||||
|
import { CstType, ICstCreateData } from '../../../models/rsform';
|
||||||
|
import { colorbgGraphNode } from '../../../utils/color';
|
||||||
|
import { TIMEOUT_GRAPH_REFRESH } from '../../../utils/constants';
|
||||||
|
import GraphSidebar from './GraphSidebar';
|
||||||
|
import GraphToolbar from './GraphToolbar';
|
||||||
|
import TermGraph from './TermGraph';
|
||||||
|
import useGraphFilter from './useGraphFilter';
|
||||||
|
import ViewHidden from './ViewHidden';
|
||||||
|
|
||||||
|
interface EditorTermGraphProps {
|
||||||
|
onOpenEdit: (cstID: number) => void
|
||||||
|
onCreateCst: (initial: ICstCreateData, skipDialog?: boolean) => void
|
||||||
|
onDeleteCst: (selected: number[], callback: (items: number[]) => void) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
||||||
|
const { schema, isEditable } = useRSForm();
|
||||||
|
const { colors } = useConceptTheme();
|
||||||
|
|
||||||
|
const [toggleDataUpdate, setToggleDataUpdate] = useState(false);
|
||||||
|
const [filterParams, setFilterParams] = useLocalStorage<GraphFilterParams>('graph_filter', {
|
||||||
|
noHermits: true,
|
||||||
|
noTemplates: false,
|
||||||
|
noTransitive: true,
|
||||||
|
noText: false,
|
||||||
|
|
||||||
|
allowBase: true,
|
||||||
|
allowStruct: true,
|
||||||
|
allowTerm: true,
|
||||||
|
allowAxiom: true,
|
||||||
|
allowFunction: true,
|
||||||
|
allowPredicate: true,
|
||||||
|
allowConstant: true,
|
||||||
|
allowTheorem: true
|
||||||
|
});
|
||||||
|
const [showParamsDialog, setShowParamsDialog] = useState(false);
|
||||||
|
const filtered = useGraphFilter(schema, filterParams, toggleDataUpdate);
|
||||||
|
|
||||||
|
const [selectedGraph, setSelectedGraph] = useState<number[]>([]);
|
||||||
|
const [hidden, setHidden] = useState<number[]>([]);
|
||||||
|
const [selectedHidden, setSelectedHidden] = useState<number[]>([]);
|
||||||
|
const selected: number[] = useMemo(
|
||||||
|
() => {
|
||||||
|
return [...selectedHidden, ...selectedGraph];
|
||||||
|
}, [selectedHidden, selectedGraph]);
|
||||||
|
const nothingSelected = useMemo(() => selected.length === 0, [selected]);
|
||||||
|
|
||||||
|
const [layout, setLayout] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d');
|
||||||
|
const is3D = useMemo(() => layout.includes('3d'), [layout]);
|
||||||
|
const [coloringScheme, setColoringScheme] = useLocalStorage<GraphColoringScheme>('graph_coloring', 'type');
|
||||||
|
const [orbit, setOrbit] = useState(false);
|
||||||
|
|
||||||
|
const [hoverID, setHoverID] = useState<number | undefined>(undefined);
|
||||||
|
const hoverCst = useMemo(
|
||||||
|
() => {
|
||||||
|
return schema?.items.find(cst => cst.id === hoverID);
|
||||||
|
}, [schema?.items, hoverID]);
|
||||||
|
|
||||||
|
const [toggleResetView, setToggleResetView] = useState(false);
|
||||||
|
const [toggleResetSelection, setToggleResetSelection] = useState(false);
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
if (!schema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newDismissed: number[] = [];
|
||||||
|
schema.items.forEach(cst => {
|
||||||
|
if (!filtered.nodes.has(cst.id)) {
|
||||||
|
newDismissed.push(cst.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setHidden(newDismissed);
|
||||||
|
setSelectedHidden([]);
|
||||||
|
setHoverID(undefined);
|
||||||
|
}, [schema, filtered, toggleDataUpdate]);
|
||||||
|
|
||||||
|
const nodes: GraphNode[] = useMemo(
|
||||||
|
() => {
|
||||||
|
const result: GraphNode[] = [];
|
||||||
|
if (!schema) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
filtered.nodes.forEach(node => {
|
||||||
|
const cst = schema.items.find(cst => cst.id === node.id);
|
||||||
|
if (cst) {
|
||||||
|
result.push({
|
||||||
|
id: String(node.id),
|
||||||
|
fill: colorbgGraphNode(cst, coloringScheme, colors),
|
||||||
|
label: cst.term_resolved && !filterParams.noText ? `${cst.alias}: ${cst.term_resolved}` : cst.alias
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, [schema, coloringScheme, filtered.nodes, filterParams.noText, colors]);
|
||||||
|
|
||||||
|
const edges: GraphEdge[] = useMemo(
|
||||||
|
() => {
|
||||||
|
const result: GraphEdge[] = [];
|
||||||
|
let edgeID = 1;
|
||||||
|
filtered.nodes.forEach(source => {
|
||||||
|
source.outputs.forEach(target => {
|
||||||
|
result.push({
|
||||||
|
id: String(edgeID),
|
||||||
|
source: String(source.id),
|
||||||
|
target: String(target)
|
||||||
|
});
|
||||||
|
edgeID += 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}, [filtered.nodes]);
|
||||||
|
|
||||||
|
function toggleDismissed(cstID: number) {
|
||||||
|
setSelectedHidden(prev => {
|
||||||
|
const index = prev.findIndex(id => cstID === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
prev.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
prev.push(cstID);
|
||||||
|
}
|
||||||
|
return [...prev];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCreateCst() {
|
||||||
|
if (!schema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data: ICstCreateData = {
|
||||||
|
insert_after: null,
|
||||||
|
cst_type: selected.length === 0 ? CstType.BASE: CstType.TERM,
|
||||||
|
alias: '',
|
||||||
|
term_raw: '',
|
||||||
|
definition_formal: selected.map(id => schema.items.find(cst => cst.id === id)!.alias).join(' '),
|
||||||
|
definition_raw: '',
|
||||||
|
convention: '',
|
||||||
|
term_forms: []
|
||||||
|
};
|
||||||
|
onCreateCst(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteCst() {
|
||||||
|
if (!schema || selected.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onDeleteCst(selected, () => {
|
||||||
|
setHidden([]);
|
||||||
|
setSelectedHidden([]);
|
||||||
|
setToggleResetSelection(prev => !prev);
|
||||||
|
setToggleDataUpdate(prev => !prev);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChangeLayout(newLayout: LayoutTypes) {
|
||||||
|
if (newLayout === layout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLayout(newLayout);
|
||||||
|
setTimeout(() => {
|
||||||
|
setToggleResetView(prev => !prev);
|
||||||
|
}, TIMEOUT_GRAPH_REFRESH);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChangeParams = useCallback(
|
||||||
|
(params: GraphFilterParams) => {
|
||||||
|
setFilterParams(params);
|
||||||
|
}, [setFilterParams]);
|
||||||
|
|
||||||
|
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||||
|
// Hotkeys implementation
|
||||||
|
if (!isEditable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.key === 'Delete') {
|
||||||
|
event.preventDefault();
|
||||||
|
handleDeleteCst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div tabIndex={-1} onKeyDown={handleKeyDown}>
|
||||||
|
{showParamsDialog ?
|
||||||
|
<DlgGraphParams
|
||||||
|
hideWindow={() => setShowParamsDialog(false)}
|
||||||
|
initial={filterParams}
|
||||||
|
onConfirm={handleChangeParams}
|
||||||
|
/> : null}
|
||||||
|
|
||||||
|
{selected.length > 0 ?
|
||||||
|
<div className='relative w-full z-pop'>
|
||||||
|
<div className='absolute top-0 left-0 px-2 select-none whitespace-nowrap small-caps clr-app'>
|
||||||
|
Выбор {selected.length} из {schema?.stats?.count_all ?? 0}
|
||||||
|
</div>
|
||||||
|
</div> : null}
|
||||||
|
|
||||||
|
<GraphToolbar
|
||||||
|
editorMode={isEditable}
|
||||||
|
nothingSelected={nothingSelected}
|
||||||
|
is3D={is3D}
|
||||||
|
orbit={orbit}
|
||||||
|
noText={filterParams.noText}
|
||||||
|
|
||||||
|
showParamsDialog={() => setShowParamsDialog(true)}
|
||||||
|
onCreate={handleCreateCst}
|
||||||
|
onDelete={handleDeleteCst}
|
||||||
|
onResetViewpoint={() => setToggleResetView(prev => !prev)}
|
||||||
|
|
||||||
|
toggleOrbit={() => setOrbit(prev => !prev)}
|
||||||
|
toggleNoText={() => setFilterParams(
|
||||||
|
(prev) => ({
|
||||||
|
...prev,
|
||||||
|
noText: !prev.noText
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{hoverCst ?
|
||||||
|
<div className='relative'>
|
||||||
|
<InfoConstituenta
|
||||||
|
data={hoverCst}
|
||||||
|
className='absolute top-[1.6rem] left-[2.6rem] z-tooltip w-[25rem] min-h-[11rem] shadow-md overflow-y-auto border h-fit clr-app px-3'
|
||||||
|
/>
|
||||||
|
</div> : null}
|
||||||
|
|
||||||
|
<div className='relative z-pop'>
|
||||||
|
<div className='absolute top-0 left-0 flex flex-col max-w-[13.5rem] min-w-[13.5rem]'>
|
||||||
|
<GraphSidebar
|
||||||
|
coloring={coloringScheme}
|
||||||
|
layout={layout}
|
||||||
|
setLayout={handleChangeLayout}
|
||||||
|
setColoring={setColoringScheme}
|
||||||
|
/>
|
||||||
|
<ViewHidden
|
||||||
|
items={hidden}
|
||||||
|
selected={selectedHidden}
|
||||||
|
schema={schema!}
|
||||||
|
coloringScheme={coloringScheme}
|
||||||
|
toggleSelection={toggleDismissed}
|
||||||
|
onEdit={onOpenEdit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TermGraph
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
layout={layout}
|
||||||
|
is3D={is3D}
|
||||||
|
orbit={orbit}
|
||||||
|
|
||||||
|
setSelected={setSelectedGraph}
|
||||||
|
setHoverID={setHoverID}
|
||||||
|
onEdit={onOpenEdit}
|
||||||
|
onDeselect={() => setSelectedHidden([])}
|
||||||
|
|
||||||
|
toggleResetView={toggleResetView}
|
||||||
|
toggleResetSelection={toggleResetSelection}
|
||||||
|
/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditorTermGraph;
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { LayoutTypes } from 'reagraph';
|
||||||
|
|
||||||
|
import SelectSingle from '../../../components/Common/SelectSingle';
|
||||||
|
import { GraphColoringScheme } from '../../../models/miscelanious';
|
||||||
|
import { mapLabelColoring, mapLableLayout } from '../../../utils/labels';
|
||||||
|
import { SelectorGraphColoring, SelectorGraphLayout } from '../../../utils/selectors';
|
||||||
|
|
||||||
|
interface GraphSidebarProps {
|
||||||
|
coloring: GraphColoringScheme
|
||||||
|
layout: LayoutTypes
|
||||||
|
|
||||||
|
setLayout: (newValue: LayoutTypes) => void
|
||||||
|
setColoring: (newValue: GraphColoringScheme) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function GraphSidebar({
|
||||||
|
coloring, setColoring,
|
||||||
|
layout, setLayout
|
||||||
|
} : GraphSidebarProps) {
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col px-2 pb-2 mt-8 text-sm select-none h-fit'>
|
||||||
|
<div className='flex items-center w-full gap-1 text-sm'>
|
||||||
|
<SelectSingle
|
||||||
|
placeholder='Выберите цвет'
|
||||||
|
options={SelectorGraphColoring}
|
||||||
|
isSearchable={false}
|
||||||
|
value={coloring ? { value: coloring, label: mapLabelColoring.get(coloring) } : null}
|
||||||
|
onChange={data => setColoring(data?.value ?? SelectorGraphColoring[0].value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<SelectSingle
|
||||||
|
placeholder='Способ расположения'
|
||||||
|
className='w-full mt-1'
|
||||||
|
options={SelectorGraphLayout}
|
||||||
|
isSearchable={false}
|
||||||
|
value={layout ? { value: layout, label: mapLableLayout.get(layout) } : null}
|
||||||
|
onChange={data => setLayout(data?.value ?? SelectorGraphLayout[0].value)}
|
||||||
|
/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GraphSidebar;
|
|
@ -0,0 +1,76 @@
|
||||||
|
import ConceptTooltip from '../../../components/Common/ConceptTooltip'
|
||||||
|
import MiniButton from '../../../components/Common/MiniButton'
|
||||||
|
import HelpTermGraph from '../../../components/Help/HelpTermGraph'
|
||||||
|
import { ArrowsFocusIcon, DumpBinIcon, FilterIcon, HelpIcon, LetterAIcon, LetterALinesIcon, PlanetIcon, SmallPlusIcon } from '../../../components/Icons'
|
||||||
|
|
||||||
|
interface GraphToolbarProps {
|
||||||
|
editorMode: boolean
|
||||||
|
nothingSelected: boolean
|
||||||
|
is3D: boolean
|
||||||
|
|
||||||
|
orbit: boolean
|
||||||
|
noText: boolean
|
||||||
|
|
||||||
|
showParamsDialog: () => void
|
||||||
|
onCreate: () => void
|
||||||
|
onDelete: () => void
|
||||||
|
onResetViewpoint: () => void
|
||||||
|
|
||||||
|
toggleNoText: () => void
|
||||||
|
toggleOrbit: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function GraphToolbar({
|
||||||
|
editorMode, nothingSelected, is3D,
|
||||||
|
noText, toggleNoText,
|
||||||
|
orbit, toggleOrbit,
|
||||||
|
showParamsDialog,
|
||||||
|
onCreate, onDelete, onResetViewpoint
|
||||||
|
} : GraphToolbarProps) {
|
||||||
|
return (
|
||||||
|
<div className='relative w-full z-pop'>
|
||||||
|
<div className='absolute right-0 flex items-start justify-center w-full top-1'>
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Настройки фильтрации узлов и связей'
|
||||||
|
icon={<FilterIcon color='text-primary' size={5} />}
|
||||||
|
onClick={showParamsDialog} />
|
||||||
|
<MiniButton
|
||||||
|
tooltip={!noText ? 'Скрыть текст' : 'Отобразить текст'}
|
||||||
|
icon={
|
||||||
|
!noText
|
||||||
|
? <LetterALinesIcon color='text-success' size={5} />
|
||||||
|
: <LetterAIcon color='text-primary' size={5} />
|
||||||
|
}
|
||||||
|
onClick={toggleNoText} />
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Новая конституента'
|
||||||
|
icon={<SmallPlusIcon color={editorMode ? 'text-success' : ''} size={5} />}
|
||||||
|
disabled={!editorMode}
|
||||||
|
onClick={onCreate} />
|
||||||
|
<MiniButton
|
||||||
|
tooltip='Удалить выбранные'
|
||||||
|
icon={<DumpBinIcon color={editorMode && !nothingSelected ? 'text-warning' : ''} size={5} />}
|
||||||
|
disabled={!editorMode || nothingSelected}
|
||||||
|
onClick={onDelete} />
|
||||||
|
<MiniButton
|
||||||
|
icon={<ArrowsFocusIcon color='text-primary' size={5} />}
|
||||||
|
tooltip='Восстановить камеру'
|
||||||
|
onClick={onResetViewpoint} />
|
||||||
|
<MiniButton
|
||||||
|
icon={<PlanetIcon color={!is3D ? '' : orbit ? 'text-success' : 'text-primary'} size={5} />}
|
||||||
|
tooltip='Анимация вращения'
|
||||||
|
disabled={!is3D}
|
||||||
|
onClick={toggleOrbit} />
|
||||||
|
<div className='px-1 py-1' id='items-graph-help'>
|
||||||
|
<HelpIcon color='text-primary' size={5} />
|
||||||
|
</div>
|
||||||
|
<ConceptTooltip anchorSelect='#items-graph-help'>
|
||||||
|
<div className='text-sm max-w-[calc(100vw-20rem)] z-tooltip'>
|
||||||
|
<HelpTermGraph />
|
||||||
|
</div>
|
||||||
|
</ConceptTooltip>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GraphToolbar;
|
|
@ -0,0 +1,139 @@
|
||||||
|
import { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
|
||||||
|
import { GraphCanvas, GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, Sphere, useSelection } from 'reagraph';
|
||||||
|
|
||||||
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||||
|
import { graphDarkT, graphLightT } from '../../../utils/color';
|
||||||
|
import { resources } from '../../../utils/constants';
|
||||||
|
|
||||||
|
interface TermGraphProps {
|
||||||
|
nodes: GraphNode[]
|
||||||
|
edges: GraphEdge[]
|
||||||
|
|
||||||
|
layout: LayoutTypes
|
||||||
|
is3D: boolean
|
||||||
|
orbit: boolean
|
||||||
|
|
||||||
|
setSelected: (selected: number[]) => void
|
||||||
|
setHoverID: (newID: number | undefined) => void
|
||||||
|
onEdit: (cstID: number) => void
|
||||||
|
onDeselect: () => void
|
||||||
|
|
||||||
|
toggleResetView: boolean
|
||||||
|
toggleResetSelection: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const TREE_SIZE_MILESTONE = 50;
|
||||||
|
|
||||||
|
function TermGraph({
|
||||||
|
nodes, edges,
|
||||||
|
layout, is3D, orbit,
|
||||||
|
toggleResetView, toggleResetSelection,
|
||||||
|
setHoverID, onEdit,
|
||||||
|
setSelected, onDeselect
|
||||||
|
} : TermGraphProps) {
|
||||||
|
const { noNavigation, darkMode } = useConceptTheme();
|
||||||
|
const graphRef = useRef<GraphCanvasRef | null>(null);
|
||||||
|
|
||||||
|
const {
|
||||||
|
selections, actives,
|
||||||
|
onNodeClick,
|
||||||
|
clearSelections,
|
||||||
|
onCanvasClick,
|
||||||
|
onNodePointerOver,
|
||||||
|
onNodePointerOut
|
||||||
|
} = useSelection({
|
||||||
|
ref: graphRef,
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
type: 'multi', // 'single' | 'multi' | 'multiModifier'
|
||||||
|
pathSelectionType: 'out',
|
||||||
|
pathHoverType: 'all',
|
||||||
|
focusOnSelect: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleHoverIn = useCallback(
|
||||||
|
(node: GraphNode) => {
|
||||||
|
setHoverID(Number(node.id));
|
||||||
|
if (onNodePointerOver) onNodePointerOver(node);
|
||||||
|
}, [onNodePointerOver, setHoverID]);
|
||||||
|
|
||||||
|
const handleHoverOut = useCallback(
|
||||||
|
(node: GraphNode) => {
|
||||||
|
setHoverID(undefined);
|
||||||
|
if (onNodePointerOut) onNodePointerOut(node);
|
||||||
|
}, [onNodePointerOut, setHoverID]);
|
||||||
|
|
||||||
|
const handleNodeClick = useCallback(
|
||||||
|
(node: GraphNode) => {
|
||||||
|
if (selections.includes(node.id)) {
|
||||||
|
onEdit(Number(node.id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (onNodeClick) onNodeClick(node);
|
||||||
|
}, [onNodeClick, selections, onEdit]);
|
||||||
|
|
||||||
|
const handleCanvasClick = useCallback(
|
||||||
|
(event: MouseEvent) => {
|
||||||
|
onDeselect();
|
||||||
|
if (onCanvasClick) onCanvasClick(event);
|
||||||
|
}, [onCanvasClick, onDeselect]);
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
graphRef.current?.resetControls(true);
|
||||||
|
graphRef.current?.centerGraph();
|
||||||
|
}, [toggleResetView]);
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
clearSelections();
|
||||||
|
}, [toggleResetSelection, clearSelections]);
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
setSelected(selections.map(id => Number(id)));
|
||||||
|
}, [selections, setSelected]);
|
||||||
|
|
||||||
|
const canvasWidth = useMemo(
|
||||||
|
() => {
|
||||||
|
return 'calc(100vw - 1.1rem)';
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const canvasHeight = useMemo(
|
||||||
|
() => {
|
||||||
|
return !noNavigation ?
|
||||||
|
'calc(100vh - 9.8rem - 4px)'
|
||||||
|
: 'calc(100vh - 3rem - 4px)';
|
||||||
|
}, [noNavigation]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full overflow-auto outline-none'>
|
||||||
|
<div className='relative' style={{width: canvasWidth, height: canvasHeight}}>
|
||||||
|
<GraphCanvas
|
||||||
|
draggable
|
||||||
|
ref={graphRef}
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
layoutType={layout}
|
||||||
|
selections={selections}
|
||||||
|
actives={actives}
|
||||||
|
onNodeClick={handleNodeClick}
|
||||||
|
onCanvasClick={handleCanvasClick}
|
||||||
|
onNodePointerOver={handleHoverIn}
|
||||||
|
onNodePointerOut={handleHoverOut}
|
||||||
|
cameraMode={ orbit ? 'orbit' : is3D ? 'rotate' : 'pan'}
|
||||||
|
layoutOverrides={
|
||||||
|
layout.includes('tree') ? { nodeLevelRatio: nodes.length < TREE_SIZE_MILESTONE ? 3 : 1 }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
labelFontUrl={resources.graph_font}
|
||||||
|
theme={darkMode ? graphDarkT : graphLightT}
|
||||||
|
renderNode={({ node, ...rest }) => (
|
||||||
|
<Sphere {...rest} node={node} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TermGraph;
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
|
import ConstituentaTooltip from '../../../components/Help/ConstituentaTooltip';
|
||||||
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||||
|
import { GraphColoringScheme } from '../../../models/miscelanious';
|
||||||
|
import { IRSForm } from '../../../models/rsform';
|
||||||
|
import { colorbgGraphNode } from '../../../utils/color';
|
||||||
|
import { prefixes } from '../../../utils/constants';
|
||||||
|
|
||||||
|
interface ViewHiddenProps {
|
||||||
|
items: number[]
|
||||||
|
selected: number[]
|
||||||
|
schema: IRSForm
|
||||||
|
coloringScheme: GraphColoringScheme
|
||||||
|
|
||||||
|
toggleSelection: (cstID: number) => void
|
||||||
|
onEdit: (cstID: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function ViewHidden({
|
||||||
|
items,
|
||||||
|
selected, toggleSelection,
|
||||||
|
schema, coloringScheme,
|
||||||
|
onEdit
|
||||||
|
} : ViewHiddenProps) {
|
||||||
|
const { colors, noNavigation } = useConceptTheme();
|
||||||
|
|
||||||
|
const dismissedHeight = useMemo(
|
||||||
|
() => {
|
||||||
|
return !noNavigation ?
|
||||||
|
'calc(100vh - 28rem - 4px)'
|
||||||
|
: 'calc(100vh - 22.2rem - 4px)';
|
||||||
|
}, [noNavigation]);
|
||||||
|
|
||||||
|
const dismissedStyle = useCallback(
|
||||||
|
(cstID: number) => {
|
||||||
|
return selected.includes(cstID) ? {outlineWidth: '2px', outlineStyle: 'solid'}: {};
|
||||||
|
}, [selected]);
|
||||||
|
|
||||||
|
if (items.length <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col text-sm ml-2 border clr-app max-w-[12.5rem] min-w-[12.5rem]'>
|
||||||
|
<p className='pt-2 text-center'><b>Скрытые конституенты</b></p>
|
||||||
|
<div className='flex flex-wrap justify-center gap-2 py-2 overflow-y-auto' style={{ maxHeight: dismissedHeight }}>
|
||||||
|
{items.map(
|
||||||
|
(cstID) => {
|
||||||
|
const cst = schema.items.find(cst => cst.id === cstID)!;
|
||||||
|
const adjustedColoring = coloringScheme === 'none' ? 'status' : coloringScheme;
|
||||||
|
const id = `${prefixes.cst_hidden_list}${cst.alias}`;
|
||||||
|
return (
|
||||||
|
<div key={`wrap-${id}`}>
|
||||||
|
<div key={id} id={id}
|
||||||
|
className='w-fit min-w-[3rem] rounded-md text-center cursor-pointer select-none'
|
||||||
|
style={{
|
||||||
|
backgroundColor: colorbgGraphNode(cst, adjustedColoring, colors),
|
||||||
|
...dismissedStyle(cstID)
|
||||||
|
}}
|
||||||
|
onClick={() => toggleSelection(cstID)}
|
||||||
|
onDoubleClick={() => onEdit(cstID)}
|
||||||
|
>
|
||||||
|
{cst.alias}
|
||||||
|
</div>
|
||||||
|
<ConstituentaTooltip
|
||||||
|
data={cst}
|
||||||
|
anchor={`#${id}`} />
|
||||||
|
</div>);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ViewHidden;
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './EditorTermGraph';
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { GraphFilterParams } from '../../../models/miscelanious';
|
||||||
|
import { CstType, IRSForm } from '../../../models/rsform';
|
||||||
|
import { Graph } from '../../../utils/Graph';
|
||||||
|
|
||||||
|
function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams, toggleUpdate: boolean) {
|
||||||
|
const [ filtered, setFiltered ] = useState<Graph>(new Graph());
|
||||||
|
|
||||||
|
const allowedTypes: CstType[] = useMemo(
|
||||||
|
() => {
|
||||||
|
const result: CstType[] = [];
|
||||||
|
if (params.allowBase) result.push(CstType.BASE);
|
||||||
|
if (params.allowStruct) result.push(CstType.STRUCTURED);
|
||||||
|
if (params.allowTerm) result.push(CstType.TERM);
|
||||||
|
if (params.allowAxiom) result.push(CstType.AXIOM);
|
||||||
|
if (params.allowFunction) result.push(CstType.FUNCTION);
|
||||||
|
if (params.allowPredicate) result.push(CstType.PREDICATE);
|
||||||
|
if (params.allowConstant) result.push(CstType.CONSTANT);
|
||||||
|
if (params.allowTheorem) result.push(CstType.THEOREM);
|
||||||
|
return result;
|
||||||
|
}, [params]);
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
if (!schema) {
|
||||||
|
setFiltered(new Graph());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const graph = schema.graph.clone();
|
||||||
|
if (params.noHermits) {
|
||||||
|
graph.removeIsolated();
|
||||||
|
}
|
||||||
|
if (params.noTransitive) {
|
||||||
|
graph.transitiveReduction();
|
||||||
|
}
|
||||||
|
if (params.noTemplates) {
|
||||||
|
schema.items.forEach(cst => {
|
||||||
|
if (cst.is_template) {
|
||||||
|
graph.foldNode(cst.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (allowedTypes.length < Object.values(CstType).length) {
|
||||||
|
schema.items.forEach(cst => {
|
||||||
|
if (!allowedTypes.includes(cst.cst_type)) {
|
||||||
|
graph.foldNode(cst.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setFiltered(graph);
|
||||||
|
}, [schema, params, allowedTypes, toggleUpdate]);
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useGraphFilter;
|
|
@ -27,10 +27,10 @@ import { SyntaxTree } from '../../models/rslang';
|
||||||
import { EXTEOR_TRS_FILE, prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
|
import { EXTEOR_TRS_FILE, prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
|
||||||
import { createAliasFor } from '../../utils/misc';
|
import { createAliasFor } from '../../utils/misc';
|
||||||
import EditorConstituenta from './EditorConstituenta';
|
import EditorConstituenta from './EditorConstituenta';
|
||||||
import EditorItems from './EditorItems';
|
|
||||||
import EditorRSForm from './EditorRSForm';
|
import EditorRSForm from './EditorRSForm';
|
||||||
|
import EditorRSList from './EditorRSList';
|
||||||
import EditorTermGraph from './EditorTermGraph';
|
import EditorTermGraph from './EditorTermGraph';
|
||||||
import RSTabsMenu from './elements/RSTabsMenu';
|
import RSTabsMenu from './RSTabsMenu';
|
||||||
|
|
||||||
export enum RSTabID {
|
export enum RSTabID {
|
||||||
CARD = 0,
|
CARD = 0,
|
||||||
|
@ -425,7 +425,7 @@ function RSTabs() {
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_LIST ? '': 'none' }}>
|
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_LIST ? '': 'none' }}>
|
||||||
<EditorItems
|
<EditorRSList
|
||||||
onOpenEdit={onOpenCst}
|
onOpenEdit={onOpenCst}
|
||||||
onCreateCst={promptCreateCst}
|
onCreateCst={promptCreateCst}
|
||||||
onDeleteCst={promptDeleteCst}
|
onDeleteCst={promptDeleteCst}
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import Button from '../../../components/Common/Button';
|
import Button from '../../components/Common/Button';
|
||||||
import Dropdown from '../../../components/Common/Dropdown';
|
import Dropdown from '../../components/Common/Dropdown';
|
||||||
import DropdownButton from '../../../components/Common/DropdownButton';
|
import DropdownButton from '../../components/Common/DropdownButton';
|
||||||
import DropdownCheckbox from '../../../components/Common/DropdownCheckbox';
|
import DropdownCheckbox from '../../components/Common/DropdownCheckbox';
|
||||||
import {
|
import {
|
||||||
CloneIcon, DownloadIcon, DumpBinIcon, EditIcon, MenuIcon, NotSubscribedIcon,
|
CloneIcon, DownloadIcon, DumpBinIcon, EditIcon, MenuIcon, NotSubscribedIcon,
|
||||||
OwnerIcon, ShareIcon, SmallPlusIcon, SubscribedIcon, UploadIcon
|
OwnerIcon, ShareIcon, SmallPlusIcon, SubscribedIcon, UploadIcon
|
||||||
} from '../../../components/Icons';
|
} from '../../components/Icons';
|
||||||
import { useAuth } from '../../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useRSForm } from '../../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import useDropdown from '../../../hooks/useDropdown';
|
import useDropdown from '../../hooks/useDropdown';
|
||||||
|
|
||||||
interface RSTabsMenuProps {
|
interface RSTabsMenuProps {
|
||||||
showUploadDialog: () => void
|
showUploadDialog: () => void
|
||||||
showCloneDialog: () => void
|
showCloneDialog: () => void
|
||||||
|
|
||||||
onDestroy: () => void
|
onDestroy: () => void
|
||||||
onClaim: () => void
|
onClaim: () => void
|
||||||
onShare: () => void
|
onShare: () => void
|
|
@ -3,7 +3,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { GramData, Grammeme, NounGrams, PartOfSpeech, VerbGrams } from '../models/language'
|
import { GramData, Grammeme, NounGrams, PartOfSpeech, VerbGrams } from '../models/language'
|
||||||
import { CstClass, ExpressionStatus } from '../models/rsform'
|
import { GraphColoringScheme } from '../models/miscelanious'
|
||||||
|
import { CstClass, ExpressionStatus, IConstituenta } from '../models/rsform'
|
||||||
import { ISyntaxTreeNode, TokenID } from '../models/rslang'
|
import { ISyntaxTreeNode, TokenID } from '../models/rslang'
|
||||||
|
|
||||||
|
|
||||||
|
@ -454,3 +455,16 @@ export function colorfgGrammeme(gram: GramData, colors: IColorTheme): string {
|
||||||
return colors.fgPurple;
|
return colors.fgPurple;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines graph color for {@link IConstituenta}.
|
||||||
|
*/
|
||||||
|
export function colorbgGraphNode(cst: IConstituenta, coloringScheme: GraphColoringScheme, colors: IColorTheme): string {
|
||||||
|
if (coloringScheme === 'type') {
|
||||||
|
return colorbgCstClass(cst.cst_class, colors);
|
||||||
|
}
|
||||||
|
if (coloringScheme === 'status') {
|
||||||
|
return colorbgCstStatus(cst.status, colors);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
|
@ -5,8 +5,8 @@ import { LayoutTypes } from 'reagraph';
|
||||||
|
|
||||||
import { type GramData, Grammeme, ReferenceType } from '../models/language';
|
import { type GramData, Grammeme, ReferenceType } from '../models/language';
|
||||||
import { grammemeCompare } from '../models/languageAPI';
|
import { grammemeCompare } from '../models/languageAPI';
|
||||||
|
import { GraphColoringScheme } from '../models/miscelanious';
|
||||||
import { CstType } from '../models/rsform';
|
import { CstType } from '../models/rsform';
|
||||||
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
|
|
||||||
import { labelGrammeme, labelReferenceType } from './labels';
|
import { labelGrammeme, labelReferenceType } from './labels';
|
||||||
import { labelCstType } from './labels';
|
import { labelCstType } from './labels';
|
||||||
|
|
||||||
|
@ -30,9 +30,9 @@ export const SelectorGraphLayout: { value: LayoutTypes, label: string }[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents options for {@link ColoringScheme} selector.
|
* Represents options for {@link GraphColoringScheme} selector.
|
||||||
*/
|
*/
|
||||||
export const SelectorGraphColoring: { value: ColoringScheme, label: string }[] = [
|
export const SelectorGraphColoring: { value: GraphColoringScheme, label: string }[] = [
|
||||||
{ value: 'none', label: 'Цвет: моно' },
|
{ value: 'none', label: 'Цвет: моно' },
|
||||||
{ value: 'status', label: 'Цвет: статус' },
|
{ value: 'status', label: 'Цвет: статус' },
|
||||||
{ value: 'type', label: 'Цвет: класс' },
|
{ value: 'type', label: 'Цвет: класс' },
|
||||||
|
|
Loading…
Reference in New Issue
Block a user