Add focus cst UI

This commit is contained in:
IRBorisov 2024-04-09 13:47:18 +03:00
parent 85d756309e
commit 4d72690234
12 changed files with 286 additions and 129 deletions

View File

@ -6,7 +6,7 @@ function HelpConstituenta() {
return ( return (
<div className='dense'> <div className='dense'>
<h1>Редактор конституент</h1> <h1>Редактор конституент</h1>
<p>При выделении также подсвечиваются производные и основание</p> <p>Помимо активной конституенты выделяются порожденные и основание</p>
<p><b>Сохранить изменения</b>: Ctrl + S или клик по кнопке Сохранить</p> <p><b>Сохранить изменения</b>: Ctrl + S или клик по кнопке Сохранить</p>
<p className='mt-1'><b>Формальное определение</b></p> <p className='mt-1'><b>Формальное определение</b></p>
<p>- Ctrl + Пробел дополняет до незанятого имени</p> <p>- Ctrl + Пробел дополняет до незанятого имени</p>

View File

@ -56,8 +56,8 @@ function DlgGraphParams({ hideWindow, initial, onConfirm }: DlgGraphParamsProps)
setValue={value => updateParams({ noTransitive: value })} setValue={value => updateParams({ noTransitive: value })}
/> />
<Checkbox <Checkbox
label='Свернуть производные' label='Свернуть порожденные'
title='Не отображать производные понятия' title='Не отображать порожденные понятия'
value={params.foldDerived} value={params.foldDerived}
setValue={value => updateParams({ foldDerived: value })} setValue={value => updateParams({ foldDerived: value })}
/> />

View File

@ -116,6 +116,7 @@ export class Graph {
const result: GraphNode[] = []; const result: GraphNode[] = [];
this.nodes.forEach(node => { this.nodes.forEach(node => {
if (node.outputs.length === 0 && node.inputs.length === 0) { if (node.outputs.length === 0 && node.inputs.length === 0) {
result.push(node);
this.nodes.delete(node.id); this.nodes.delete(node.id);
} }
}); });

View File

@ -103,6 +103,9 @@ export interface GraphFilterParams {
noText: boolean; noText: boolean;
foldDerived: boolean; foldDerived: boolean;
focusShowInputs: boolean;
focusShowOutputs: boolean;
allowBase: boolean; allowBase: boolean;
allowStruct: boolean; allowStruct: boolean;
allowTerm: boolean; allowTerm: boolean;

View File

@ -7,6 +7,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import InfoConstituenta from '@/components/info/InfoConstituenta'; import InfoConstituenta from '@/components/info/InfoConstituenta';
import SelectedCounter from '@/components/info/SelectedCounter'; import SelectedCounter from '@/components/info/SelectedCounter';
import SelectGraphToolbar from '@/components/select/SelectGraphToolbar';
import { GraphCanvasRef, GraphEdge, GraphLayout, GraphNode } from '@/components/ui/GraphUI'; import { GraphCanvasRef, GraphEdge, GraphLayout, GraphNode } from '@/components/ui/GraphUI';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { useConceptOptions } from '@/context/OptionsContext'; import { useConceptOptions } from '@/context/OptionsContext';
@ -14,12 +15,14 @@ import DlgGraphParams from '@/dialogs/DlgGraphParams';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import { GraphColoring, GraphFilterParams, GraphSizing } from '@/models/miscellaneous'; import { GraphColoring, GraphFilterParams, GraphSizing } from '@/models/miscellaneous';
import { applyNodeSizing } from '@/models/miscellaneousAPI'; import { applyNodeSizing } from '@/models/miscellaneousAPI';
import { ConstituentaID, CstType } from '@/models/rsform'; import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform';
import { isBasicConcept } from '@/models/rsformAPI';
import { colorBgGraphNode } from '@/styling/color'; import { colorBgGraphNode } from '@/styling/color';
import { PARAMETER, storage } from '@/utils/constants'; import { PARAMETER, storage } from '@/utils/constants';
import { convertBase64ToBlob } from '@/utils/utils'; import { convertBase64ToBlob } from '@/utils/utils';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
import FocusToolbar from './FocusToolbar';
import GraphSelectors from './GraphSelectors'; import GraphSelectors from './GraphSelectors';
import GraphToolbar from './GraphToolbar'; import GraphToolbar from './GraphToolbar';
import TermGraph from './TermGraph'; import TermGraph from './TermGraph';
@ -41,6 +44,9 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
noText: false, noText: false,
foldDerived: false, foldDerived: false,
focusShowInputs: false,
focusShowOutputs: false,
allowBase: true, allowBase: true,
allowStruct: true, allowStruct: true,
allowTerm: true, allowTerm: true,
@ -51,7 +57,8 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
allowTheorem: true allowTheorem: true
}); });
const [showParamsDialog, setShowParamsDialog] = useState(false); const [showParamsDialog, setShowParamsDialog] = useState(false);
const filtered = useGraphFilter(controller.schema, filterParams); const [focusCst, setFocusCst] = useState<IConstituenta | undefined>(undefined);
const filtered = useGraphFilter(controller.schema, filterParams, focusCst);
const graphRef = useRef<GraphCanvasRef | null>(null); const graphRef = useRef<GraphCanvasRef | null>(null);
const [hidden, setHidden] = useState<ConstituentaID[]>([]); const [hidden, setHidden] = useState<ConstituentaID[]>([]);
@ -93,7 +100,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
if (cst) { if (cst) {
result.push({ result.push({
id: String(node.id), id: String(node.id),
fill: colorBgGraphNode(cst, coloring, colors), fill: focusCst === cst ? colors.bgPurple : colorBgGraphNode(cst, coloring, colors),
label: cst.alias, label: cst.alias,
subLabel: !filterParams.noText ? cst.term_resolved : undefined, subLabel: !filterParams.noText ? cst.term_resolved : undefined,
size: applyNodeSizing(cst, sizing) size: applyNodeSizing(cst, sizing)
@ -101,23 +108,25 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
} }
}); });
return result; return result;
}, [controller.schema, coloring, sizing, filtered.nodes, filterParams.noText, colors]); }, [controller.schema, coloring, sizing, filtered.nodes, filterParams.noText, colors, focusCst]);
const edges: GraphEdge[] = useMemo(() => { const edges: GraphEdge[] = useMemo(() => {
const result: GraphEdge[] = []; const result: GraphEdge[] = [];
let edgeID = 1; let edgeID = 1;
filtered.nodes.forEach(source => { filtered.nodes.forEach(source => {
source.outputs.forEach(target => { source.outputs.forEach(target => {
if (nodes.find(node => node.id === String(target))) {
result.push({ result.push({
id: String(edgeID), id: String(edgeID),
source: String(source.id), source: String(source.id),
target: String(target) target: String(target)
}); });
edgeID += 1; edgeID += 1;
}
}); });
}); });
return result; return result;
}, [filtered.nodes]); }, [filtered.nodes, nodes]);
function handleCreateCst() { function handleCreateCst() {
if (!controller.schema) { if (!controller.schema) {
@ -174,6 +183,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
} }
if (event.key === 'Escape') { if (event.key === 'Escape') {
event.preventDefault(); event.preventDefault();
setFocusCst(undefined);
controller.deselectAll(); controller.deselectAll();
} }
} }
@ -188,6 +198,17 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
}, PARAMETER.graphRefreshDelay); }, PARAMETER.graphRefreshDelay);
}, [setFilterParams, setToggleResetView]); }, [setFilterParams, setToggleResetView]);
const handleSetFocus = useCallback(
(cstID: ConstituentaID | undefined) => {
const target = cstID !== undefined ? controller.schema?.cstByID.get(cstID) : cstID;
setFocusCst(prev => (prev === target ? undefined : target));
if (target) {
controller.setSelected([]);
}
},
[controller]
);
const graph = useMemo( const graph = useMemo(
() => ( () => (
<TermGraph <TermGraph
@ -202,6 +223,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
onDeselect={controller.deselect} onDeselect={controller.deselect}
setHoverID={setHoverID} setHoverID={setHoverID}
onEdit={onOpenEdit} onEdit={onOpenEdit}
onSelectCentral={handleSetFocus}
toggleResetView={toggleResetView} toggleResetView={toggleResetView}
/> />
), ),
@ -217,7 +239,8 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
onOpenEdit, onOpenEdit,
toggleResetView, toggleResetView,
controller.select, controller.select,
controller.deselect controller.deselect,
handleSetFocus
] ]
); );
@ -240,6 +263,10 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
position='top-[4.3rem] sm:top-[0.3rem] left-0' position='top-[4.3rem] sm:top-[0.3rem] left-0'
/> />
<Overlay
position='top-0 pt-1 right-1/2 translate-x-1/2'
className='flex flex-col items-center rounded-b-2xl cc-blur'
>
<GraphToolbar <GraphToolbar
is3D={is3D} is3D={is3D}
orbit={orbit} orbit={orbit}
@ -259,6 +286,34 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
})) }))
} }
/> />
{!focusCst ? (
<SelectGraphToolbar
graph={controller.schema!.graph}
core={controller.schema!.items.filter(cst => isBasicConcept(cst.cst_type)).map(cst => cst.id)}
setSelected={controller.setSelected}
/>
) : null}
{focusCst ? (
<FocusToolbar
center={focusCst}
reset={() => handleSetFocus(undefined)}
showInputs={filterParams.focusShowInputs}
showOutputs={filterParams.focusShowOutputs}
toggleShowInputs={() =>
setFilterParams(prev => ({
...prev,
focusShowInputs: !prev.focusShowInputs
}))
}
toggleShowOutputs={() =>
setFilterParams(prev => ({
...prev,
focusShowOutputs: !prev.focusShowOutputs
}))
}
/>
) : null}
</Overlay>
{hoverCst ? ( {hoverCst ? (
<Overlay <Overlay
@ -286,6 +341,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
schema={controller.schema} schema={controller.schema}
coloringScheme={coloring} coloringScheme={coloring}
toggleSelection={controller.toggleSelect} toggleSelection={controller.toggleSelect}
setFocus={handleSetFocus}
onEdit={onOpenEdit} onEdit={onOpenEdit}
/> />
</div> </div>

View File

@ -0,0 +1,75 @@
'use client';
import { useCallback } from 'react';
import { IconGraphInputs, IconGraphOutputs, IconReset } from '@/components/Icons';
import MiniButton from '@/components/ui/MiniButton';
import { useConceptOptions } from '@/context/OptionsContext';
import { IConstituenta } from '@/models/rsform';
import { useRSEdit } from '../RSEditContext';
interface FocusToolbarProps {
center: IConstituenta;
showInputs: boolean;
showOutputs: boolean;
reset: () => void;
toggleShowInputs: () => void;
toggleShowOutputs: () => void;
}
function FocusToolbar({
center,
reset,
showInputs,
showOutputs,
toggleShowInputs,
toggleShowOutputs
}: FocusToolbarProps) {
const { colors } = useConceptOptions();
const controller = useRSEdit();
const resetSelection = useCallback(() => {
reset();
controller.setSelected([]);
}, [reset, controller]);
return (
<div className='cc-icons items-center'>
<div className='w-[7.8rem] text-right select-none' style={{ color: colors.fgPurple }}>
Фокус
<b className='px-1'> {center.alias} </b>
</div>
<MiniButton
titleHtml='Сбросить фокус'
icon={<IconReset size='1.25rem' className='icon-primary' />}
onClick={resetSelection}
/>
<MiniButton
title={showInputs ? 'Скрыть поставщиков' : 'Отобразить поставщиков'}
icon={
showInputs ? (
<IconGraphInputs size='1.25rem' className='icon-green' />
) : (
<IconGraphInputs size='1.25rem' className='icon-primary' />
)
}
onClick={toggleShowInputs}
/>
<MiniButton
title={showOutputs ? 'Скрыть потребителей' : 'Отобразить потребителей'}
icon={
showOutputs ? (
<IconGraphOutputs size='1.25rem' className='icon-green' />
) : (
<IconGraphOutputs size='1.25rem' className='icon-primary' />
)
}
onClick={toggleShowOutputs}
/>
</div>
);
}
export default FocusToolbar;

View File

@ -1,5 +1,3 @@
'use client';
import { import {
IconClustering, IconClustering,
IconClusteringOff, IconClusteringOff,
@ -13,11 +11,8 @@ import {
IconTextOff IconTextOff
} from '@/components/Icons'; } from '@/components/Icons';
import BadgeHelp from '@/components/man/BadgeHelp'; import BadgeHelp from '@/components/man/BadgeHelp';
import SelectGraphToolbar from '@/components/select/SelectGraphToolbar';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { isBasicConcept } from '@/models/rsformAPI';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
@ -56,10 +51,6 @@ function GraphToolbar({
const controller = useRSEdit(); const controller = useRSEdit();
return ( return (
<Overlay
position='top-0 pt-1 right-1/2 translate-x-1/2'
className='flex flex-col items-center rounded-b-2xl cc-blur'
>
<div className='cc-icons'> <div className='cc-icons'>
<MiniButton <MiniButton
title='Настройки фильтрации узлов и связей' title='Настройки фильтрации узлов и связей'
@ -83,7 +74,7 @@ function GraphToolbar({
onClick={toggleNoText} onClick={toggleNoText}
/> />
<MiniButton <MiniButton
title={!foldDerived ? 'Скрыть производные' : 'Отобразить производные'} title={!foldDerived ? 'Скрыть порожденные' : 'Отобразить порожденные'}
icon={ icon={
!foldDerived ? ( !foldDerived ? (
<IconClustering size='1.25rem' className='icon-green' /> <IconClustering size='1.25rem' className='icon-green' />
@ -122,12 +113,6 @@ function GraphToolbar({
/> />
<BadgeHelp topic={HelpTopic.GRAPH_TERM} className='max-w-[calc(100vw-4rem)]' offset={4} /> <BadgeHelp topic={HelpTopic.GRAPH_TERM} className='max-w-[calc(100vw-4rem)]' offset={4} />
</div> </div>
<SelectGraphToolbar
graph={controller.schema!.graph}
core={controller.schema!.items.filter(cst => isBasicConcept(cst.cst_type)).map(cst => cst.id)}
setSelected={controller.setSelected}
/>
</Overlay>
); );
} }

View File

@ -20,6 +20,7 @@ interface TermGraphProps {
setHoverID: (newID: ConstituentaID | undefined) => void; setHoverID: (newID: ConstituentaID | undefined) => void;
onEdit: (cstID: ConstituentaID) => void; onEdit: (cstID: ConstituentaID) => void;
onSelectCentral: (selectedID: ConstituentaID) => void;
onSelect: (newID: ConstituentaID) => void; onSelect: (newID: ConstituentaID) => void;
onDeselect: (newID: ConstituentaID) => void; onDeselect: (newID: ConstituentaID) => void;
@ -37,9 +38,11 @@ function TermGraph({
toggleResetView, toggleResetView,
setHoverID, setHoverID,
onEdit, onEdit,
onSelectCentral,
onSelect, onSelect,
onDeselect onDeselect
}: TermGraphProps) { }: TermGraphProps) {
let ctrlKey: boolean = false;
const { calculateHeight, darkMode } = useConceptOptions(); const { calculateHeight, darkMode } = useConceptOptions();
const { selections, setSelections } = useSelection({ const { selections, setSelections } = useSelection({
@ -63,13 +66,15 @@ function TermGraph({
const handleNodeClick = useCallback( const handleNodeClick = useCallback(
(node: GraphNode) => { (node: GraphNode) => {
if (selections.includes(node.id)) { if (ctrlKey) {
onSelectCentral(Number(node.id));
} else if (selections.includes(node.id)) {
onDeselect(Number(node.id)); onDeselect(Number(node.id));
} else { } else {
onSelect(Number(node.id)); onSelect(Number(node.id));
} }
}, },
[onSelect, selections, onDeselect] [onSelect, selections, onDeselect, onSelectCentral, ctrlKey]
); );
const handleNodeDoubleClick = useCallback( const handleNodeDoubleClick = useCallback(
@ -96,7 +101,13 @@ function TermGraph({
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]); const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
return ( return (
<div className='outline-none'> <div
className='outline-none'
tabIndex={-1}
// TODO: fix hacky way of tracking CTRL. Expose event from onNodeClick instead
onKeyUp={event => (ctrlKey = event.ctrlKey)}
onKeyDown={event => (ctrlKey = event.ctrlKey)}
>
<div className='relative' style={{ width: canvasWidth, height: canvasHeight }}> <div className='relative' style={{ width: canvasWidth, height: canvasHeight }}>
<GraphUI <GraphUI
nodes={nodes} nodes={nodes}

View File

@ -2,10 +2,11 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { IconDropArrow, IconDropArrowUp } from '@/components/Icons'; import { IconDropArrow, IconDropArrowUp } from '@/components/Icons';
import ConstituentaTooltip from '@/components/info/ConstituentaTooltip'; import ConstituentaTooltip from '@/components/info/ConstituentaTooltip';
import { CProps } from '@/components/props';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { useConceptOptions } from '@/context/OptionsContext'; import { useConceptOptions } from '@/context/OptionsContext';
@ -24,15 +25,27 @@ interface ViewHiddenProps {
coloringScheme: GraphColoring; coloringScheme: GraphColoring;
toggleSelection: (cstID: ConstituentaID) => void; toggleSelection: (cstID: ConstituentaID) => void;
setFocus: (cstID: ConstituentaID) => void;
onEdit: (cstID: ConstituentaID) => void; onEdit: (cstID: ConstituentaID) => void;
} }
function ViewHidden({ items, selected, toggleSelection, schema, coloringScheme, onEdit }: ViewHiddenProps) { function ViewHidden({ items, selected, toggleSelection, setFocus, schema, coloringScheme, onEdit }: ViewHiddenProps) {
const { colors, calculateHeight } = useConceptOptions(); const { colors, calculateHeight } = useConceptOptions();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const localSelected = useMemo(() => items.filter(id => selected.includes(id)), [items, selected]); const localSelected = useMemo(() => items.filter(id => selected.includes(id)), [items, selected]);
const [isFolded, setIsFolded] = useLocalStorage(storage.rsgraphFoldHidden, false); const [isFolded, setIsFolded] = useLocalStorage(storage.rsgraphFoldHidden, false);
const handleClick = useCallback(
(cstID: ConstituentaID, event: CProps.EventMouse) => {
if (event.ctrlKey) {
setFocus(cstID);
} else {
toggleSelection(cstID);
}
},
[setFocus, toggleSelection]
);
if (!schema || items.length <= 0) { if (!schema || items.length <= 0) {
return null; return null;
} }
@ -93,7 +106,7 @@ function ViewHidden({ items, selected, toggleSelection, schema, coloringScheme,
backgroundColor: colorBgGraphNode(cst, adjustedColoring, colors), backgroundColor: colorBgGraphNode(cst, adjustedColoring, colors),
...(localSelected.includes(cstID) ? { outlineWidth: '2px', outlineStyle: 'solid' } : {}) ...(localSelected.includes(cstID) ? { outlineWidth: '2px', outlineStyle: 'solid' } : {})
}} }}
onClick={() => toggleSelection(cstID)} onClick={event => handleClick(cstID, event)}
onDoubleClick={() => onEdit(cstID)} onDoubleClick={() => onEdit(cstID)}
> >
{cst.alias} {cst.alias}

View File

@ -2,9 +2,9 @@ import { useLayoutEffect, useMemo, useState } from 'react';
import { Graph } from '@/models/Graph'; import { Graph } from '@/models/Graph';
import { GraphFilterParams } from '@/models/miscellaneous'; import { GraphFilterParams } from '@/models/miscellaneous';
import { CstType, IRSForm } from '@/models/rsform'; import { ConstituentaID, CstType, IConstituenta, IRSForm } from '@/models/rsform';
function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams) { function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams, focusCst: IConstituenta | undefined) {
const [filtered, setFiltered] = useState<Graph>(new Graph()); const [filtered, setFiltered] = useState<Graph>(new Graph());
const allowedTypes: CstType[] = useMemo(() => { const allowedTypes: CstType[] = useMemo(() => {
@ -29,32 +29,45 @@ function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams)
if (params.noHermits) { if (params.noHermits) {
graph.removeIsolated(); graph.removeIsolated();
} }
if (params.noTransitive) {
graph.transitiveReduction();
}
if (params.noTemplates) { if (params.noTemplates) {
schema.items.forEach(cst => { schema.items.forEach(cst => {
if (cst.is_template) { if (cst !== focusCst && cst.is_template) {
graph.foldNode(cst.id); graph.foldNode(cst.id);
} }
}); });
} }
if (allowedTypes.length < Object.values(CstType).length) { if (allowedTypes.length < Object.values(CstType).length) {
schema.items.forEach(cst => { schema.items.forEach(cst => {
if (!allowedTypes.includes(cst.cst_type)) { if (cst !== focusCst && !allowedTypes.includes(cst.cst_type)) {
graph.foldNode(cst.id); graph.foldNode(cst.id);
} }
}); });
} }
if (params.foldDerived) { if (!focusCst && params.foldDerived) {
schema.items.forEach(cst => { schema.items.forEach(cst => {
if (cst.parent_alias) { if (cst.parent) {
graph.foldNode(cst.id); graph.foldNode(cst.id);
} }
}); });
} }
if (focusCst) {
const includes: ConstituentaID[] = [
focusCst.id,
...focusCst.children,
...(params.focusShowInputs ? schema.graph.expandInputs([focusCst.id]) : []),
...(params.focusShowOutputs ? schema.graph.expandOutputs([focusCst.id]) : [])
];
schema.items.forEach(cst => {
if (!includes.includes(cst.id)) {
graph.foldNode(cst.id);
}
});
}
if (params.noTransitive) {
graph.transitiveReduction();
}
setFiltered(graph); setFiltered(graph);
}, [schema, params, allowedTypes]); }, [schema, params, allowedTypes, focusCst]);
return filtered; return filtered;
} }

View File

@ -145,11 +145,11 @@ function RSTabs() {
const onOpenCst = useCallback( const onOpenCst = useCallback(
(cstID: ConstituentaID) => { (cstID: ConstituentaID) => {
if (cstID !== activeCst?.id) { if (cstID !== activeCst?.id || activeTab !== RSTabID.CST_EDIT) {
navigateTab(RSTabID.CST_EDIT, cstID); navigateTab(RSTabID.CST_EDIT, cstID);
} }
}, },
[navigateTab, activeCst] [navigateTab, activeCst, activeTab]
); );
const onDestroySchema = useCallback(() => { const onDestroySchema = useCallback(() => {

View File

@ -90,7 +90,7 @@ export const storage = {
librarySearchStrategy: 'library.search.strategy', librarySearchStrategy: 'library.search.strategy',
libraryPagination: 'library.pagination', libraryPagination: 'library.pagination',
rsgraphFilter: 'rsgraph.filter_options', rsgraphFilter: 'rsgraph.filter2',
rsgraphLayout: 'rsgraph.layout', rsgraphLayout: 'rsgraph.layout',
rsgraphColoring: 'rsgraph.coloring', rsgraphColoring: 'rsgraph.coloring',
rsgraphSizing: 'rsgraph.sizing', rsgraphSizing: 'rsgraph.sizing',