Fix force layout bug caused when many layout changes then edits
nodes.css, graphSlice.ts, GraphDetails.tsx, BaseMiniNode.tsx, BaseNode.tsx, ContextMenu.tsx, EntityOptions.tsx, Graph.tsx, and index.tsx
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* Start node */
|
||||
.node.container {
|
||||
@apply flex flex-col w-full max-w-sm justify-between rounded-md transition-all duration-150 ease-in-out ring-1 ring-mirage-400/30 backdrop-blur-lg hover:ring-mirage-400/70 to-mirage-700/70 from-slate-800/20 from-30% bg-gradient-to-br hover:shadow-md shadow-sm;
|
||||
@apply flex flex-col w-full max-w-sm justify-between rounded-md transition-all duration-150 ease-in-out ring-1 ring-mirage-400/30 backdrop-blur-lg hover:ring-mirage-400/70 to-mirage-600/50 from-mirage-500/40 from-60% bg-gradient-to-br hover:shadow-md shadow-sm;
|
||||
}
|
||||
|
||||
.node .side-header {
|
||||
@@ -97,7 +97,7 @@ p.node-label {
|
||||
/* End node context menu */
|
||||
|
||||
.elements {
|
||||
@apply p-2 grid rounded-b-md;
|
||||
@apply p-2 grid rounded-b-md gap-x-1;
|
||||
}
|
||||
|
||||
.elements > div.dropdown-input:last-child {
|
||||
|
||||
@@ -78,7 +78,7 @@ export const graph = createSlice({
|
||||
state.editLabel = action.payload;
|
||||
},
|
||||
|
||||
setEditId: (state, action: PayloadAction<string>) => {
|
||||
toggleEntityEdit: (state, action: PayloadAction<string>) => {
|
||||
state.editId = action.payload;
|
||||
state.nodes = state.nodes.map((node) => node.id === action.payload ?
|
||||
{
|
||||
@@ -176,7 +176,10 @@ export const graph = createSlice({
|
||||
},
|
||||
|
||||
deleteNode: (state, action: PayloadAction<Node['id']>) => {
|
||||
// state.editId = action.payload
|
||||
// state.editLabel = 'deleteNode'
|
||||
state.nodes = state.nodes.filter((n) => n.id !== action.payload);
|
||||
state.edges = state.edges.filter((e) => e.target !== action.payload && e.source !== action.payload)
|
||||
},
|
||||
|
||||
updateNodeFlow: (state, action: PayloadAction<NodeChange[]>) => {
|
||||
@@ -259,7 +262,7 @@ export const {
|
||||
setEditLabel,
|
||||
setEditValue,
|
||||
saveUserEdits,
|
||||
setEditId,
|
||||
toggleEntityEdit,
|
||||
onEdgesChange,
|
||||
setEditState,
|
||||
setNodeSelected,
|
||||
|
||||
@@ -125,7 +125,7 @@ export default function GraphDetails() {
|
||||
isBounded: true
|
||||
}} key="u-edges-count" className={isEdgesLocked ? 'z-50' : '-z-10'}>
|
||||
|
||||
<GridPanel setIsLocked={setIsEdgesLocked} isLocked={isEdgesLocked} icon='graph' label="Outgoing Relations Count by Entity">
|
||||
<GridPanel setIsLocked={setIsEdgesLocked} isLocked={isEdgesLocked} icon='graph' label="Outgoing Edges Count by Entity">
|
||||
<div className="w-full mb-0 bar-hz" ref={uniqueOutEdgesChartRef} />
|
||||
</GridPanel>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
EditState,
|
||||
saveUserEdits,
|
||||
selectNodeValue,
|
||||
setEditId,
|
||||
toggleEntityEdit,
|
||||
} from '@/features/graph/graphSlice';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function BaseMiniNode({
|
||||
<Handle position={Position.Left} id='l2' key='l2' type='target' style={handleStyle} />
|
||||
<div
|
||||
onDoubleClick={() => {
|
||||
dispatch(setEditId(ctx.id));
|
||||
dispatch(toggleEntityEdit(ctx.id))
|
||||
}}
|
||||
data-label-type={node.label}
|
||||
className='node container !rounded-full'
|
||||
|
||||
@@ -8,7 +8,7 @@ import { GripIcon, Icon } from '@/components/Icons';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useAppDispatch, useAppSelector } from '@/app/hooks';
|
||||
import { type ThunkDispatch } from 'redux-thunk';
|
||||
import { type Graph, EditState, saveUserEdits, selectNodeValue, setEditId, clearEditId } from '@/features/graph/graphSlice';
|
||||
import { type Graph, EditState, saveUserEdits, selectNodeValue, clearEditId } from '@/features/graph/graphSlice';
|
||||
import { AnyAction } from '@reduxjs/toolkit';
|
||||
|
||||
var dropdownKey = 0;
|
||||
@@ -39,7 +39,7 @@ export default function BaseNode({ ctx, sendJsonMessage, closeRef }: JSONObject)
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const getNodeElement = (element: NodeInput, key: string | null = getNodeKey()) => {
|
||||
const getNodeElement = (element: NodeInput, key: string | null = getNodeKey(), width: number = 1) => {
|
||||
switch (element.type) {
|
||||
case 'dropdown':
|
||||
return (
|
||||
@@ -104,6 +104,9 @@ export default function BaseNode({ ctx, sendJsonMessage, closeRef }: JSONObject)
|
||||
return <div className='hidden' />;
|
||||
}
|
||||
};
|
||||
|
||||
const columnsCount = Math.max(0, ...node.elements.map(s => s.length))
|
||||
const gridRepeat = columnsCount === 0 ? 1 : columnsCount
|
||||
return (
|
||||
<>
|
||||
<Handle position={Position.Right} id='r1' key='r1' type='source' style={handleStyle} />
|
||||
@@ -135,23 +138,28 @@ export default function BaseNode({ ctx, sendJsonMessage, closeRef }: JSONObject)
|
||||
id={`${ctx.id}-form`}
|
||||
style={node.style}
|
||||
onSubmit={(event) => event.preventDefault()}
|
||||
className='elements gap-x-1'
|
||||
className='elements'
|
||||
style={{
|
||||
gridTemplateColumns: '100%'
|
||||
}}
|
||||
>
|
||||
{node.elements.map((element: NodeInput, i: number) => {
|
||||
if (Array.isArray(element))
|
||||
if (Array.isArray(element)) {
|
||||
console.log('columnsCount', columnsCount)
|
||||
return (
|
||||
<Fragment key={i.toString()}>
|
||||
<div style={{ display: 'grid', columnGap: '0.5rem', gridTemplateColumns: `repeat(${gridRepeat}, minmax(0, 1fr))` }} key={i.toString()}>
|
||||
{element.map((elm, i: number) => (
|
||||
<div key={i.toString()} className='flex flex-col mr-2 last:mr-0'>
|
||||
{getNodeElement(elm, `${elm.label}-${elm.id}-${ctx.id}`)}
|
||||
</div>
|
||||
<Fragment key={i.toString()}>
|
||||
{getNodeElement(elm, `${elm.label}-${elm.id}-${ctx.id}`, 1)}
|
||||
</Fragment>
|
||||
))}
|
||||
</Fragment>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return getNodeElement(element, `${element.label}-${element.id}-${ctx.id}`);
|
||||
})}
|
||||
</form>
|
||||
</div>
|
||||
</div >
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -318,7 +326,7 @@ export function DropdownInput({ options, label, nodeId, sendJsonMessage, dispatc
|
||||
return (
|
||||
<>
|
||||
<Combobox
|
||||
className=' w-full z-[999] dropdown-input'
|
||||
className=' w-full z-[999] dropdown-input col-span-1'
|
||||
as='div'
|
||||
value={activeOption}
|
||||
onChange={(option) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import ContextAction from './ContextAction';
|
||||
import { MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||
import classNames from 'classnames';
|
||||
import { useAppDispatch } from '@/app/hooks';
|
||||
import { deleteNode, setEditId, setEditState } from '@/features/graph/graphSlice';
|
||||
import { deleteNode, setEditState } from '@/features/graph/graphSlice';
|
||||
import { useState } from 'react';
|
||||
import { useGetEntityTransformsQuery } from '@/app/api';
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function ContextMenu({
|
||||
node: { id: ctxSelection.id },
|
||||
});
|
||||
dispatch(deleteNode(ctxSelection.id));
|
||||
dispatch(setEditState({ editId: ctxSelection.id, editLabel: 'deleteNode' }))
|
||||
// dispatch(setEditState({ editId: ctxSelection.id, editLabel: 'deleteNode' }))
|
||||
}}
|
||||
type='button'
|
||||
>
|
||||
|
||||
@@ -8,10 +8,10 @@ import { Link } from 'react-router-dom';
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
import { Responsive, WidthProvider, Layout } from 'react-grid-layout';
|
||||
import { useGetEntitiesQuery } from '@/app/api';
|
||||
import { selectPositionMode, selectViewMode, setAllEdges, setAllNodes, setNodeType, setPositionMode, setViewMode } from '@/features/graph/graphSlice';
|
||||
import { selectPositionMode, selectViewMode, setAllEdges, setAllNodes, setEditState, setNodeType, setPositionMode, setViewMode } from '@/features/graph/graphSlice';
|
||||
import classNames from 'classnames';
|
||||
import { useAppDispatch, useAppSelector, useEffectOnce } from '@/app/hooks';
|
||||
import { ArrowsPointingInIcon, ArrowsPointingOutIcon, CubeTransparentIcon, HandRaisedIcon, RectangleStackIcon, Square2StackIcon, Squares2X2Icon } from '@heroicons/react/20/solid';
|
||||
import { useAppDispatch, } from '@/app/hooks';
|
||||
import { HandRaisedIcon } from '@heroicons/react/20/solid';
|
||||
import { Icon } from '@/components/Icons';
|
||||
|
||||
type UseResizeProps = {
|
||||
@@ -112,7 +112,7 @@ const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||
|
||||
const MAX_GRAPH_LABEL_LENGTH = 22;
|
||||
|
||||
export default function EntityOptions({ allManualNodes, allManualEdges, positionMode, activeGraph, setElkLayout, toggleForceLayout, isForceActive, fitView }: JSONObject) {
|
||||
export default function EntityOptions({ positionMode, activeGraph, setElkLayout, toggleForceLayout, fitView }: JSONObject) {
|
||||
const {
|
||||
data: entitiesData = { entities: [], count: 0, favorite_entities: [], favorite_count: 0 },
|
||||
isLoading,
|
||||
@@ -168,7 +168,7 @@ export default function EntityOptions({ allManualNodes, allManualEdges, position
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
||||
const [isForceActive, setIsForceActive] = useState(false);
|
||||
|
||||
return (
|
||||
<ResponsiveGridLayout
|
||||
@@ -279,10 +279,9 @@ export default function EntityOptions({ allManualNodes, allManualEdges, position
|
||||
<ul className='isolate inline-flex shadow-sm pt-2.5 w-full'>
|
||||
<button
|
||||
onClick={() => {
|
||||
isForceActive !== undefined && !isForceActive && toggleForceLayout(false)
|
||||
setIsForceActive(false)
|
||||
toggleForceLayout && toggleForceLayout(false)
|
||||
dispatch(setPositionMode('manual'))
|
||||
dispatch(setAllNodes(allManualNodes))
|
||||
dispatch(setAllEdges(allManualEdges))
|
||||
}}
|
||||
type='button'
|
||||
className={classNames(
|
||||
@@ -299,7 +298,13 @@ export default function EntityOptions({ allManualNodes, allManualEdges, position
|
||||
<button
|
||||
onClick={() => {
|
||||
dispatch(setPositionMode('force'))
|
||||
toggleForceLayout && toggleForceLayout()
|
||||
|
||||
console.log('setting to isForceActive', !isForceActive)
|
||||
toggleForceLayout && toggleForceLayout(!isForceActive)
|
||||
setIsForceActive(!isForceActive)
|
||||
// dispatch(setEditState({ editId: '', editLabel: 'force' }))
|
||||
|
||||
|
||||
}}
|
||||
type='button'
|
||||
className={classNames(
|
||||
@@ -308,17 +313,18 @@ export default function EntityOptions({ allManualNodes, allManualEdges, position
|
||||
)}
|
||||
>
|
||||
<span className='sr-only'>List view</span>
|
||||
<CubeTransparentIcon
|
||||
className={classNames('h-5 w-5 ', positionMode === 'force' && 'text-primary-300')}
|
||||
aria-hidden='true'
|
||||
<Icon
|
||||
icon={isForceActive !== undefined && isForceActive ? "3d-cube-sphere" : "3d-cube-sphere-off"}
|
||||
className={classNames('h-5 w-5 text-inherit', positionMode === 'force' && 'text-primary-300')}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
isForceActive !== undefined && !isForceActive && toggleForceLayout(false)
|
||||
toggleForceLayout && toggleForceLayout(false)
|
||||
// setElkLayout({ 'elk.algorithm': 'org.eclipse.elk.radial', })
|
||||
// setElkLayout({ 'elk.algorithm': 'layered', 'elk.direction': 'DOWN' })
|
||||
// setElkLayout({ 'elk.algorithm': 'layered', 'elk.direction': 'RIGHT' })
|
||||
setIsForceActive(false)
|
||||
dispatch(setPositionMode('right tree'))
|
||||
setElkLayout({
|
||||
'elk.algorithm': 'layered',
|
||||
@@ -335,13 +341,12 @@ export default function EntityOptions({ allManualNodes, allManualEdges, position
|
||||
<Icon
|
||||
icon="binary-tree-2"
|
||||
className={classNames('h-5 w-5 -rotate-90 origin-center text-inherit', positionMode === 'right tree' && 'text-primary-300')}
|
||||
aria-hidden='true'
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
isForceActive !== undefined && !isForceActive && toggleForceLayout(false)
|
||||
|
||||
setIsForceActive(false)
|
||||
toggleForceLayout && toggleForceLayout(false)
|
||||
dispatch(setPositionMode('tree'))
|
||||
setElkLayout({
|
||||
'elk.algorithm': 'layered',
|
||||
@@ -358,7 +363,6 @@ export default function EntityOptions({ allManualNodes, allManualEdges, position
|
||||
<Icon
|
||||
icon="binary-tree"
|
||||
className={classNames('h-5 w-5 text-inherit', positionMode === 'tree' && 'text-primary-300')}
|
||||
aria-hidden='true'
|
||||
/>
|
||||
</button>
|
||||
</ul>
|
||||
|
||||
@@ -32,7 +32,6 @@ export default function Graph({
|
||||
onMultiSelectionCtxMenu,
|
||||
onPaneCtxMenu,
|
||||
onPaneClick,
|
||||
addEdge,
|
||||
graphRef,
|
||||
nodes,
|
||||
edges,
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
selectViewMode,
|
||||
setAllEdges,
|
||||
setAllNodes,
|
||||
setEditState,
|
||||
setPositionMode,
|
||||
} from '@/features/graph/graphSlice';
|
||||
import { WS_URL } from '@/app/baseApi';
|
||||
@@ -109,11 +110,11 @@ export default function GraphInquiry({ }: GraphInquiryProps) {
|
||||
function addEdge(
|
||||
source: string,
|
||||
target: string,
|
||||
id: any = getEdgeId(),
|
||||
sourceHandle: string = 'r1',
|
||||
targetHandle: string = 'l2',
|
||||
type: string = 'float',
|
||||
label: string = '',
|
||||
id: any = getEdgeId()
|
||||
): void {
|
||||
dispatch(
|
||||
createEdge({
|
||||
@@ -165,7 +166,6 @@ export default function GraphInquiry({ }: GraphInquiryProps) {
|
||||
return updatedNode;
|
||||
};
|
||||
|
||||
// websocket updates happen here
|
||||
useEffect(() => {
|
||||
if (lastJsonMessage) {
|
||||
setMessageHistory((prev) => prev.concat(lastJsonMessage));
|
||||
@@ -178,7 +178,6 @@ export default function GraphInquiry({ }: GraphInquiryProps) {
|
||||
} else {
|
||||
lastJsonMessage.map((node, idx) => {
|
||||
if (node?.action === 'addNode') {
|
||||
console.log('adding node')
|
||||
const isOdd = idx % 2 === 0;
|
||||
const pos = node.position;
|
||||
const x = isOdd ? pos.x + 560 : pos.x + 970;
|
||||
@@ -188,9 +187,11 @@ export default function GraphInquiry({ }: GraphInquiryProps) {
|
||||
y,
|
||||
};
|
||||
addNodeAction(node);
|
||||
dispatch(setEditState({ editId: node.id, editLabel: 'addNode' }))
|
||||
sendJsonMessage({ action: 'update:node', node: { id: node.id, x, y } });
|
||||
}
|
||||
});
|
||||
|
||||
if (lastJsonMessage.length > 0) {
|
||||
toast.success(`Found ${lastJsonMessage.length} results`);
|
||||
} else {
|
||||
@@ -262,19 +263,14 @@ export default function GraphInquiry({ }: GraphInquiryProps) {
|
||||
setNodesBeforeLayout(initialNodes)
|
||||
setEdgesBeforeLayout(initialEdges)
|
||||
}
|
||||
}, [initialNodes, activeEditState])
|
||||
}, [initialNodes, initialEdges]) // , activeEditState
|
||||
|
||||
useEffect(() => {
|
||||
if (activeEditState.label === 'deleteNode') {
|
||||
setNodesBeforeLayout(nodesBeforeLayout.filter((node) => node.id !== activeEditState.id))
|
||||
if (positionMode === 'manual') {
|
||||
fitView && fitView({ padding: 0.25 })
|
||||
dispatch(setAllNodes(nodesBeforeLayout))
|
||||
dispatch(setAllEdges(edgesBeforeLayout))
|
||||
}
|
||||
if (activeEditState.label === 'addNode') {
|
||||
setNodesBeforeLayout([...nodesBeforeLayout, initialNodes.find((node) => node.id === activeEditState.id) as Node])
|
||||
}
|
||||
}, [activeEditState])
|
||||
|
||||
useEffect(() => {
|
||||
if (positionMode === 'manual') fitView && fitView({ padding: 0.25 })
|
||||
}, [positionMode])
|
||||
// TODO: Also implement d3-hierarchy, entitree-flex, dagre, webcola, and graphology layout modes
|
||||
// Once implemented measure performance and deprecate whatever performs worse
|
||||
@@ -306,8 +302,7 @@ export default function GraphInquiry({ }: GraphInquiryProps) {
|
||||
fitView && fitView({ padding: 0.25 });
|
||||
});
|
||||
});
|
||||
|
||||
}, [nodesBeforeLayout]);
|
||||
}, [nodesBeforeLayout, activeEditState]);
|
||||
|
||||
return { setElkLayout };
|
||||
};
|
||||
@@ -325,56 +320,71 @@ export default function GraphInquiry({ }: GraphInquiryProps) {
|
||||
.alphaTarget(0.01)
|
||||
.stop();
|
||||
|
||||
// console.log('insideForce allLayoutNodes', allLayoutNodes)
|
||||
let forceNodes = initialNodes.map((node: any) => ({ ...node, x: node.position.x, y: node.position.y }));
|
||||
let forceEdges = initialEdges.map((edge: any) => ({ ...edge }));
|
||||
|
||||
const forceSimOff = [false, { toggleForceLayout: (setForce?: boolean) => null } as any]
|
||||
// if no width or height or no nodes in the flow, can't run the simulation!
|
||||
if (!nodesInitialized || forceNodes.length === 0) return [false, { toggleForceLayout: (setForce?: boolean) => null } as any];
|
||||
if (!nodesInitialized || forceNodes.length === 0) return forceSimOff;
|
||||
let running = false;
|
||||
try {
|
||||
simulation.nodes(forceNodes).force(
|
||||
'link',
|
||||
forceLink(forceEdges)
|
||||
.id((d: any) => d.id)
|
||||
.strength(0.05)
|
||||
.distance(42)
|
||||
);
|
||||
|
||||
simulation.nodes(forceNodes).force(
|
||||
'link',
|
||||
forceLink(forceEdges)
|
||||
.id((d: any) => d.id)
|
||||
.strength(0.05)
|
||||
.distance(42)
|
||||
);
|
||||
// The tick function is called every animation frame while the simulation is
|
||||
// running and progresses the simulation one step forward each time.
|
||||
const tick = () => {
|
||||
fitView && fitView({ padding: 0.25 })
|
||||
forceNodes.forEach((node: any, i: number) => {
|
||||
const activeNode = document.querySelector(`[data-id="${node.id}"].dragging`)
|
||||
const dragging = Boolean(activeNode);
|
||||
forceNodes[i].fx = dragging ? node.x : null;
|
||||
forceNodes[i].fy = dragging ? node.y : null;
|
||||
});
|
||||
simulation.tick();
|
||||
dispatch(setAllNodes(forceNodes.map((node: any) => ({ ...node, position: { x: node.x, y: node.y } }))) as any);
|
||||
|
||||
// The tick function is called every animation frame while the simulation is
|
||||
// running and progresses the simulation one step forward each time.
|
||||
const tick = () => {
|
||||
fitView && fitView({ padding: 0.25 })
|
||||
forceNodes.forEach((node: any, i: number) => {
|
||||
const activeNode = document.querySelector(`[data-id="${node.id}"].dragging`)
|
||||
const dragging = Boolean(activeNode);
|
||||
forceNodes[i].fx = dragging ? node.x : null;
|
||||
forceNodes[i].fy = dragging ? node.y : null;
|
||||
});
|
||||
simulation.tick();
|
||||
dispatch(setAllNodes(forceNodes.map((node: any) => ({ ...node, position: { x: node.x, y: node.y } }))) as any);
|
||||
window.requestAnimationFrame(() => {
|
||||
if (running) {
|
||||
tick()
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
if (running) {
|
||||
tick()
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const toggleForceLayout = (setForce?: boolean) => {
|
||||
if (typeof setForce === 'boolean') {
|
||||
running = setForce
|
||||
} else {
|
||||
running = !running
|
||||
}
|
||||
running && window.requestAnimationFrame(tick);
|
||||
};
|
||||
return [true, { toggleForceLayout, isForceRunning: running }];
|
||||
const toggleForceLayout = (setForce?: boolean) => {
|
||||
if (typeof setForce === 'boolean') {
|
||||
running = setForce
|
||||
} else {
|
||||
running = !running
|
||||
}
|
||||
running && window.requestAnimationFrame(tick);
|
||||
};
|
||||
return [true, { toggleForceLayout, isForceRunning: running }];
|
||||
} catch (e) { console.warn(e) }
|
||||
return forceSimOff
|
||||
}, [nodesBeforeLayout, activeEditState]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const [forceInitialized, { toggleForceLayout, isForceRunning }] = useForceLayoutElements();
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (activeEditState.label === 'deleteNode') {
|
||||
setNodesBeforeLayout(nodesBeforeLayout.filter((node) => node.id !== activeEditState.id))
|
||||
}
|
||||
if (activeEditState.label === 'addNode') {
|
||||
setNodesBeforeLayout([...nodesBeforeLayout, initialNodes.find((node) => node.id === activeEditState.id) as Node])
|
||||
}
|
||||
}, [activeEditState])
|
||||
|
||||
// Prevents layout bugs from occurring on navigate away and returning to a graph
|
||||
// https://reactrouter.com/en/main/hooks/use-blocker
|
||||
useBlocker(useCallback(
|
||||
@@ -398,10 +408,7 @@ export default function GraphInquiry({ }: GraphInquiryProps) {
|
||||
<EntityOptions
|
||||
positionMode={positionMode}
|
||||
toggleForceLayout={toggleForceLayout}
|
||||
isForceActive={isForceRunning}
|
||||
activeGraph={activeGraph}
|
||||
allManualNodes={nodesBeforeLayout}
|
||||
allManualEdges={edgesBeforeLayout}
|
||||
setElkLayout={setElkLayout}
|
||||
/>
|
||||
<div className='h-full w-full justify-between bg-mirage-600/95'>
|
||||
@@ -411,7 +418,6 @@ export default function GraphInquiry({ }: GraphInquiryProps) {
|
||||
onMultiSelectionCtxMenu={onMultiSelectionCtxMenu}
|
||||
onPaneCtxMenu={onPaneCtxMenu}
|
||||
onPaneClick={onPaneClick}
|
||||
addEdge={addEdge}
|
||||
graphRef={graphRef}
|
||||
nodes={initialNodes}
|
||||
edges={initialEdges}
|
||||
|
||||
Reference in New Issue
Block a user