Skip to content

Commit 88136e2

Browse files
authored
Fix directory expansion logic to ensure unique parent directories are fetched and sorted by depth (#822)
Signed-off-by: achour94 <[email protected]>
1 parent b376610 commit 88136e2

File tree

2 files changed

+54
-42
lines changed

2 files changed

+54
-42
lines changed

src/components/directoryItemSelector/DirectoryItemSelector.tsx

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -260,21 +260,21 @@ export function DirectoryItemSelector({
260260
}, [convertRoots, types, snackError]);
261261

262262
const fetchDirectoryChildren = useCallback(
263-
(nodeId: UUID): void => {
263+
(nodeId: UUID): Promise<void> => {
264264
const typeList = types.includes(ElementType.DIRECTORY) ? [] : types;
265-
fetchDirectoryContent(nodeId, typeList)
265+
return fetchDirectoryContent(nodeId, typeList)
266266
.then((children) => {
267267
const childrenMatchedTypes = children.filter((item: ElementAttributes) =>
268268
contentFilter().has(item.type)
269269
);
270270

271271
if (childrenMatchedTypes.length > 0 && equipmentTypes && equipmentTypes.length > 0) {
272-
fetchElementsInfos(
272+
return fetchElementsInfos(
273273
childrenMatchedTypes.map((e: ElementAttributes) => e.elementUuid),
274274
types,
275275
equipmentTypes
276276
).then((childrenWithMetadata: ElementAttributes[]) => {
277-
const filtredChildren = itemFilter
277+
const filteredChildren = itemFilter
278278
? childrenWithMetadata.filter((val: ElementAttributes) => {
279279
// Accept every directory
280280
if (val.type === ElementType.DIRECTORY) {
@@ -285,12 +285,12 @@ export function DirectoryItemSelector({
285285
})
286286
: childrenWithMetadata;
287287
// update directory content
288-
addToDirectory(nodeId, filtredChildren);
288+
addToDirectory(nodeId, filteredChildren);
289289
});
290-
} else {
291-
// update directory content
292-
addToDirectory(nodeId, childrenMatchedTypes);
293290
}
291+
// update directory content
292+
addToDirectory(nodeId, childrenMatchedTypes);
293+
return Promise.resolve();
294294
})
295295
.catch((error) => {
296296
console.warn(`Could not update subs (and content) of '${nodeId}' : ${error.message}`);
@@ -301,13 +301,11 @@ export function DirectoryItemSelector({
301301

302302
// Helper function to fetch children for a node if not already loaded
303303
const fetchNodeChildrenIfNeeded = useCallback(
304-
(nodeId: UUID, delay: number = 0) => {
305-
setTimeout(() => {
306-
const node = nodeMap.current[nodeId];
307-
if (node && (!node.children || node.children.length === 0) && node.type === ElementType.DIRECTORY) {
308-
fetchDirectoryChildren(nodeId);
309-
}
310-
}, delay);
304+
async (nodeId: UUID): Promise<void> => {
305+
const node = nodeMap.current[nodeId];
306+
if (node && (!node.children || node.children.length === 0) && node.type === ElementType.DIRECTORY) {
307+
await fetchDirectoryChildren(nodeId);
308+
}
311309
},
312310
[fetchDirectoryChildren]
313311
);
@@ -320,18 +318,18 @@ export function DirectoryItemSelector({
320318

321319
const expandedArray = await getExpansionPathsForSelected(selected, expanded);
322320
setAutoExpandedNodes(expandedArray);
323-
fetchChildrenForExpandedNodes(expandedArray, fetchNodeChildrenIfNeeded);
321+
await fetchChildrenForExpandedNodes(expandedArray, fetchNodeChildrenIfNeeded);
324322
return true;
325323
}, [selected, expanded, fetchNodeChildrenIfNeeded]);
326324

327325
// Handle expansion from provided expanded prop
328-
const handleProvidedExpansion = useCallback((): boolean => {
326+
const handleProvidedExpansion = useCallback(async (): Promise<boolean> => {
329327
if (!expanded || expanded.length === 0) {
330328
return false;
331329
}
332330

333331
setAutoExpandedNodes(expanded);
334-
fetchChildrenForExpandedNodes(expanded, fetchNodeChildrenIfNeeded);
332+
await fetchChildrenForExpandedNodes(expanded, fetchNodeChildrenIfNeeded);
335333

336334
return true;
337335
}, [expanded, fetchNodeChildrenIfNeeded]);
@@ -345,7 +343,7 @@ export function DirectoryItemSelector({
345343
}
346344

347345
setAutoExpandedNodes(expandPath);
348-
fetchChildrenForExpandedNodes(expandPath, fetchNodeChildrenIfNeeded);
346+
await fetchChildrenForExpandedNodes(expandPath, fetchNodeChildrenIfNeeded);
349347

350348
return true;
351349
}, [fetchNodeChildrenIfNeeded]);
@@ -354,11 +352,15 @@ export function DirectoryItemSelector({
354352
const initializeExpansion = useCallback(async () => {
355353
// Priority 1: Handle selected items
356354
const selectedSuccess = await handleSelectedExpansion();
357-
if (selectedSuccess) return;
355+
if (selectedSuccess) {
356+
return;
357+
}
358358

359359
// Priority 2: Handle provided expanded items
360-
const expandedSuccess = handleProvidedExpansion();
361-
if (expandedSuccess) return;
360+
const expandedSuccess = await handleProvidedExpansion();
361+
if (expandedSuccess) {
362+
return;
363+
}
362364

363365
// Priority 3: Fall back to last selected directory
364366
await handleLastSelectedExpansion();

src/components/directoryItemSelector/utils.ts

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -92,29 +92,40 @@ export async function initializeFromLastSelected(): Promise<UUID[] | null> {
9292
}
9393

9494
/**
95-
* Fetches expansion paths for multiple selected items
95+
* Fetches expansion paths for multiple selected items, collecting unique parent directories
96+
* (excluding the selected items themselves) and sorting them by their minimum depth across
97+
* all paths. This ensures parents appear before their descendants in the returned array,
98+
* which is crucial for sequential fetching to avoid loading children before parents are
99+
* fully populated in the node map.
96100
* @param selectedIds Array of selected item UUIDs
97101
* @param expanded Optional existing expanded nodes
98-
* @returns Promise resolving to combined expansion array
102+
* @returns Promise resolving to combined expansion array sorted by depth
99103
*/
100104
export async function getExpansionPathsForSelected(selectedIds: UUID[], expanded: UUID[] = []): Promise<UUID[]> {
101105
const expandedSet = new Set<UUID>(expanded);
106+
const idToMinIndex = new Map<UUID, number>();
102107

103-
const fetchPromises = selectedIds.map(async (selectedId) => {
104-
const path = await fetchDirectoryPathSafe(selectedId);
108+
const paths = await Promise.all(selectedIds.map(fetchDirectoryPathSafe));
105109

106-
if (path && path.length > 0) {
107-
// Add all parent directories to the expanded set (exclude the item itself)
110+
paths
111+
.filter((p): p is ElementAttributes[] => !!p && p.length > 0)
112+
.forEach((path) => {
108113
path.forEach((element, index) => {
109114
if (index < path.length - 1) {
110-
expandedSet.add(element.elementUuid);
115+
const id = element.elementUuid;
116+
expandedSet.add(id);
117+
if (!idToMinIndex.has(id) || index < idToMinIndex.get(id)!) {
118+
idToMinIndex.set(id, index);
119+
}
111120
}
112121
});
113-
}
114-
});
122+
});
123+
124+
const expandedArray = Array.from(expandedSet).sort(
125+
(a, b) => (idToMinIndex.get(a) ?? Infinity) - (idToMinIndex.get(b) ?? Infinity)
126+
);
115127

116-
await Promise.all(fetchPromises);
117-
return Array.from(expandedSet);
128+
return expandedArray;
118129
}
119130

120131
/**
@@ -142,17 +153,16 @@ export async function saveLastSelectedDirectoryFromNode(node: {
142153
}
143154

144155
/**
145-
* Fetches children for expanded nodes with staggered delay to avoid overwhelming the server
156+
* Fetches children for expanded nodes sequentially to ensure parent nodes are loaded before children
146157
* @param expandedNodes Array of node UUIDs to fetch children for
147158
* @param fetchChildrenCallback Function to fetch children for a single node
148-
* @param delayBetweenRequests Delay in milliseconds between requests (default: 100ms)
149159
*/
150-
export function fetchChildrenForExpandedNodes(
160+
export async function fetchChildrenForExpandedNodes(
151161
expandedNodes: UUID[],
152-
fetchChildrenCallback: (nodeId: UUID, delay: number) => void,
153-
delayBetweenRequests: number = 100
154-
): void {
155-
expandedNodes.forEach((nodeId, index) => {
156-
fetchChildrenCallback(nodeId, index * delayBetweenRequests);
157-
});
162+
fetchChildrenCallback: (nodeId: UUID) => Promise<void>
163+
): Promise<void> {
164+
await expandedNodes.reduce(async (promise, nodeId) => {
165+
await promise;
166+
return fetchChildrenCallback(nodeId);
167+
}, Promise.resolve());
158168
}

0 commit comments

Comments
 (0)