mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 07:05:24 +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) => {
|
getItemKey: (index) => {
|
||||||
const row = visibleRows[index];
|
const row = visibleRows[index];
|
||||||
if (!row) return 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)
|
return isFolder(row.item)
|
||||||
? `folder:${row.item.path ?? row.item.name}`
|
? `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;
|
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(
|
function collectVisibleHostRows(
|
||||||
host: Host,
|
host: Host,
|
||||||
@@ -42,9 +53,10 @@ function collectVisibleHostRows(
|
|||||||
closedHostParents: Set<string>,
|
closedHostParents: Set<string>,
|
||||||
out: VirtualRow[],
|
out: VirtualRow[],
|
||||||
depth: number,
|
depth: number,
|
||||||
|
parentPath: string,
|
||||||
): void {
|
): void {
|
||||||
if (!query || hostMatchesQuery(host, query) || hostHasMatch(host, query)) {
|
if (!query || hostMatchesQuery(host, query) || hostHasMatch(host, query)) {
|
||||||
out.push({ item: host, depth });
|
out.push({ item: host, depth, parentPath });
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -57,7 +69,14 @@ function collectVisibleHostRows(
|
|||||||
const isOpen = query ? true : !closedHostParents.has(hostExpandKey(host));
|
const isOpen = query ? true : !closedHostParents.has(hostExpandKey(host));
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
for (const child of childHosts) {
|
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[] = [],
|
out: VirtualRow[] = [],
|
||||||
depth = 0,
|
depth = 0,
|
||||||
closedHostParents: Set<string> = new Set(),
|
closedHostParents: Set<string> = new Set(),
|
||||||
|
parentPath: string = ROOT_PARENT,
|
||||||
): VirtualRow[] {
|
): VirtualRow[] {
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (isFolder(child)) {
|
if (isFolder(child)) {
|
||||||
const visible = query ? folderHasMatch(child, query) : true;
|
const visible = query ? folderHasMatch(child, query) : true;
|
||||||
if (!visible) continue;
|
if (!visible) continue;
|
||||||
out.push({ item: child, depth });
|
const path = child.path ?? child.name;
|
||||||
const childOpen = query ? true : openSet.has(child.path ?? child.name);
|
out.push({ item: child, depth, parentPath });
|
||||||
|
const childOpen = query ? true : openSet.has(path);
|
||||||
if (childOpen)
|
if (childOpen)
|
||||||
collectVisibleRows(
|
collectVisibleRows(
|
||||||
child.children,
|
child.children,
|
||||||
@@ -83,9 +104,17 @@ export function collectVisibleRows(
|
|||||||
out,
|
out,
|
||||||
depth + 1,
|
depth + 1,
|
||||||
closedHostParents,
|
closedHostParents,
|
||||||
|
path,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
collectVisibleHostRows(child, query, closedHostParents, out, depth);
|
collectVisibleHostRows(
|
||||||
|
child,
|
||||||
|
query,
|
||||||
|
closedHostParents,
|
||||||
|
out,
|
||||||
|
depth,
|
||||||
|
parentPath,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
@@ -123,9 +152,6 @@ export function collectAllFolderPaths(
|
|||||||
return Array.from(paths).sort((a, b) => a.localeCompare(b));
|
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 {
|
export function rowKey(item: Host | HostFolder): string {
|
||||||
return isFolder(item)
|
return isFolder(item)
|
||||||
? `folder:${item.path ?? item.name}`
|
? `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", () => {
|
describe("buildReorderRows", () => {
|
||||||
it("keys folders by path and hosts by id", () => {
|
it("keys folders by path and hosts by id", () => {
|
||||||
const folder: HostFolder = {
|
const folder: HostFolder = {
|
||||||
|
|||||||
Reference in new issue
Block a user