Skip to content

Commit faea768

Browse files
ChristianHuehnchristian-huehn-mwclaude
authored
feat(visualization): redesign node context menu (#4492)
Migrate the node context menu from ui/nodeContextMenu (Material, CDK overlay, SCSS) to features/nodeContextMenu following the new feature architecture: standalone OnPush components with signals, DaisyUI templates, ngrx confined to stores/ and selectors/. - Header row shows the node name and copies its path without the leading root segment on click - New "Show in Explorer" action (map right-clicks only) expands the File Explorer, scrolls to the node, and briefly highlights it via the new ExplorerRevealService - rightClickedNodeData state gains an origin field to distinguish map and explorer right-clicks - Folder color row keeps the five marking colors, adds a clear button for custom colors, and reuses the inline color picker, which moved to features/shared with a new brush-trigger variant - Unflatten entry shows the flatten icon rotated 180 degrees Co-authored-by: Christian Hühn <christian.huehn@maibornwolff.de> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 972f1bd commit faea768

75 files changed

Lines changed: 1254 additions & 1165 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
name: redesign-context-menu
3+
issue: none
4+
state: complete
5+
version: 1
6+
---
7+
8+
## Goal
9+
10+
Migrate the node context menu (right-click on map buildings and explorer rows) from `ui/nodeContextMenu` (Material, CDK overlay, SCSS) to a new `features/nodeContextMenu` feature — zoneless, signals, OnPush, daisyUI/Tailwind, no SCSS — matching the mock in `redesign-context-menu.png`. Includes a new "Show in Explorer" action and migrating the custom color picker.
11+
12+
## Clarified decisions
13+
14+
- Header row (copy icon + `…/name`) copies the node path **without the leading root/map-name segment**
15+
- Color swatch row stays **folders only** (mock just shows a file)
16+
- "Show in Explorer" does a **full reveal**: open the sidebar if collapsed, expand ancestors, scroll node into view, brief highlight
17+
- "Show in Explorer" is **hidden when the right-click came from the explorer**
18+
- Preset swatches use the **same marking colors as before** (from `mapColors.markingColors` state)
19+
- Custom color: **reuse `inlineColorPicker`** from metricsBar, extracted to a shared location
20+
21+
## Tasks
22+
23+
### 1. Feature scaffold + trigger
24+
- Create `features/nodeContextMenu/` with `components/`, `stores/` following sidebarExplorer/metricsBar conventions
25+
- Keep `setRightClickedNodeData` as the trigger; extend its state value with an `origin: "map" | "explorer"` field (update both dispatch sites: `codeMap.mouseEvent.service` and `explorerTreeLevel`)
26+
- Menu component subscribes via `toSignal`, renders a fixed-positioned native popover at the click coordinates (clamp to viewport); close on outside click, scroll, and node deselect — mirror old close conditions
27+
28+
### 2. Menu content per mock
29+
- Header: copy icon + truncated path (`…/<name>`), click copies path without root/map-name prefix, brief confirmation feedback
30+
- Entries with dividers per mock: Show in Explorer / Focus / Keep Highlight / Flatten / Exclude
31+
- Preserve toggle behavior of the old buttons (Unfocus, Unflatten, Remove Highlight) with state-dependent labels
32+
- Same store actions as the old menu (focusedNodePath, blacklist flatten/exclude, highlight)
33+
- Bottom row (folders only): preset swatches from `mapColors.markingColors`, mark/unmark via `markPackages`/`unmarkPackage`, ×-icon on the active swatch like before
34+
35+
### 3. Show in Explorer
36+
- Add a `revealNode(path)` API to the sidebarExplorer feature (exposed via its `facade.ts`): uncollapse sidebar, expand ancestor folders, scroll into view inside `#explorer-scroll`, transient highlight
37+
- Hide the entry when `origin === "explorer"`
38+
39+
### 4. Shared color picker
40+
- Extract `inlineColorPicker` out of `features/metricsBar/components/colorSettingsPopover/` into a shared features location; update metricsBar imports
41+
- Brush button in the color row opens it; picked color dispatches `markPackages`
42+
43+
### 5. Remove old implementation + tests
44+
- Delete `ui/nodeContextMenu/` (incl. `NodeContextMenuService`), the `openNodeContextMenu` effect, and their wiring; keep `ui/colorPicker` (still used by legendPanel etc.)
45+
- Specs for new components (AAA, "should" naming); migrate/replace nodeContextMenu e2e + po
46+
- Run `npm test`, `npm run lint:architecture`, verify at runtime (map right-click, explorer right-click, reveal, marking colors unchanged)
47+
48+
## Steps
49+
50+
- [x] Complete Task 1: Feature scaffold + trigger
51+
- [x] Complete Task 2: Menu content per mock
52+
- [x] Complete Task 3: Show in Explorer
53+
- [x] Complete Task 4: Shared color picker
54+
- [x] Complete Task 5: Remove old implementation + tests
55+
56+
## Review Feedback Addressed
57+
58+
1. **Clear custom color**: a custom (non-preset) mark color had no unmark affordance — added an ×-button next to the brush that appears when the folder is marked with a color no preset swatch represents
59+
2. **Exclude styling**: was red icon + grey text — now black like the other entries (removed the `isDestructive` styling from `contextMenuItem`)
60+
3. **Show icon**: the unflatten ("Show") entry uses the flatten icon rotated 180° (`fa-rotate-180`)
61+
4. **Color row squish**: when the clear button appeared (custom color set) the row overflowed the `w-64` card and flexbox shrank the swatches together — restructured to two `justify-between` groups with `shrink-0`, swatches at `h-6 w-6` (closer to the mock); verified at runtime that swatch widths stay 24px with the clear button visible
62+
5. **Subagent review** (verdict: mergeable, no critical findings) — applied: deleted leftover verification driver script; added unit tests for the close model (pointerdown outside/inside, wheel, resize), the reveal effect (ancestor expansion, sibling-prefix non-match, flash), unfocus-all, `_getCurrentMarkColor`, and the picker's `triggerIcon` branch; `markFolderRow` now derives the node path from the right-clicked-node store instead of an input (single source of truth with its mark state); brush button title follows `ariaLabel`; pending clamp `requestAnimationFrame` is cancelled on reopen; removed unused `clickOnExclude` from the page object. Confirmed-OK by review: close-condition parity (old click-listener leak fixed), focused-parent prefix false positive fixed, reveal effect ordering, architecture rules, e2e validity
63+
64+
## Notes
65+
66+
- Mock: `plans/context-menu/redesign-context-menu.png`
67+
- `origin: "codeMap" | "explorer"` added to `rightClickedNodeData` — drives the show-in-explorer visibility
68+
- Copy path strips the leading `/root/` segment; for the root node itself the node name is copied
69+
- Reveal: `ExplorerRevealService` (exported via sidebarExplorer facade) holds `revealedNodePath`; each `explorerTreeLevel` opens itself when an ancestor of that path and scrolls/flashes when it is the target; flash clears after 1.5 s
70+
- `inlineColorPicker` moved to `features/shared/components/inlineColorPicker/` with a new optional `triggerIcon` input (brush-button variant); metricsBar imports updated
71+
- Verified at runtime via Electron + Playwright under xvfb (no Playwright chromium for ubuntu26-arm64): map/explorer right-click, reveal+flash, preset colors unchanged (`#FF1D8E #1d8eff #1DFFFF #8eff1d #8e1dff`), custom pick + clear, flatten/show toggle, wheel/outside-click dismissal
72+
- `npm test` 379 suites green; dependency-cruiser 0 errors (80 pre-existing circular warnings)
73+
- Pre-existing, unrelated: `colorBandRow.component.spec` has a tsc-only typing error (`mapColorFor: string`); jest is unaffected
74+
- Explorer hover tooltip stays visible next to an open context menu and shows the metric twice when height and color metric are identical (pre-existing; noticed during verification)
45.5 KB
Loading
128 KB
Loading
120 KB
Loading
111 KB
Loading

visualization/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/)
1818

1919
### Changed
2020

21+
- **Node context menu**: Right-clicking a building or a File Explorer row opens a redesigned context menu (`features/nodeContextMenu/`, DaisyUI, free of `@angular/material`). The header row shows the node name and copies its path — without the leading root/map segment — on click. A new "Show in Explorer" action (offered for map right-clicks only) expands the File Explorer, scrolls to the node, and briefly highlights it. The folder color row keeps the five known marking colors and the custom color picker, and now shows a clear (×) button when the folder is marked with a custom color. The unflatten entry ("Show") displays the flatten icon rotated 180°.
2122
- **File extension bar**: The metric distribution strip moved from below the toolbar to the bottom of the screen, sitting directly above the bottom bar. The floating metrics bar, the legend, and both sidebars account for its height, and map screenshots exclude it at its new position.
2223
- **Toolbar**: Small vertical dividers now separate the Compare, 3D Print, and Settings controls on the right side, and the CodeCharta logo links to [codecharta.com](https://codecharta.com/) instead of the GitHub repository.
2324
- **Inspector sidebar**: The attribute sidebar was redesigned as the "Inspector" — a right-side drawer (`features/sidebarInspector/`) that slides in when a building is selected and closes via ✕ (which also deselects the building) or by deselecting. It shows the selected node's path with a copy-to-clipboard button and badges for the node type (file/folder) and a folder's file count, the current Area/Height/Color/Edge metric mapping with the selected node's value per mapped metric (buildings show their own values, folders the aggregate of the contained files; the color block carries an `inverted` marker), and all node metrics with severity-colored bars sized by the value's share of the whole map — a building with 100 rloc in a 1,000-rloc map fills 10% (severity respects higher-is-better metric directions). A `map | range` toggle in the METRICS header switches the bars between comparing against the whole map and comparing against the min/max range of all files. The header and metric mapping stay pinned at the top; only the metrics list scrolls. Metrics with empty (zero) values and no delta are greyed out and grouped in a collapsible "Empty metrics" section below the valued metrics; in comparison mode a metric that dropped to zero stays in the main list so its delta remains visible. Delta values and delta file counts are still shown in comparison mode. The Σ/median attribute-type toggle was removed.

visualization/app/app.config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { ChangelogFacade } from "app/codeCharta/features/changelog/facade"
99
import { provideAnimationsAsync } from "@angular/platform-browser/animations/async"
1010
import { AutoFitCodeMapEffect } from "app/codeCharta/state/effects/autoFitCodeMapChange/autoFitCodeMap.effect"
1111
import { LinkColorMetricToHeightMetricEffect } from "app/codeCharta/state/effects/linkColorMetricToHeightMetric/linkColorMetricToHeightMetric.effect"
12-
import { OpenNodeContextMenuEffect } from "app/codeCharta/state/effects/openNodeContextMenu/openNodeContextMenu.effect"
1312
import { RenderCodeMapEffect } from "app/codeCharta/state/effects/renderCodeMapEffect/renderCodeMap.effect"
1413
import { ResetChosenMetricsEffect } from "app/codeCharta/state/effects/resetChosenMetrics/resetChosenMetrics.effect"
1514
import { ResetSelectedEdgeMetricWhenItDoesntExistAnymoreEffect } from "app/codeCharta/state/effects/resetSelectedEdgeMetricWhenItDoesntExistAnymore/resetSelectedEdgeMetricWhenItDoesntExistAnymore.effect"
@@ -36,7 +35,6 @@ export const appConfig: ApplicationConfig = {
3635
UnfocusNodesEffect,
3736
AddBlacklistItemsIfNotResultsInEmptyMapEffect,
3837
UpdateAmountOfEdgePreviewsEffect,
39-
OpenNodeContextMenuEffect,
4038
BlacklistSearchPatternEffect,
4139
BlacklistExtensionEffect,
4240
ResetColorRangeEffect,

visualization/app/codeCharta/codeCharta.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<cc-code-map></cc-code-map>
66
<cc-sidebar-explorer></cc-sidebar-explorer>
77
<cc-sidebar-inspector></cc-sidebar-inspector>
8+
<cc-node-context-menu></cc-node-context-menu>
89

910
<cc-nav-bar></cc-nav-bar>
1011

visualization/app/codeCharta/codeCharta.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { LoadInitialFileService } from "./services/loadInitialFile/loadInitialFi
44
import { setIsLoadingFile } from "./state/store/appSettings/isLoadingFile/isLoadingFile.actions"
55
import { NavBarComponent } from "./features/navBar/facade"
66
import { BottomBarComponent } from "./features/bottomBar/facade"
7+
import { NodeContextMenuComponent } from "./features/nodeContextMenu/facade"
78
import { SidebarExplorerComponent } from "./features/sidebarExplorer/facade"
89
import { SidebarInspectorComponent } from "./features/sidebarInspector/facade"
910
import { FileExtensionBarComponent } from "./ui/fileExtensionBar/fileExtensionBar.component"
@@ -21,6 +22,7 @@ import { ErrorDialogComponent } from "./ui/dialogs/errorDialog/errorDialog.compo
2122
NavBarComponent,
2223
FileExtensionBarComponent,
2324
MetricsBarComponent,
25+
NodeContextMenuComponent,
2426
SidebarExplorerComponent,
2527
SidebarInspectorComponent,
2628
CodeMapComponent,

visualization/app/codeCharta/codeCharta.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,5 +354,6 @@ export interface AppStatus {
354354
nodeId: number
355355
xPositionOfRightClickEvent: number
356356
yPositionOfRightClickEvent: number
357+
origin: "codeMap" | "explorer"
357358
} | null
358359
}

0 commit comments

Comments
 (0)