Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions plans/2026-06-12-explorer-hover-tooltip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: explorer-hover-tooltip
issue: none
state: complete
version: 1
---

## Goal

Show the map's building tooltip (name + active metrics) when hovering files and folders in the sidebar explorer tree, anchored to the right edge of the hovered row.

## Tasks

### 1. Widen tooltip service input
- `CodeMapTooltipService.show()` currently takes layout `Node`; accept a minimal `TooltipNode` (name, optional id/attributes) so `CodeMapNode` works too
- Guard against missing `attributes`

### 2. Wire explorer rows
- `ExplorerTreeLevelComponent.onMouseEnter`: anchor tooltip at the row's right edge via the mouseenter event's currentTarget rect
- `onMouseLeave`: hide tooltip
- Template: pass `$event` to `onMouseEnter`

### 3. Tests + architecture check
- Unit tests for show/hide in explorerTreeLevel spec
- Run `npm run lint:architecture` (dependency-cruiser)

## Steps

- [x] Complete Task 1: Widen tooltip service input
- [x] Complete Task 2: Wire explorer rows
- [x] Complete Task 3: Tests + architecture check

## Notes

- Anchored to row (not mouse-following) per recommendation accepted by user
- Map tooltip is canvas-raycast driven only; no double-trigger from store
- Verified at runtime: tooltip shows for files and folders (aggregated metrics), hides on unhover and on click (consistent with map behavior); map building tooltip unaffected
- dependency-cruiser: 0 errors, 80 warnings — all pre-existing circulars in 3DExports/model, none involve the new sidebarExplorer → ui/codeMap import
- Follow-up: removed the native `[title]` tooltip from explorerTreeItemName — redundant now that the hover tooltip shows the node name
40 changes: 40 additions & 0 deletions plans/2026-06-12-navbar-extension-bar-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
name: navbar-extension-bar-fixes
issue: none
state: complete
version: 1
---

## Goal

Four small UI fixes: move the file extension bar from the top to directly above the bottom bar, add small vertical dividers between Compare | 3D Print | Settings in the navbar, and point the CC logo link to https://codecharta.com/.

## Tasks

### 1. Move file extension bar above the bottom bar
- Fix the `cc-file-extension-bar` host above the bottom bar (`bottom: var(--cc-bottom-bar-height)`), full width, with `bg-base-100` background
- Publish `--cc-file-extension-bar-height` via ResizeObserver (same pattern as bottomBar)
- Remove extension bar from `--cc-bars-height` calculation in `codeMap.component.ts` (nav bar only now)
- Subtract `--cc-file-extension-bar-height` in sidebar explorer/inspector height calcs
- Raise metrics bar bottom offset by `--cc-file-extension-bar-height`
- Update `screenshot.service.ts`: extension bar height counts toward bottom exclusion, not top

### 2. Navbar dividers
- Add short centered vertical line (`w-px h-4 bg-base-300`) between mode toggle and 3D Print button
- Add the same line between 3D Print and Settings buttons

### 3. CC logo link
- Change `navBarLogo` href from GitHub to https://codecharta.com/, update title

## Steps

- [x] Complete Task 1: Move file extension bar above the bottom bar
- [x] Complete Task 2: Navbar dividers
- [x] Complete Task 3: CC logo link
- [x] Run affected unit tests

## Notes

- User chose: separate strip above the bottom bar (not merged into footer); short centered divider style (not full-height DaisyUI divider)
- Verified at runtime via Electron + Playwright against `ng serve`: bar adjacency, metrics bar/sidebar clearance, dividers, logo link, segment toggle, screenshot-to-file download, window resize
- Pre-existing (not from this change): at narrow widths with both sidebars open, the centered floating metrics bar slides under the sidebars; type error in colorBandRow.component.spec.ts
3 changes: 3 additions & 0 deletions visualization/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/)
- **File Explorer sidebar**: The file tree was redesigned as a dedicated left-side sidebar drawer that overlays the codemap. The drawer adds a Shown/Flattened/Hidden chip row at the top, a sort control, and click-to-edit popovers for the active flatten and exclude rules.
- **Collapsible File Explorer**: The sidebar can now be collapsed via the `«` button in the header. Collapsed mode shows a small floating search box (with the kebab Flatten/Exclude menu) pinned at the top-left, freeing the full viewport for the codemap. Re-expand with the folder-tree button.
- **Folder overrides in the Color settings popover**: Marked packages can now be managed directly from the Color popover. A new "Folder Overrides" section lists each pinned folder with its file count, lets you recolor it via a swatch click (any color, no longer limited to the five marking colors), unpin it, and pin new folders through an inline folder search.
- **Explorer hover tooltip**: Hovering a file or folder in the File Explorer shows the same tooltip as hovering a building on the map — the node name plus its current area, height, and color metric values (folders show aggregated values) — anchored to the right of the hovered row. The native browser tooltip on row names was removed as redundant.

### Changed

- **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.
- **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.
- **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.
- **Floating metrics bar**: Replaced the top-of-page ribbon with a single rounded card that floats just above the bottom bar (`features/metricsBar/`). Each metric segment (Scenario, Area, Height, Color, Edges, Labels) opens a native popover for search and settings; the bar uses DaisyUI primitives and is free of `@angular/material`.
- **Metric settings popovers**: Restyled the Area, Height, Color, and Edge settings popovers to match the Label settings panel — same DaisyUI spacing (`gap-2.5 py-2 px-5`) and a full `btn btn-outline btn-error` reset button that stretches to the popover width.
Expand Down
4 changes: 2 additions & 2 deletions visualization/app/codeCharta/codeCharta.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

<cc-nav-bar></cc-nav-bar>

<cc-file-extension-bar></cc-file-extension-bar>

<cc-metrics-bar></cc-metrics-bar>

<cc-legend-panel></cc-legend-panel>

<cc-changelog-dialog></cc-changelog-dialog>

<cc-file-extension-bar></cc-file-extension-bar>

<cc-bottom-bar></cc-bottom-bar>
</div>
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { LinkColorHeightButtonComponent } from "../linkColorHeightButton/linkCol
],
host: {
class: "fixed flex bg-base-100 rounded-box shadow-lg border border-base-300",
"[style.bottom]": "'calc(var(--cc-bottom-bar-height, 32px) + 12px)'",
"[style.bottom]": "'calc(var(--cc-bottom-bar-height, 32px) + var(--cc-file-extension-bar-height, 17px) + 12px)'",
"[style.left]": "'0'",
"[style.right]": "'0'",
"[style.margin-inline]": "'auto'",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
</div>
<div class="navbar-end gap-1">
<cc-mode-toggle></cc-mode-toggle>
<div class="w-px h-4 bg-base-300 self-center mx-1"></div>
<cc-print-3d-button></cc-print-3d-button>
<div class="w-px h-4 bg-base-300 self-center mx-1"></div>
<cc-settings-button></cc-settings-button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<a
href="https://github.com/MaibornWolff/codecharta"
href="https://codecharta.com/"
target="_blank"
rel="noopener noreferrer"
class="flex items-center px-2"
title="CodeCharta on GitHub"
title="CodeCharta website"
>
<img src="codeCharta/assets/codecharta_logo.svg" alt="CodeCharta" class="h-8 w-auto" />
</a>
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<span
class="node-name"
[title]="node().name"
[class.line-through]="isFlattenedFile()"
[class.italic]="isUnclickable() || !isAreaMetricValid()"
[class.opacity-50]="!isAreaMetricValid()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
[class.ring-2]="isMarked()"
[class.ring-primary]="isMarked()"
[ngClass]="{ 'bg-error/20': isSelected() }"
(mouseenter)="onMouseEnter()"
(mouseenter)="onMouseEnter($event)"
(mouseleave)="onMouseLeave()"
(click)="onClick()"
(contextmenu)="openNodeContextMenu($event)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as RightClickedNodeDataSelector from "../../../../state/store/appStatus
import * as AreaMetricSelector from "../../../../state/store/dynamicSettings/areaMetric/areaMetric.selector"
import { appReducers, setStateMiddleware } from "../../../../state/store/state.manager"
import { CodeMapMouseEventService } from "../../../../ui/codeMap/codeMap.mouseEvent.service"
import { CodeMapTooltipService } from "../../../../ui/codeMap/codeMap.tooltip.service"
import { CodeMapBuilding } from "../../../../ui/codeMap/rendering/codeMapBuilding"
import { ThreeRendererService } from "../../../../ui/codeMap/threeViewer/threeRenderer.service"
import { ThreeSceneService } from "../../../../ui/codeMap/threeViewer/threeSceneService"
Expand Down Expand Up @@ -69,6 +70,13 @@ describe("ExplorerTreeLevelComponent", () => {
hoverNode: jest.fn(),
unhoverNode: jest.fn()
}
},
{
provide: CodeMapTooltipService,
useValue: {
show: jest.fn(),
hide: jest.fn()
}
}
]
})
Expand Down Expand Up @@ -220,4 +228,29 @@ describe("ExplorerTreeLevelComponent", () => {
expect(dispatchSpy).toHaveBeenCalledWith(setHoveredNodeId({ value: null }))
})
})

it("should show the tooltip for the hovered row and hide it on unhover", async () => {
// Arrange
const { container } = await render(ExplorerTreeLevelComponent, { inputs: componentInputs, excludeComponentDeclaration: true })
const tooltipService = TestBed.inject(CodeMapTooltipService)
const firstLevelFolder = container.querySelector("#\\/root\\/ParentLeaf")

// Act
await userEvent.hover(firstLevelFolder)

// Assert
await waitFor(() => {
expect(tooltipService.show).toHaveBeenCalledWith(
expect.objectContaining({ name: "ParentLeaf" }),
expect.any(Number),
expect.any(Number)
)
})

// Act
await userEvent.unhover(firstLevelFolder)

// Assert
await waitFor(() => expect(tooltipService.hide).toHaveBeenCalled())
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { toSignal } from "@angular/core/rxjs-interop"
import { CodeMapNode } from "../../../../codeCharta.model"
import { IdToBuildingService } from "../../../../services/idToBuilding/idToBuilding.service"
import { CodeMapMouseEventService } from "../../../../ui/codeMap/codeMap.mouseEvent.service"
import { CodeMapTooltipService } from "../../../../ui/codeMap/codeMap.tooltip.service"
import { ThreeRendererService } from "../../../../ui/codeMap/threeViewer/threeRenderer.service"
import { ThreeSceneService } from "../../../../ui/codeMap/threeViewer/threeSceneService"
import { isAreaValid, isLeaf } from "../../../../util/codeMapHelper"
Expand All @@ -28,6 +29,7 @@ export class ExplorerTreeLevelComponent implements OnInit {
private readonly idToBuildingService = inject(IdToBuildingService)
private readonly threeRendererService = inject(ThreeRendererService)
private readonly codeMapMouseEventService = inject(CodeMapMouseEventService)
private readonly codeMapTooltipService = inject(CodeMapTooltipService)
private readonly destroyRef = inject(DestroyRef)

private isScrollListenerRegistered = false
Expand Down Expand Up @@ -66,14 +68,17 @@ export class ExplorerTreeLevelComponent implements OnInit {
this.destroyRef.onDestroy(() => this.removeScrollListener())
}

onMouseEnter() {
onMouseEnter($event: MouseEvent) {
this.codeMapMouseEventService.hoverNode(this.node().id)
this.appStatusStore.setHoveredNodeId(this.node().id)
const rowRect = ($event.currentTarget as HTMLElement).getBoundingClientRect()
this.codeMapTooltipService.show(this.node(), rowRect.right, rowRect.top)
}

onMouseLeave() {
this.codeMapMouseEventService.unhoverNode()
this.appStatusStore.setHoveredNodeId(null)
this.codeMapTooltipService.hide()
}

onClick() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import { RulesPopoverComponent } from "../rulesPopover/rulesPopover.component"
host: {
class: "fixed left-0 z-[60] w-72 bg-base-100 overflow-hidden flex flex-col shadow-[2px_0_8px_-2px_rgba(0,0,0,0.15)]",
"[class.rounded-br-md]": "isCollapsed()",
"[style.top]": "'var(--cc-bars-height, 98px)'",
"[style.height]": "isCollapsed() ? 'auto' : 'calc(100vh - var(--cc-bars-height, 98px) - var(--cc-bottom-bar-height, 32px))'"
"[style.top]": "'var(--cc-bars-height, 49px)'",
"[style.height]":
"isCollapsed() ? 'auto' : 'calc(100vh - var(--cc-bars-height, 49px) - var(--cc-file-extension-bar-height, 17px) - var(--cc-bottom-bar-height, 32px))'"
}
})
export class SidebarExplorerComponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import { InspectorMetricsListComponent } from "../inspectorMetricsList/inspector
"[class.translate-x-full]": "!isVisible()",
"[class.pointer-events-none]": "!isVisible()",
"[attr.aria-hidden]": "!isVisible()",
"[style.top]": "'var(--cc-bars-height, 98px)'",
"[style.height]": "'calc(100vh - var(--cc-bars-height, 98px) - var(--cc-bottom-bar-height, 32px))'"
"[style.top]": "'var(--cc-bars-height, 49px)'",
"[style.height]":
"'calc(100vh - var(--cc-bars-height, 49px) - var(--cc-file-extension-bar-height, 17px) - var(--cc-bottom-bar-height, 32px))'"
}
})
export class SidebarInspectorComponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,13 @@ export class ScreenshotService {
// so measure the inner footer like bottomBar.component does
const bottomBarElement = document.querySelector("cc-bottom-bar") as HTMLElement | null
const bottomBarHeight = (bottomBarElement?.querySelector("footer") ?? bottomBarElement)?.offsetHeight ?? 0
const topBarsHeight = navBarHeight + fileExtensionBarHeight
const bottomBarsHeight = fileExtensionBarHeight + bottomBarHeight

const canvas = await html2canvas(document.querySelector("body"), {
removeContainer: true,
backgroundColor: null,
scrollY: -topBarsHeight,
height: Math.max(0, bodyHeight - topBarsHeight - bottomBarHeight),
scrollY: -navBarHeight,
height: Math.max(0, bodyHeight - navBarHeight - bottomBarsHeight),
ignoreElements(element) {
return (
tagsNamesToIgnore.has(element.tagName.toLowerCase()) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#codeMap {
position: fixed;
z-index: 0;
top: var(--cc-bars-height, 98px);
top: var(--cc-bars-height, 49px);
left: 0;
width: 100%;
height: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class CodeMapComponent implements AfterViewInit, OnDestroy {
}

private observeBarsHeight(): void {
const bars = ["cc-nav-bar", "cc-file-extension-bar"]
const bars = ["cc-nav-bar"]
.map(selector => document.querySelector(selector) as HTMLElement | null)
.filter((el): el is HTMLElement => el !== null)
if (bars.length === 0) {
Expand Down
20 changes: 13 additions & 7 deletions visualization/app/codeCharta/ui/codeMap/codeMap.tooltip.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Injectable } from "@angular/core"
import { Node, CcState } from "../../codeCharta.model"
import { CcState, KeyValuePair } from "../../codeCharta.model"
import { State } from "@ngrx/store"

export interface TooltipNode {
name: string
id?: number
attributes?: KeyValuePair
}

@Injectable({ providedIn: "root" })
export class CodeMapTooltipService {
private static readonly CURSOR_OFFSET_X = 12
Expand Down Expand Up @@ -34,7 +40,7 @@ export class CodeMapTooltipService {

constructor(private readonly state: State<CcState>) {}

show(node: Node, clientX: number, clientY: number) {
show(node: TooltipNode, clientX: number, clientY: number) {
if (!this.tooltipElement) {
this.createTooltipElement()
}
Expand All @@ -43,7 +49,7 @@ export class CodeMapTooltipService {
this.positionTooltip(clientX, clientY)
this.tooltipElement.style.opacity = "1"
this.visible = true
this.currentNodeId = node.id
this.currentNodeId = node.id ?? null
}

updatePosition(clientX: number, clientY: number) {
Expand Down Expand Up @@ -89,14 +95,14 @@ export class CodeMapTooltipService {
document.body.appendChild(this.tooltipElement)
}

private populateTooltip(node: Node) {
private populateTooltip(node: TooltipNode) {
const { dynamicSettings } = this.state.getValue()
const { areaMetric, heightMetric, colorMetric } = dynamicSettings

const metrics = [
{ label: areaMetric, value: node.attributes[areaMetric] },
{ label: heightMetric, value: node.attributes[heightMetric] },
{ label: colorMetric, value: node.attributes[colorMetric] }
{ label: areaMetric, value: node.attributes?.[areaMetric] },
{ label: heightMetric, value: node.attributes?.[heightMetric] },
{ label: colorMetric, value: node.attributes?.[colorMetric] }
]

this.tooltipElement.textContent = ""
Expand Down
Loading
Loading