@@ -8,20 +8,24 @@ declare var DEBUG: boolean;
88import { useMemo } from "react";
99import { useIntl } from "react-intl";
1010
11+ import { switchAccountPage } from "@cocalc/frontend/account/util";
1112import {
1213 redux,
1314 useActions,
14- useTypedRedux,
1515 useRedux,
16+ useTypedRedux,
1617} from "@cocalc/frontend/app-framework";
18+ import type { EditorSpec } from "@cocalc/frontend/frame-editors/frame-tree/types";
19+ import { labels } from "@cocalc/frontend/i18n";
20+ import type { FixedTab } from "@cocalc/frontend/project/page/file-tab";
21+ import { FIXED_PROJECT_TABS } from "@cocalc/frontend/project/page/file-tab";
1722import { useBookmarkedProjects } from "@cocalc/frontend/projects/use-bookmarked-projects";
1823import {
24+ getRandomColor,
1925 path_to_tab,
2026 trunc_middle,
2127 unreachable,
22- getRandomColor,
2328} from "@cocalc/util/misc";
24-
2529import {
2630 buildNavigationTree,
2731 PageInfo,
@@ -32,11 +36,6 @@ import {
3236 type ProjectInfo,
3337} from "./build-tree";
3438import type { NavigationTreeNode } from "./dialog";
35- import { switchAccountPage } from "@cocalc/frontend/account/util";
36- import { labels } from "@cocalc/frontend/i18n";
37- import type { EditorSpec } from "@cocalc/frontend/frame-editors/frame-tree/types";
38- import type { FixedTab } from "@cocalc/frontend/project/page/file-tab";
39- import { FIXED_PROJECT_TABS } from "@cocalc/frontend/project/page/file-tab";
4039import {
4140 ensureFrameFilePath,
4241 focusFrameWithRetry,
@@ -171,8 +170,12 @@ function extractFramesFromTree(
171170 * - Current project (prioritized)
172171 * - All other projects
173172 * - Account pages (hardcoded, always available)
173+ *
174+ * @param skip - If true, skip computation and return empty array (used to avoid updates when dialog is closed)
174175 */
175- export function useNavigationTreeData(): NavigationTreeNode[] {
176+ export function useNavigationTreeData(
177+ skip: boolean = false,
178+ ): NavigationTreeNode[] {
176179 const intl = useIntl();
177180 const is_logged_in = useTypedRedux("account", "is_logged_in");
178181 const is_anonymous = useTypedRedux("account", "is_anonymous");
@@ -216,6 +219,10 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
216219 // This would improve navigation for frequently-used files across all projects,
217220 // following accessibility best practices for quick navigation dialogs
218221 const projectsData = useMemo(() => {
222+ // Short-circuit if dialog is closed
223+ if (skip) {
224+ return [];
225+ }
219226 // if (DEBUG) {
220227 // console.log("useNavigationTreeData - Building projectsData:", {
221228 // openProjectsLength: open_projects?.size,
@@ -308,7 +315,7 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
308315 if (starred_files) {
309316 const allStarred = Array.isArray(starred_files)
310317 ? starred_files
311- : ( starred_files.toArray?.() ?? []) ;
318+ : starred_files.toArray?.() ?? [];
312319 starredFiles = allStarred.filter(
313320 (path) => !openFilePaths.has(path),
314321 );
@@ -321,7 +328,7 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
321328 if (otherStarredFiles) {
322329 const allStarred = Array.isArray(otherStarredFiles)
323330 ? otherStarredFiles
324- : ( otherStarredFiles.toArray?.() ?? []) ;
331+ : otherStarredFiles.toArray?.() ?? [];
325332 starredFiles = allStarred.filter(
326333 (path) => !openFilePaths.has(path),
327334 );
@@ -339,6 +346,7 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
339346 })
340347 .filter((p): p is ProjectInfo => p !== null);
341348 }, [
349+ skip,
342350 project_map,
343351 open_projects,
344352 project_id,
@@ -355,7 +363,7 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
355363
356364 // Build ProjectInfo for bookmarked projects (excluding open projects)
357365 const bookmarkedProjectsData = useMemo(() => {
358- if (!bookmarksInitialized || !project_map) {
366+ if (skip || !bookmarksInitialized || !project_map) {
359367 return [];
360368 }
361369
@@ -377,9 +385,13 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
377385 } as ProjectInfo;
378386 })
379387 .filter((p): p is ProjectInfo => p !== null);
380- }, [bookmarkedProjectIds, project_map, bookmarksInitialized]);
388+ }, [skip, bookmarkedProjectIds, project_map, bookmarksInitialized]);
381389
382390 const appPages: AppPageInfo[] = useMemo(() => {
391+ if (skip) {
392+ return [];
393+ }
394+
383395 const pages: AppPageInfo[] = [];
384396
385397 const projectsLabel = intl.formatMessage(labels.projects);
@@ -412,10 +424,14 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
412424 }
413425
414426 return pages;
415- }, [intl, is_logged_in, is_anonymous]);
427+ }, [skip, intl, is_logged_in, is_anonymous]);
416428
417429 // Build the complete navigation tree
418430 const treeData = useMemo(() => {
431+ if (skip) {
432+ return [];
433+ }
434+
419435 const currentProject =
420436 projectsData.find((p) => p.id === project_id) || null;
421437 const otherProjects = projectsData.filter((p) => p.id !== project_id);
@@ -427,16 +443,18 @@ export function useNavigationTreeData(): NavigationTreeNode[] {
427443 appPages,
428444 intl,
429445 );
430- }, [projectsData, project_id, bookmarkedProjectsData, appPages, intl]);
446+ }, [skip, projectsData, project_id, bookmarkedProjectsData, appPages, intl]);
431447
432448 return treeData;
433449}
434450
435451/**
436452 * Hook that returns just the frame tree structure and active frames
437453 * (without the full tree data)
454+ *
455+ * @param skip - If true, skip computation and return empty values (used to avoid updates when dialog is closed)
438456 */
439- export function useActiveFrameData(): {
457+ export function useActiveFrameData(skip: boolean = false ): {
440458 frameTreeStructure: FrameTreeStructure | null;
441459 activeFrames: FrameInfo[];
442460 activeFileName?: string;
@@ -514,7 +532,8 @@ export function useActiveFrameData(): {
514532 const activeFrameTree = useRedux(frameTreePath);
515533
516534 const { activeFrames, frameTreeStructure } = useMemo(() => {
517- if (!activeEditorContext.activeFileName || !activeFrameTree) {
535+ // Short-circuit if dialog is closed
536+ if (skip || !activeEditorContext.activeFileName || !activeFrameTree) {
518537 return {
519538 activeFrames: [],
520539 frameTreeStructure: null,
@@ -549,6 +568,7 @@ export function useActiveFrameData(): {
549568 frameTreeStructure: result.treeStructure,
550569 };
551570 }, [
571+ skip,
552572 activeFrameTree,
553573 activeEditorContext.activeFileName,
554574 activeEditorContext.editorSpec,
@@ -567,9 +587,13 @@ export function useActiveFrameData(): {
567587/**
568588 * Hook that adds action handlers to tree nodes
569589 * Ties navigation to Redux actions and routing
590+ *
591+ * @param skip - If true, skip computation and return empty array (used to avoid updates when dialog is closed)
570592 */
571- export function useEnhancedNavigationTreeData(): NavigationTreeNode[] {
572- const treeData = useNavigationTreeData();
593+ export function useEnhancedNavigationTreeData(
594+ skip: boolean = false,
595+ ): NavigationTreeNode[] {
596+ const treeData = useNavigationTreeData(skip);
573597 const active_top_tab = useTypedRedux("page", "active_top_tab");
574598
575599 // Get project_id from active_top_tab (same logic as useNavigationTreeData)
@@ -582,6 +606,10 @@ export function useEnhancedNavigationTreeData(): NavigationTreeNode[] {
582606
583607 // Enhance tree nodes with action handlers
584608 const enhancedTreeData = useMemo(() => {
609+ if (skip || treeData.length === 0) {
610+ return [];
611+ }
612+
585613 const enhanceNode = (node: NavigationTreeNode): NavigationTreeNode => {
586614 if (node.navigationData) {
587615 const navData = node.navigationData;
@@ -720,7 +748,7 @@ export function useEnhancedNavigationTreeData(): NavigationTreeNode[] {
720748 };
721749
722750 return treeData.map(enhanceNode);
723- }, [treeData, project_id, page_actions]);
751+ }, [skip, treeData, project_id, page_actions]);
724752
725753 return enhancedTreeData;
726754}
0 commit comments