Collapse newly imported collector packages
Project disclosure state through stable link URLs so new or moved import content starts collapsed, duplicate-only imports preserve manual state, and enrichment-driven package ID changes retain the existing disclosure state.
This commit is contained in:
+10
-3
@@ -70,6 +70,7 @@ import {
|
|||||||
mergeCollectorEnrichment,
|
mergeCollectorEnrichment,
|
||||||
mergeCollectorPackages,
|
mergeCollectorPackages,
|
||||||
removeCollectorLinks,
|
removeCollectorLinks,
|
||||||
|
reconcileCollectorCollapsedPackageIds,
|
||||||
selectCollectorPackageLinks,
|
selectCollectorPackageLinks,
|
||||||
type CollectorWorkspaceFilter
|
type CollectorWorkspaceFilter
|
||||||
} from "./views/collector/collector-model";
|
} from "./views/collector/collector-model";
|
||||||
@@ -3596,13 +3597,19 @@ export function App(): ReactElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mergeCollectorResult = (result: CollectorInspectionResult, enrichment = false): void => {
|
const mergeCollectorResult = (result: CollectorInspectionResult, enrichment = false): void => {
|
||||||
setCollectorPackages((current) => {
|
const current = collectorPackagesRef.current;
|
||||||
const merged = enrichment
|
const merged = enrichment
|
||||||
? mergeCollectorEnrichment(current, result.packages)
|
? mergeCollectorEnrichment(current, result.packages)
|
||||||
: mergeCollectorPackages(current, result.packages);
|
: mergeCollectorPackages(current, result.packages);
|
||||||
collectorPackagesRef.current = merged.packages;
|
collectorPackagesRef.current = merged.packages;
|
||||||
return merged.packages;
|
setCollectorPackages(merged.packages);
|
||||||
});
|
setCollapsedCollectorPackageIds((collapsed) => reconcileCollectorCollapsedPackageIds(
|
||||||
|
collapsed,
|
||||||
|
current,
|
||||||
|
merged.packages,
|
||||||
|
result.packages,
|
||||||
|
!enrichment
|
||||||
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
const enrichCollectorResult = (packages: CollectorPackage[]): void => {
|
const enrichCollectorResult = (packages: CollectorPackage[]): void => {
|
||||||
|
|||||||
@@ -186,6 +186,37 @@ export function mergeCollectorEnrichment(current: CollectorPackage[], incoming:
|
|||||||
return mergeCollectorPackages(current, retained);
|
return mergeCollectorPackages(current, retained);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function reconcileCollectorCollapsedPackageIds(
|
||||||
|
current: Set<string>,
|
||||||
|
previousPackages: CollectorPackage[],
|
||||||
|
packages: CollectorPackage[],
|
||||||
|
incoming: CollectorPackage[],
|
||||||
|
collapseImported: boolean
|
||||||
|
): Set<string> {
|
||||||
|
const incomingUrls = new Set(incoming.flatMap((pkg) => pkg.links.map((link) => collectorUrlKey(link.url))));
|
||||||
|
const previousPackageByUrl = new Map<string, string>();
|
||||||
|
const collapsedUrls = new Set<string>();
|
||||||
|
for (const pkg of previousPackages) {
|
||||||
|
for (const link of pkg.links) {
|
||||||
|
const key = collectorUrlKey(link.url);
|
||||||
|
previousPackageByUrl.set(key, pkg.id);
|
||||||
|
if (current.has(pkg.id)) collapsedUrls.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const next = new Set<string>();
|
||||||
|
for (const pkg of packages) {
|
||||||
|
const keys = pkg.links.map((link) => collectorUrlKey(link.url));
|
||||||
|
const preservesCollapsedState = current.has(pkg.id) || keys.some((key) => collapsedUrls.has(key));
|
||||||
|
const receivesImportedContent = collapseImported && keys.some((key) => {
|
||||||
|
if (!incomingUrls.has(key)) return false;
|
||||||
|
const previousPackageId = previousPackageByUrl.get(key);
|
||||||
|
return !previousPackageId || previousPackageId !== pkg.id;
|
||||||
|
});
|
||||||
|
if (preservesCollapsedState || receivesImportedContent) next.add(pkg.id);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
export function selectCollectorPackageLinks(current: Set<string>, pkg: CollectorPackage, selected: boolean): Set<string> {
|
export function selectCollectorPackageLinks(current: Set<string>, pkg: CollectorPackage, selected: boolean): Set<string> {
|
||||||
const next = new Set(current);
|
const next = new Set(current);
|
||||||
for (const link of pkg.links) {
|
for (const link of pkg.links) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
mergeCollectorEnrichment,
|
mergeCollectorEnrichment,
|
||||||
mergeCollectorPackages,
|
mergeCollectorPackages,
|
||||||
removeCollectorLinks,
|
removeCollectorLinks,
|
||||||
|
reconcileCollectorCollapsedPackageIds,
|
||||||
selectCollectorPackageLinks,
|
selectCollectorPackageLinks,
|
||||||
type CollectorPackage
|
type CollectorPackage
|
||||||
} from "../src/renderer/views/collector/collector-model";
|
} from "../src/renderer/views/collector/collector-model";
|
||||||
@@ -335,6 +336,26 @@ describe("collector disclosure virtualization", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("CollectorView", () => {
|
describe("CollectorView", () => {
|
||||||
|
it("collapses newly imported packages while preserving existing disclosure state", () => {
|
||||||
|
const incoming = [{ ...packages[0], id: "temporary-import-package" }];
|
||||||
|
const collapsed = reconcileCollectorCollapsedPackageIds(new Set(["package-mixed"]), [packages[1]], packages, incoming, true);
|
||||||
|
|
||||||
|
expect(collapsed).toEqual(new Set(["package-mixed", "package-sbs"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not collapse an expanded package for a duplicate-only import", () => {
|
||||||
|
const incoming = [{ ...packages[0], id: "temporary-import-package" }];
|
||||||
|
|
||||||
|
expect([...reconcileCollectorCollapsedPackageIds(new Set(), packages, packages, incoming, true)]).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("moves collapsed state to regrouped package ids during enrichment", () => {
|
||||||
|
const before = [{ ...packages[0], id: "package-before" }];
|
||||||
|
const after = [{ ...packages[0], id: "package-after" }];
|
||||||
|
|
||||||
|
expect([...reconcileCollectorCollapsedPackageIds(new Set(["package-before"]), before, after, after, false)]).toEqual(["package-after"]);
|
||||||
|
});
|
||||||
|
|
||||||
it("reserves the exact package height while content visibility skips offscreen rows", () => {
|
it("reserves the exact package height while content visibility skips offscreen rows", () => {
|
||||||
const expanded = buildCollectorWorkspaceViewModel([packages[0]], "all", "", false, [], [], "", true).packages[0];
|
const expanded = buildCollectorWorkspaceViewModel([packages[0]], "all", "", false, [], [], "", true).packages[0];
|
||||||
const collapsed = { ...expanded, collapsed: true };
|
const collapsed = { ...expanded, collapsed: true };
|
||||||
|
|||||||
Reference in New Issue
Block a user