fix(sidebar): cancel stale table search work across modes
This commit is contained in:
1 parent
e671b3da7d
commit
71136377e5
8 files changed
+763
-24
No files matched your search
@@ -40,6 +40,7 @@ import { sidebarTreeContextKey } from "@/lib/sidebar/sidebarTreeContext";
|
||||
import { createSidebarTreeRuntime, sidebarTreeRuntimeKey, type SidebarTreeRuntimeHostInstance } from "@/lib/sidebar/sidebarTreeRuntime";
|
||||
import { createSidebarPasteHandlerRegistry } from "@/lib/sidebar/sidebarPasteHandlerRegistry";
|
||||
import { insertSidebarTableSearchControls, isSidebarTableSearchControlNode } from "@/lib/sidebar/sidebarTableSearchControl";
|
||||
import { createSidebarTableSearchDebouncer, loadOrBuildSidebarTableSearchIndex, scheduleExclusiveSidebarTableSearchDebounce } from "@/lib/sidebar/sidebarTableSearchIndex";
|
||||
import TreeItem from "./TreeItem.vue";
|
||||
import SidebarTreeRuntimeHost from "./SidebarTreeRuntimeHost.vue";
|
||||
import SidebarTreeItemDialogs from "./SidebarTreeItemDialogs.vue";
|
||||
@@ -149,7 +150,10 @@ const searchRefreshedNodeIds = searchExpansionState.filteredNodeIds;
|
||||
// touched group open and reloading it again.
|
||||
const searchAutoExpandedNodeIds = searchExpansionState.autoExpandedNodeIds;
|
||||
let searchTimer: number | undefined;
|
||||
const tableSearchTimers = new Map<string, number>();
|
||||
const remoteTableSearchDebouncer = createSidebarTableSearchDebouncer();
|
||||
// Local-mode table searches debounce like remote ones: rapid typing coalesces
|
||||
// into a single index load/build instead of one full build per keystroke.
|
||||
const localTableSearchDebouncer = createSidebarTableSearchDebouncer();
|
||||
const tableSearchFocusRestoreTokens = new Map<string, number>();
|
||||
let tableSearchFocusRestoreTokenSeq = 0;
|
||||
let latestTableSearchInteractionParentId: string | null = null;
|
||||
@@ -244,8 +248,8 @@ watch(
|
||||
if (parentNodeIds.length === 0) return;
|
||||
|
||||
for (const parentNodeId of parentNodeIds) {
|
||||
window.clearTimeout(tableSearchTimers.get(parentNodeId));
|
||||
tableSearchTimers.delete(parentNodeId);
|
||||
remoteTableSearchDebouncer.cancel(parentNodeId);
|
||||
localTableSearchDebouncer.cancel(parentNodeId);
|
||||
tableSearchFocusRestoreTokens.delete(parentNodeId);
|
||||
store.setSidebarTableSearchQuery(parentNodeId, "");
|
||||
}
|
||||
@@ -486,15 +490,17 @@ function clearSearchScopeFilter() {
|
||||
}
|
||||
|
||||
function scheduleSidebarTableSearchRefresh(parentNodeId: string, options?: { focusRestore?: TableSearchFocusRestore }) {
|
||||
window.clearTimeout(tableSearchTimers.get(parentNodeId));
|
||||
if (isTreeSearchFiltering.value) return;
|
||||
if (isTreeSearchFiltering.value) {
|
||||
remoteTableSearchDebouncer.cancel(parentNodeId);
|
||||
localTableSearchDebouncer.cancel(parentNodeId);
|
||||
return;
|
||||
}
|
||||
const restoreToken = options?.focusRestore?.shouldRestoreFocus ? ++tableSearchFocusRestoreTokenSeq : 0;
|
||||
if (restoreToken) {
|
||||
tableSearchFocusRestoreTokens.clear();
|
||||
tableSearchFocusRestoreTokens.set(parentNodeId, restoreToken);
|
||||
}
|
||||
const timer = window.setTimeout(() => {
|
||||
tableSearchTimers.delete(parentNodeId);
|
||||
scheduleExclusiveSidebarTableSearchDebounce(parentNodeId, remoteTableSearchDebouncer, localTableSearchDebouncer, () => {
|
||||
void store.refreshSidebarTableSearch(parentNodeId).then(() => {
|
||||
if (!restoreToken) return;
|
||||
if (tableSearchFocusRestoreTokens.get(parentNodeId) !== restoreToken) return;
|
||||
@@ -503,8 +509,7 @@ function scheduleSidebarTableSearchRefresh(parentNodeId: string, options?: { foc
|
||||
if (!focusRestore || !isCurrentTableSearchInteraction(focusRestore)) return;
|
||||
restoreTableSearchInput(focusRestore);
|
||||
});
|
||||
}, 250);
|
||||
tableSearchTimers.set(parentNodeId, timer);
|
||||
});
|
||||
}
|
||||
|
||||
function captureTableSearchFocus(parentNodeId: string): TableSearchFocusRestore {
|
||||
@@ -607,10 +612,35 @@ function filterGloballyIndexedRegexTables(nodes: TreeNode[]): TreeNode[] {
|
||||
return mergeSidebarRegexIndexScopes(nodes, matchingScopes);
|
||||
}
|
||||
|
||||
function scheduleLocalSidebarTableSearchRefresh(parentNodeId: string, focusRestore?: TableSearchFocusRestore) {
|
||||
if (isTreeSearchFiltering.value) {
|
||||
localTableSearchDebouncer.cancel(parentNodeId);
|
||||
remoteTableSearchDebouncer.cancel(parentNodeId);
|
||||
return;
|
||||
}
|
||||
scheduleExclusiveSidebarTableSearchDebounce(parentNodeId, localTableSearchDebouncer, remoteTableSearchDebouncer, () => {
|
||||
void loadLocalTableSearchResults(parentNodeId, false, focusRestore);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadLocalTableSearchResults(parentNodeId: string, refresh = false, focusRestore?: TableSearchFocusRestore) {
|
||||
try {
|
||||
const entries = refresh ? await store.refreshSidebarTableSearchIndex(parentNodeId) : await store.loadSidebarTableSearchIndex(parentNodeId);
|
||||
localTableSearchResults.value = { ...localTableSearchResults.value, [parentNodeId]: entries };
|
||||
// A missing persisted index (null) means this scope was never indexed, so
|
||||
// only the currently loaded first page of children is searchable. That
|
||||
// silently misses alphabetically-late tables (e.g. "T_Erp_Nc_SuPlan_List"
|
||||
// for "erpncs") until an explicit index refresh happens. Build the index
|
||||
// on the first search so results always cover the complete table set,
|
||||
// matching the behavior of an explicit refresh. The scope key deduplicates
|
||||
// concurrent full builds, and an empty (cleared) query never builds.
|
||||
const query = store.sidebarTableSearchQueries[parentNodeId]?.trim() ?? "";
|
||||
const entries = await loadOrBuildSidebarTableSearchIndex(
|
||||
parentNodeId,
|
||||
query,
|
||||
() => store.loadSidebarTableSearchIndex(parentNodeId),
|
||||
() => store.refreshSidebarTableSearchIndex(parentNodeId),
|
||||
refresh,
|
||||
);
|
||||
if (query || refresh) localTableSearchResults.value = { ...localTableSearchResults.value, [parentNodeId]: entries };
|
||||
} finally {
|
||||
if (focusRestore) restoreTableSearchInput(focusRestore);
|
||||
}
|
||||
@@ -1099,7 +1129,7 @@ provide(sidebarTreeContextKey, {
|
||||
restoreTableSearchInput(focusRestore);
|
||||
localTableSearchFocusPending = false;
|
||||
});
|
||||
void loadLocalTableSearchResults(parentNodeId, false, focusRestore);
|
||||
scheduleLocalSidebarTableSearchRefresh(parentNodeId, focusRestore);
|
||||
} else scheduleSidebarTableSearchRefresh(parentNodeId, { focusRestore });
|
||||
},
|
||||
refreshTableSearchIndex: (parentNodeId) => void loadLocalTableSearchResults(parentNodeId, true),
|
||||
@@ -1999,10 +2029,8 @@ onUnmounted(() => {
|
||||
resetSidebarTreeDialogState();
|
||||
window.removeEventListener("keydown", onWindowKeydown);
|
||||
cancelPendingSidebarDataOpen();
|
||||
for (const timer of tableSearchTimers.values()) {
|
||||
window.clearTimeout(timer);
|
||||
}
|
||||
tableSearchTimers.clear();
|
||||
remoteTableSearchDebouncer.cancelAll();
|
||||
localTableSearchDebouncer.cancelAll();
|
||||
tableSearchFocusRestoreTokens.clear();
|
||||
latestTableSearchInteractionParentId = null;
|
||||
latestTableSearchInteractionId = 0;
|
||||
|
||||
Reference in new issue
Block a user