diff --git a/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.spec.ts b/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.spec.ts new file mode 100644 index 0000000000..fa312846fc --- /dev/null +++ b/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.spec.ts @@ -0,0 +1,90 @@ +import { TestBed } from "@angular/core/testing" +import { provideMockStore, MockStore } from "@ngrx/store/testing" +import { firstValueFrom, take } from "rxjs" +import { CodeMapNode, NodeType } from "../../../codeCharta.model" +import { accumulatedDataSelector } from "./accumulatedData.selector" +import { maxFolderDepthSelector } from "./maxFolderDepth.selector" + +function getValue() { + maxFolderDepthSelector.release() + const store = TestBed.inject(MockStore) + return firstValueFrom(store.select(maxFolderDepthSelector).pipe(take(1))) +} + +describe("maxFolderDepthSelector", () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [provideMockStore()] + }) + }) + + it("should return undefined when no project is loaded", async () => { + const store = TestBed.inject(MockStore) + store.overrideSelector(accumulatedDataSelector, { unifiedMapNode: undefined, unifiedFileMeta: undefined }) + + expect(await getValue()).toBeUndefined() + }) + + it("should return 1 for a root with only leaf children", async () => { + const root: CodeMapNode = { + name: "root", + type: NodeType.FOLDER, + attributes: {}, + path: "/root", + children: [ + { name: "a.ts", type: NodeType.FILE, attributes: {}, path: "/root/a.ts" }, + { name: "b.ts", type: NodeType.FILE, attributes: {}, path: "/root/b.ts" } + ] + } + const store = TestBed.inject(MockStore) + store.overrideSelector(accumulatedDataSelector, { unifiedMapNode: root, unifiedFileMeta: undefined }) + + expect(await getValue()).toBe(1) + }) + + it("should return deepest folder depth + 1 for a nested tree", async () => { + const root: CodeMapNode = { + name: "root", + type: NodeType.FOLDER, + attributes: {}, + path: "/root", + children: [ + { + name: "src", + type: NodeType.FOLDER, + attributes: {}, + path: "/root/src", + children: [ + { + name: "deep", + type: NodeType.FOLDER, + attributes: {}, + path: "/root/src/deep", + children: [{ name: "x.ts", type: NodeType.FILE, attributes: {}, path: "/root/src/deep/x.ts" }] + } + ] + }, + { name: "readme.md", type: NodeType.FILE, attributes: {}, path: "/root/readme.md" } + ] + } + const store = TestBed.inject(MockStore) + store.overrideSelector(accumulatedDataSelector, { unifiedMapNode: root, unifiedFileMeta: undefined }) + + // deepest folder ("deep") is at depth 2 → max = 3 + expect(await getValue()).toBe(3) + }) + + it("should clamp to at least 1 for a root with no children", async () => { + const root: CodeMapNode = { + name: "root", + type: NodeType.FOLDER, + attributes: {}, + path: "/root", + children: [] + } + const store = TestBed.inject(MockStore) + store.overrideSelector(accumulatedDataSelector, { unifiedMapNode: root, unifiedFileMeta: undefined }) + + expect(await getValue()).toBe(1) + }) +}) diff --git a/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.ts b/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.ts new file mode 100644 index 0000000000..0db1321867 --- /dev/null +++ b/visualization/app/codeCharta/state/selectors/accumulatedData/maxFolderDepth.selector.ts @@ -0,0 +1,25 @@ +import { createSelector } from "@ngrx/store" +import { CodeMapNode } from "../../../codeCharta.model" +import { accumulatedDataSelector } from "./accumulatedData.selector" + +function computeMaxFolderDepth(node: CodeMapNode, depth: number): number { + if (!node.children || node.children.length === 0) { + return depth - 1 + } + let max = depth + for (const child of node.children) { + const childDepth = computeMaxFolderDepth(child, depth + 1) + if (childDepth > max) { + max = childDepth + } + } + return max +} + +export const maxFolderDepthSelector = createSelector(accumulatedDataSelector, accumulatedData => { + const root = accumulatedData.unifiedMapNode + if (!root) { + return undefined + } + return Math.max(1, computeMaxFolderDepth(root, 0) + 1) +}) diff --git a/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.html b/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.html index 220c3e67e2..f725b49af9 100644 --- a/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.html +++ b/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.html @@ -14,7 +14,7 @@ [value]="floorLabelDepth$ | async" [onChange]="applyDebouncedFloorLabelDepth" [min]="1" - [max]="8" + [max]="(maxFloorLabelDepth$ | async) ?? 15" > Invert Area diff --git a/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.ts b/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.ts index 9f36ad5e78..f0ecec80ac 100644 --- a/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.ts +++ b/visualization/app/codeCharta/ui/ribbonBar/areaSettingsPanel/areaSettingsPanel.component.ts @@ -6,6 +6,7 @@ import { setEnableFloorLabels } from "../../../state/store/appSettings/enableFlo import { enableFloorLabelsSelector } from "../../../state/store/appSettings/enableFloorLabels/enableFloorLabels.selector" import { setFloorLabelDepth } from "../../../state/store/appSettings/floorLabelDepth/floorLabelDepth.actions" import { floorLabelDepthSelector } from "../../../state/store/appSettings/floorLabelDepth/floorLabelDepth.selector" +import { maxFolderDepthSelector } from "../../../state/selectors/accumulatedData/maxFolderDepth.selector" import { invertAreaSelector } from "../../../state/store/appSettings/invertArea/invertArea.selector" import { debounce } from "../../../util/debounce" import { setInvertArea } from "../../../state/store/appSettings/invertArea/invertArea.actions" @@ -27,6 +28,7 @@ export class AreaSettingsPanelComponent { margin$ = this.store.select(marginSelector) enableFloorLabels$ = this.store.select(enableFloorLabelsSelector) floorLabelDepth$ = this.store.select(floorLabelDepthSelector) + maxFloorLabelDepth$ = this.store.select(maxFolderDepthSelector) isInvertedArea$ = this.store.select(invertAreaSelector) constructor(private store: Store) {}