mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
Merge pull request #114 from Lum1104/fix/dashboard-tour-cycle
fix(dashboard): tour navigation glitches across layers
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "understand-anything",
|
||||
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"author": {
|
||||
"name": "Lum1104"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "understand-anything",
|
||||
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"author": {
|
||||
"name": "Lum1104"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "understand-anything",
|
||||
"displayName": "Understand Anything",
|
||||
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"author": {
|
||||
"name": "Lum1104"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "understand-anything",
|
||||
"description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"author": {
|
||||
"name": "Lum1104"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@understand-anything/skill",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
ReactFlow,
|
||||
ReactFlowProvider,
|
||||
useNodes,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
useReactFlow,
|
||||
@@ -78,32 +79,101 @@ const NODE_TYPE_TO_CATEGORY: Record<NodeType, NodeCategory> = {
|
||||
|
||||
// ── Helper components that must live inside <ReactFlow> ────────────────
|
||||
|
||||
/** Pans/zooms to tour-highlighted nodes. */
|
||||
/**
|
||||
* Pans/zooms to tour-highlighted nodes. Highlighted nodes are usually
|
||||
* children of collapsed containers — auto-expand fires synchronously on
|
||||
* the same `tourHighlightedNodeIds` change, but their child entries don't
|
||||
* appear in React Flow's node list until Stage 2 layout writes the
|
||||
* `containerLayoutCache` (async ELK call, hundreds of ms on big layers).
|
||||
*
|
||||
* We subscribe to React Flow's reactive node list via `useNodes()` so the
|
||||
* effect re-runs every time the node set actually changes (Stage 1, Stage
|
||||
* 2, expand/collapse). When every highlighted id is present we fit; until
|
||||
* then we wait. A 2s fallback timer covers the case where a highlighted
|
||||
* id is filtered out and never materialises.
|
||||
*/
|
||||
function TourFitView() {
|
||||
const tourHighlightedNodeIds = useDashboardStore((s) => s.tourHighlightedNodeIds);
|
||||
const { fitView } = useReactFlow();
|
||||
const prevRef = useRef<string[]>([]);
|
||||
const setTourFitPending = useDashboardStore((s) => s.setTourFitPending);
|
||||
const { fitView, getInternalNode } = useReactFlow();
|
||||
// Subscribe to React Flow's user-node array so this effect re-fires when
|
||||
// the node set changes (e.g. Stage 2 finally lands the highlighted ids
|
||||
// after the per-step RAF window already gave up). The RAF poll inside
|
||||
// covers the common fast path; the `nodes` dep covers slow Stage 2.
|
||||
const nodes = useNodes();
|
||||
const fittedKeyRef = useRef<string>("");
|
||||
const fallbackKeyRef = useRef<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
const prev = prevRef.current;
|
||||
const changed =
|
||||
tourHighlightedNodeIds.length > 0 &&
|
||||
(tourHighlightedNodeIds.length !== prev.length ||
|
||||
tourHighlightedNodeIds.some((id, i) => id !== prev[i]));
|
||||
prevRef.current = tourHighlightedNodeIds;
|
||||
const targetKey = tourHighlightedNodeIds.join("\n");
|
||||
if (targetKey === "") {
|
||||
fittedKeyRef.current = "";
|
||||
fallbackKeyRef.current = "";
|
||||
setTourFitPending(false);
|
||||
return;
|
||||
}
|
||||
if (targetKey === fittedKeyRef.current) return;
|
||||
|
||||
if (changed) {
|
||||
requestAnimationFrame(() => {
|
||||
// Poll React Flow's internal lookup directly — `useNodes()` reflects
|
||||
// user-supplied nodes and may not fire on measure completion. Once
|
||||
// every highlighted id has measured dimensions, `fitView({ nodes })`
|
||||
// handles the child→absolute coordinate transform itself, which is
|
||||
// more reliable than recomputing bbox manually.
|
||||
const MAX_FRAMES = 240; // ~4s at 60fps
|
||||
let frame = 0;
|
||||
let cancelled = false;
|
||||
let rafId = 0;
|
||||
// After we've already shown the fallback for this step, suppress the
|
||||
// "Locating tour highlight…" overlay on subsequent re-fires (each
|
||||
// `nodes` change re-enters the effect, but the user has already given
|
||||
// up waiting). The retry still runs silently in case Stage 2 lands.
|
||||
if (fallbackKeyRef.current !== targetKey) setTourFitPending(true);
|
||||
|
||||
const tick = () => {
|
||||
if (cancelled) return;
|
||||
let ready = true;
|
||||
for (const id of tourHighlightedNodeIds) {
|
||||
const internal = getInternalNode(id);
|
||||
if (!internal || !internal.measured?.width || !internal.measured?.height) {
|
||||
ready = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ready) {
|
||||
fitView({
|
||||
nodes: tourHighlightedNodeIds.map((id) => ({ id })),
|
||||
duration: 500,
|
||||
padding: 0.3,
|
||||
maxZoom: 1.2,
|
||||
minZoom: 0.01,
|
||||
minZoom: 0.4,
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [tourHighlightedNodeIds, fitView]);
|
||||
fittedKeyRef.current = targetKey;
|
||||
fallbackKeyRef.current = "";
|
||||
setTourFitPending(false);
|
||||
return;
|
||||
}
|
||||
if (++frame < MAX_FRAMES) {
|
||||
rafId = requestAnimationFrame(tick);
|
||||
return;
|
||||
}
|
||||
// Highlights still not ready after the poll window. Pan into the
|
||||
// layer so the user isn't stranded, but DON'T set fittedKeyRef —
|
||||
// if Stage 2 lands later, a `nodes` change will re-fire this effect
|
||||
// and we'll get another shot at the proper highlight fit.
|
||||
// `fallbackKeyRef` prevents the fallback fitView from re-firing on
|
||||
// every subsequent nodes update for the same step.
|
||||
if (fallbackKeyRef.current !== targetKey) {
|
||||
fitView({ duration: 500, padding: 0.3 });
|
||||
fallbackKeyRef.current = targetKey;
|
||||
}
|
||||
setTourFitPending(false);
|
||||
};
|
||||
rafId = requestAnimationFrame(tick);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
cancelAnimationFrame(rafId);
|
||||
};
|
||||
}, [tourHighlightedNodeIds, nodes, fitView, getInternalNode, setTourFitPending]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1219,6 +1289,10 @@ function GraphViewInner() {
|
||||
const setReactFlowInstance = useDashboardStore((s) => s.setReactFlowInstance);
|
||||
const tourHighlightedNodeIds = useDashboardStore((s) => s.tourHighlightedNodeIds);
|
||||
const expandContainer = useDashboardStore((s) => s.expandContainer);
|
||||
const collapseContainer = useDashboardStore((s) => s.collapseContainer);
|
||||
const pendingFocusContainer = useDashboardStore((s) => s.pendingFocusContainer);
|
||||
const setPendingFocusContainer = useDashboardStore((s) => s.setPendingFocusContainer);
|
||||
const tourFitPending = useDashboardStore((s) => s.tourFitPending);
|
||||
const { preset } = useTheme();
|
||||
|
||||
const overviewGraph = useOverviewGraph();
|
||||
@@ -1237,7 +1311,7 @@ function GraphViewInner() {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
const { fitView, getViewport } = useReactFlow();
|
||||
const { fitView, getViewport, setCenter } = useReactFlow();
|
||||
|
||||
useEffect(() => {
|
||||
setNodes(initialNodes);
|
||||
@@ -1267,6 +1341,33 @@ function GraphViewInner() {
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}, [nodes, fitView]);
|
||||
|
||||
// Lock viewport onto a container the user just manually expanded so it
|
||||
// appears to expand in place rather than getting yanked off-screen by
|
||||
// the surrounding ELK reflow. Re-runs as nodes update (Stage 2 may
|
||||
// shift positions a few times) and clears itself after a short window
|
||||
// so subsequent layout shifts stop hijacking the viewport.
|
||||
useEffect(() => {
|
||||
if (!pendingFocusContainer) return;
|
||||
const node = nodes.find((n) => n.id === pendingFocusContainer);
|
||||
if (!node) return;
|
||||
const w =
|
||||
(node.width as number | undefined) ??
|
||||
((node.style?.width as number | undefined) ?? 0);
|
||||
const h =
|
||||
(node.height as number | undefined) ??
|
||||
((node.style?.height as number | undefined) ?? 0);
|
||||
const cx = node.position.x + w / 2;
|
||||
const cy = node.position.y + h / 2;
|
||||
const { zoom } = getViewport();
|
||||
setCenter(cx, cy, { zoom, duration: 0 });
|
||||
}, [pendingFocusContainer, nodes, getViewport, setCenter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pendingFocusContainer) return;
|
||||
const t = window.setTimeout(() => setPendingFocusContainer(null), 1200);
|
||||
return () => window.clearTimeout(t);
|
||||
}, [pendingFocusContainer, setPendingFocusContainer]);
|
||||
|
||||
// ── Auto-expand triggers (Task 13) ─────────────────────────────────────
|
||||
// Only meaningful in layer-detail; in overview mode there are no
|
||||
// containers so all three effects no-op.
|
||||
@@ -1281,15 +1382,40 @@ function GraphViewInner() {
|
||||
if (cid && cid !== focusNodeId) expandContainer(cid);
|
||||
}, [focusNodeId, nodeToContainer, expandContainer]);
|
||||
|
||||
// Tour: expand containers for every tour-highlighted node so the tour
|
||||
// can fitView onto real nodes rather than collapsed atoms.
|
||||
// Tour: expand containers needed for the current step, and release any
|
||||
// containers we expanded for the previous step that aren't needed now.
|
||||
// Containers the user expanded manually aren't tracked here, so they're
|
||||
// never auto-collapsed. stopTour resets tourHighlightedNodeIds to [],
|
||||
// which falls through to the "release all" branch.
|
||||
const tourBorrowedContainersRef = useRef<Set<string>>(new Set());
|
||||
useEffect(() => {
|
||||
if (tourHighlightedNodeIds.length === 0 || !nodeToContainer) return;
|
||||
if (!nodeToContainer) return;
|
||||
|
||||
const needed = new Set<string>();
|
||||
for (const nid of tourHighlightedNodeIds) {
|
||||
const cid = nodeToContainer.get(nid);
|
||||
if (cid && cid !== nid) expandContainer(cid);
|
||||
if (cid && cid !== nid) needed.add(cid);
|
||||
}
|
||||
}, [tourHighlightedNodeIds, nodeToContainer, expandContainer]);
|
||||
|
||||
const stillBorrowed = new Set<string>();
|
||||
for (const cid of tourBorrowedContainersRef.current) {
|
||||
if (needed.has(cid)) {
|
||||
stillBorrowed.add(cid);
|
||||
} else {
|
||||
collapseContainer(cid);
|
||||
}
|
||||
}
|
||||
|
||||
const expandedNow = useDashboardStore.getState().expandedContainers;
|
||||
for (const cid of needed) {
|
||||
if (!expandedNow.has(cid)) {
|
||||
expandContainer(cid);
|
||||
stillBorrowed.add(cid);
|
||||
}
|
||||
}
|
||||
|
||||
tourBorrowedContainersRef.current = stillBorrowed;
|
||||
}, [tourHighlightedNodeIds, nodeToContainer, expandContainer, collapseContainer]);
|
||||
|
||||
// Zoom: debounced auto-expand when the user has zoomed in past 1.0.
|
||||
// Hysteresis: zoom < 0.6 = no auto-expand AND no auto-collapse (v1, the
|
||||
@@ -1406,7 +1532,7 @@ function GraphViewInner() {
|
||||
<TourFitView />
|
||||
<SelectedNodeFitView />
|
||||
</ReactFlow>
|
||||
{layoutStatus === "computing" && (
|
||||
{(layoutStatus === "computing" || tourFitPending) && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
@@ -1419,7 +1545,9 @@ function GraphViewInner() {
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: "#d4a574", fontSize: 14 }}>Computing layout…</span>
|
||||
<span style={{ color: "#d4a574", fontSize: 14 }}>
|
||||
{tourFitPending ? "Locating tour highlight…" : "Computing layout…"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -196,7 +196,14 @@ interface DashboardStore {
|
||||
expandedContainers: Set<string>;
|
||||
toggleContainer: (containerId: string) => void;
|
||||
expandContainer: (containerId: string) => void;
|
||||
collapseContainer: (containerId: string) => void;
|
||||
collapseAllContainers: () => void;
|
||||
/** Container the user just manually expanded; viewport should lock onto it. Cleared by GraphView once the lock is applied. */
|
||||
pendingFocusContainer: string | null;
|
||||
setPendingFocusContainer: (containerId: string | null) => void;
|
||||
/** True while TourFitView is waiting for highlighted nodes to materialise (Stage 2 layout in progress). Drives the "Computing layout…" overlay. */
|
||||
tourFitPending: boolean;
|
||||
setTourFitPending: (pending: boolean) => void;
|
||||
|
||||
containerLayoutCache: Map<
|
||||
string,
|
||||
@@ -245,6 +252,32 @@ function navigateTourToLayer(
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Container ids derive from per-layer state — folder names in folder-strategy
|
||||
* layers, community indices (`container:cluster-N`) in community-strategy
|
||||
* layers — and collide across layers (e.g. API Contracts and Load Testing
|
||||
* both produce `container:cluster-0`). When a tour step crosses layers we
|
||||
* must drop the previous layer's container caches so Stage 2 actually re-
|
||||
* runs for the new layer's children. Mirrors the reset block in
|
||||
* `drillIntoLayer`.
|
||||
*/
|
||||
function layerResetIfChanged(
|
||||
layerNav: Partial<DashboardStore>,
|
||||
prevLayerId: string | null,
|
||||
): Partial<DashboardStore> {
|
||||
const next = layerNav.activeLayerId;
|
||||
if (!next || next === prevLayerId) return {};
|
||||
return {
|
||||
containerLayoutCache: new Map(),
|
||||
containerSizeMemory: new Map(),
|
||||
expandedContainers: new Set(),
|
||||
// Drop any pending focus too — its id was scoped to the previous
|
||||
// layer and would otherwise re-collide with a same-id container in
|
||||
// the new layer for the duration of the 1.2s timer.
|
||||
pendingFocusContainer: null,
|
||||
};
|
||||
}
|
||||
|
||||
export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
graph: null,
|
||||
nodesById: new Map<string, GraphNode>(),
|
||||
@@ -295,6 +328,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
containerLayoutCache: new Map(),
|
||||
containerSizeMemory: new Map(),
|
||||
expandedContainers: new Set(),
|
||||
pendingFocusContainer: null,
|
||||
})),
|
||||
|
||||
setGraph: (graph) => {
|
||||
@@ -321,6 +355,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
activeDomainId: keepDomainView ? activeDomainId : null,
|
||||
containerLayoutCache: new Map(),
|
||||
expandedContainers: new Set(),
|
||||
pendingFocusContainer: null,
|
||||
containerSizeMemory: new Map(),
|
||||
stage1Tick: 0,
|
||||
layoutIssues: [],
|
||||
@@ -420,6 +455,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
containerLayoutCache: new Map(),
|
||||
containerSizeMemory: new Map(),
|
||||
expandedContainers: new Set(),
|
||||
pendingFocusContainer: null,
|
||||
}),
|
||||
|
||||
navigateToOverview: () =>
|
||||
@@ -434,6 +470,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
containerLayoutCache: new Map(),
|
||||
containerSizeMemory: new Map(),
|
||||
expandedContainers: new Set(),
|
||||
pendingFocusContainer: null,
|
||||
}),
|
||||
|
||||
setFocusNode: (nodeId) =>
|
||||
@@ -446,6 +483,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
containerLayoutCache: new Map(),
|
||||
containerSizeMemory: new Map(),
|
||||
expandedContainers: new Set(),
|
||||
pendingFocusContainer: null,
|
||||
}),
|
||||
setSearchMode: (mode) => set({ searchMode: mode }),
|
||||
setSearchQuery: (query) => {
|
||||
@@ -469,6 +507,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
containerLayoutCache: new Map(),
|
||||
containerSizeMemory: new Map(),
|
||||
expandedContainers: new Set(),
|
||||
pendingFocusContainer: null,
|
||||
}),
|
||||
|
||||
openCodeViewer: (nodeId) =>
|
||||
@@ -532,7 +571,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
},
|
||||
|
||||
startTour: () => {
|
||||
const { graph, nodeIdToLayerId } = get();
|
||||
const { graph, nodeIdToLayerId, activeLayerId } = get();
|
||||
if (!graph || !graph.tour || graph.tour.length === 0) return;
|
||||
const sorted = getSortedTour(graph);
|
||||
const layerNav = navigateTourToLayer(nodeIdToLayerId, sorted[0].nodeIds);
|
||||
@@ -542,6 +581,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
tourHighlightedNodeIds: sorted[0].nodeIds,
|
||||
selectedNodeId: null,
|
||||
...layerNav,
|
||||
...layerResetIfChanged(layerNav, activeLayerId),
|
||||
});
|
||||
},
|
||||
|
||||
@@ -553,7 +593,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
}),
|
||||
|
||||
setTourStep: (step) => {
|
||||
const { graph, nodeIdToLayerId } = get();
|
||||
const { graph, nodeIdToLayerId, activeLayerId } = get();
|
||||
if (!graph || !graph.tour || graph.tour.length === 0) return;
|
||||
const sorted = getSortedTour(graph);
|
||||
if (step < 0 || step >= sorted.length) return;
|
||||
@@ -562,11 +602,12 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
currentTourStep: step,
|
||||
tourHighlightedNodeIds: sorted[step].nodeIds,
|
||||
...layerNav,
|
||||
...layerResetIfChanged(layerNav, activeLayerId),
|
||||
});
|
||||
},
|
||||
|
||||
nextTourStep: () => {
|
||||
const { graph, currentTourStep, nodeIdToLayerId } = get();
|
||||
const { graph, currentTourStep, nodeIdToLayerId, activeLayerId } = get();
|
||||
if (!graph || !graph.tour || graph.tour.length === 0) return;
|
||||
const sorted = getSortedTour(graph);
|
||||
if (currentTourStep < sorted.length - 1) {
|
||||
@@ -576,12 +617,13 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
currentTourStep: next,
|
||||
tourHighlightedNodeIds: sorted[next].nodeIds,
|
||||
...layerNav,
|
||||
...layerResetIfChanged(layerNav, activeLayerId),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
prevTourStep: () => {
|
||||
const { graph, currentTourStep, nodeIdToLayerId } = get();
|
||||
const { graph, currentTourStep, nodeIdToLayerId, activeLayerId } = get();
|
||||
if (!graph || !graph.tour || graph.tour.length === 0) return;
|
||||
if (currentTourStep > 0) {
|
||||
const sorted = getSortedTour(graph);
|
||||
@@ -591,6 +633,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
currentTourStep: prev,
|
||||
tourHighlightedNodeIds: sorted[prev].nodeIds,
|
||||
...layerNav,
|
||||
...layerResetIfChanged(layerNav, activeLayerId),
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -641,12 +684,23 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
},
|
||||
|
||||
expandedContainers: new Set<string>(),
|
||||
pendingFocusContainer: null,
|
||||
setPendingFocusContainer: (containerId) =>
|
||||
set({ pendingFocusContainer: containerId }),
|
||||
tourFitPending: false,
|
||||
setTourFitPending: (pending) => set({ tourFitPending: pending }),
|
||||
toggleContainer: (containerId) =>
|
||||
set((state) => {
|
||||
const next = new Set(state.expandedContainers);
|
||||
if (next.has(containerId)) next.delete(containerId);
|
||||
else next.add(containerId);
|
||||
return { expandedContainers: next };
|
||||
const willExpand = !next.has(containerId);
|
||||
if (willExpand) next.add(containerId);
|
||||
else next.delete(containerId);
|
||||
return {
|
||||
expandedContainers: next,
|
||||
pendingFocusContainer: willExpand
|
||||
? containerId
|
||||
: state.pendingFocusContainer,
|
||||
};
|
||||
}),
|
||||
expandContainer: (containerId) =>
|
||||
set((state) => {
|
||||
@@ -655,6 +709,13 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
next.add(containerId);
|
||||
return { expandedContainers: next };
|
||||
}),
|
||||
collapseContainer: (containerId) =>
|
||||
set((state) => {
|
||||
if (!state.expandedContainers.has(containerId)) return {};
|
||||
const next = new Set(state.expandedContainers);
|
||||
next.delete(containerId);
|
||||
return { expandedContainers: next };
|
||||
}),
|
||||
collapseAllContainers: () => set({ expandedContainers: new Set() }),
|
||||
|
||||
containerLayoutCache: new Map(),
|
||||
@@ -667,7 +728,7 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
return { containerLayoutCache: next, containerSizeMemory: sizeNext };
|
||||
}),
|
||||
clearContainerLayouts: () =>
|
||||
set({ containerLayoutCache: new Map(), expandedContainers: new Set() }),
|
||||
set({ containerLayoutCache: new Map(), expandedContainers: new Set(), pendingFocusContainer: null }),
|
||||
|
||||
containerSizeMemory: new Map(),
|
||||
|
||||
@@ -689,3 +750,4 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
|
||||
}),
|
||||
clearLayoutIssues: () => set({ layoutIssues: [] }),
|
||||
}));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user