mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-09 21:32:38 +00:00
fix: Search in side bar shows overlapping hosts when grouped by tags (#1303)
https://github.com/Termix-SSH/Support/issues/1303
This commit is contained in:
1 parent
afae2889cb
commit
e9fd092ebd
3 files changed
+68
-10
No files matched your search
@@ -820,9 +820,13 @@ export function SidebarTree({
|
||||
getItemKey: (index) => {
|
||||
const row = visibleRows[index];
|
||||
if (!row) return index;
|
||||
// Group-by views (e.g. by tags) can list the same host under several
|
||||
// group folders, so parentPath is folded in to keep each occurrence's
|
||||
// key unique -- a shared key across rows made the virtualizer collide
|
||||
// their measured positions and render them stacked on top of each other.
|
||||
return isFolder(row.item)
|
||||
? `folder:${row.item.path ?? row.item.name}`
|
||||
: `host:${row.item.id}`;
|
||||
: `${row.parentPath}>host:${row.item.id}`;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -34,7 +34,18 @@ export function folderHasMatch(folder: HostFolder, query: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export type VirtualRow = { item: Host | HostFolder; depth: number };
|
||||
/** Sentinel parent for rows sitting at the tree root (no folder). */
|
||||
export const ROOT_PARENT = "__root__";
|
||||
|
||||
// parentPath identifies which folder/group a row was collected under. Group-by
|
||||
// views (e.g. group by tags) can list the same host under several synthetic
|
||||
// group folders, so the host id alone isn't a unique row identity -- the
|
||||
// virtualizer needs parentPath+id to tell those rows apart.
|
||||
export type VirtualRow = {
|
||||
item: Host | HostFolder;
|
||||
depth: number;
|
||||
parentPath: string;
|
||||
};
|
||||
|
||||
function collectVisibleHostRows(
|
||||
host: Host,
|
||||
@@ -42,9 +53,10 @@ function collectVisibleHostRows(
|
||||
closedHostParents: Set<string>,
|
||||
out: VirtualRow[],
|
||||
depth: number,
|
||||
parentPath: string,
|
||||
): void {
|
||||
if (!query || hostMatchesQuery(host, query) || hostHasMatch(host, query)) {
|
||||
out.push({ item: host, depth });
|
||||
out.push({ item: host, depth, parentPath });
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -57,7 +69,14 @@ function collectVisibleHostRows(
|
||||
const isOpen = query ? true : !closedHostParents.has(hostExpandKey(host));
|
||||
if (!isOpen) return;
|
||||
for (const child of childHosts) {
|
||||
collectVisibleHostRows(child, query, closedHostParents, out, depth + 1);
|
||||
collectVisibleHostRows(
|
||||
child,
|
||||
query,
|
||||
closedHostParents,
|
||||
out,
|
||||
depth + 1,
|
||||
`${parentPath}>host:${host.id}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,13 +87,15 @@ export function collectVisibleRows(
|
||||
out: VirtualRow[] = [],
|
||||
depth = 0,
|
||||
closedHostParents: Set<string> = new Set(),
|
||||
parentPath: string = ROOT_PARENT,
|
||||
): VirtualRow[] {
|
||||
for (const child of children) {
|
||||
if (isFolder(child)) {
|
||||
const visible = query ? folderHasMatch(child, query) : true;
|
||||
if (!visible) continue;
|
||||
out.push({ item: child, depth });
|
||||
const childOpen = query ? true : openSet.has(child.path ?? child.name);
|
||||
const path = child.path ?? child.name;
|
||||
out.push({ item: child, depth, parentPath });
|
||||
const childOpen = query ? true : openSet.has(path);
|
||||
if (childOpen)
|
||||
collectVisibleRows(
|
||||
child.children,
|
||||
@@ -83,9 +104,17 @@ export function collectVisibleRows(
|
||||
out,
|
||||
depth + 1,
|
||||
closedHostParents,
|
||||
path,
|
||||
);
|
||||
} else {
|
||||
collectVisibleHostRows(child, query, closedHostParents, out, depth);
|
||||
collectVisibleHostRows(
|
||||
child,
|
||||
query,
|
||||
closedHostParents,
|
||||
out,
|
||||
depth,
|
||||
parentPath,
|
||||
);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
@@ -123,9 +152,6 @@ export function collectAllFolderPaths(
|
||||
return Array.from(paths).sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
/** Sentinel parent for rows sitting at the tree root (no folder). */
|
||||
export const ROOT_PARENT = "__root__";
|
||||
|
||||
export function rowKey(item: Host | HostFolder): string {
|
||||
return isFolder(item)
|
||||
? `folder:${item.path ?? item.name}`
|
||||
|
||||
@@ -133,6 +133,34 @@ describe("collectVisibleRows with sub-host nesting", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("collectVisibleRows with group-by views", () => {
|
||||
it("gives each occurrence of a multi-tag host a distinct parentPath", () => {
|
||||
// Group-by-tags puts the same host under one synthetic folder per tag,
|
||||
// so a host with two tags shows up twice in the tree with the same id.
|
||||
const multiTagHost = host("9", "shared-box");
|
||||
const tree: (Host | HostFolder)[] = [
|
||||
{
|
||||
name: "prod",
|
||||
path: "__group__:tag:prod",
|
||||
children: [multiTagHost],
|
||||
},
|
||||
{
|
||||
name: "web",
|
||||
path: "__group__:tag:web",
|
||||
children: [multiTagHost],
|
||||
},
|
||||
];
|
||||
const rows = collectVisibleRows(
|
||||
tree,
|
||||
"",
|
||||
new Set(["__group__:tag:prod", "__group__:tag:web"]),
|
||||
);
|
||||
const hostRows = rows.filter((r) => !isFolder(r.item));
|
||||
expect(hostRows).toHaveLength(2);
|
||||
expect(hostRows[0].parentPath).not.toBe(hostRows[1].parentPath);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildReorderRows", () => {
|
||||
it("keys folders by path and hosts by id", () => {
|
||||
const folder: HostFolder = {
|
||||
|
||||
Reference in new issue
Block a user