diff --git a/apps/desktop/src/components/sidebar/ConnectionTree.vue b/apps/desktop/src/components/sidebar/ConnectionTree.vue index a665ea1aee..7cd41714e2 100644 --- a/apps/desktop/src/components/sidebar/ConnectionTree.vue +++ b/apps/desktop/src/components/sidebar/ConnectionTree.vue @@ -22,6 +22,7 @@ import { import { createSidebarLabelMatcher, matchSidebarLabel } from "@/lib/sidebar/sidebarSearch"; import { collectSidebarRegexIndexScopes, resolveSidebarRemoteSearchQuery, resolveSidebarSearchDispatchMode } from "@/lib/sidebar/sidebarRegexSearchIndex"; import { createSidebarSearchExpansionState } from "@/lib/sidebar/sidebarSearchExpansionState"; +import { createSidebarSearchLoadingTracker } from "@/lib/sidebar/sidebarSearchLoadingTracker"; import { buildTableTreeNodes } from "@/lib/table/tableTree"; import { isCancelSearchShortcut, isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut, isViewTableDdlShortcut } from "@/lib/editor/keyboardShortcuts"; import { sidebarNodeSupportsDdlView } from "@/lib/sidebar/sidebarTreeDdlShortcut"; @@ -78,6 +79,8 @@ const { toast } = useToast(); const searchQuery = ref(""); const deferredSearchQuery = ref(""); const regexMode = ref(false); +const sidebarSearchLoadingTracker = createSidebarSearchLoadingTracker(); +const isSidebarSearchLoading = ref(false); const showConnectedConnectionsOnly = ref(false); const isDisconnectingAllActiveConnections = ref(false); const searchInputRef = ref(); @@ -262,14 +265,20 @@ watch([deferredSearchQuery, regexMode], ([newQuery, isRegexMode], [oldQuery, was // Regex search is a read-only projection over live nodes and the local // table index. It must never trigger ensureConnected/listTables. const restoreTasks = restoreTrackedSearchTargets(); - void Promise.all([loadRegexTableSearchIndexes(), ...restoreTasks]) + const searchGeneration = sidebarSearchLoadingTracker.begin(); + isSidebarSearchLoading.value = true; + void Promise.allSettled([loadRegexTableSearchIndexes(), ...restoreTasks]) .then(() => { if (restoreTasks.length > 0) refreshActiveSidebarTableSearches(); }) - .catch(() => {}); + .finally(() => { + if (sidebarSearchLoadingTracker.end(searchGeneration)) isSidebarSearchLoading.value = false; + }); return; } if (dispatchMode === "none") { + sidebarSearchLoadingTracker.cancel(); + isSidebarSearchLoading.value = false; const restoreTasks = restoreTrackedSearchTargets(); if (restoreTasks.length > 0) { void Promise.all(restoreTasks) @@ -286,11 +295,21 @@ watch([deferredSearchQuery, regexMode], ([newQuery, isRegexMode], [oldQuery, was if (!newQuery && oldQuery) { searchExpansionState.clear(); } - Promise.all(tasks) + let searchGeneration = -1; + if (newQuery && tasks.length > 0) { + searchGeneration = sidebarSearchLoadingTracker.begin(); + isSidebarSearchLoading.value = true; + } else { + sidebarSearchLoadingTracker.cancel(); + isSidebarSearchLoading.value = false; + } + void Promise.allSettled(tasks) .then(() => { if (!newQuery && oldQuery) refreshActiveSidebarTableSearches(); }) - .catch(() => {}); + .finally(() => { + if (searchGeneration >= 0 && sidebarSearchLoadingTracker.end(searchGeneration)) isSidebarSearchLoading.value = false; + }); }); const searchableObjectGroupTypes = new Set(["group-tables", "group-dolt-system-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-synonyms", "group-packages", "group-types"]); @@ -2023,7 +2042,8 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes });