Skip to content

Commit bac6070

Browse files
authored
Merge pull request #2 from vbakke/v4-feat/dep-nav-in-heatmap
Allow navigation to and between activities in the heatmap
2 parents f1eb441 + 1bfb201 commit bac6070

File tree

3 files changed

+85
-17
lines changed

3 files changed

+85
-17
lines changed

src/app/pages/circular-heatmap/circular-heatmap.component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ <h2>Nothing to show</h2>
106106
<b>Dependencies</b>
107107
</mat-panel-title>
108108
</mat-expansion-panel-header>
109-
<app-dependency-graph [activityName]="showActivityDetails?.name || ''">
109+
<app-dependency-graph
110+
[activityName]="showActivityDetails?.name || ''"
111+
(activityClicked)="onDependencyClicked($event)">
110112
</app-dependency-graph>
111113
</mat-expansion-panel>
112114

@@ -248,9 +250,7 @@ <h2>Nothing to show</h2>
248250
(closed)="onPanelClosed(activity)">
249251
<mat-expansion-panel-header>
250252
<mat-panel-title>
251-
<button
252-
class="title-button"
253-
(click)="openActivityDetails(showActivityCard.dimension, activity['name'])">
253+
<button class="title-button" (click)="openActivityDetails(activity['uuid'])">
254254
{{ activity['name'] }}
255255
</button>
256256
</mat-panel-title>

src/app/pages/circular-heatmap/circular-heatmap.component.ts

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { Component, OnInit } from '@angular/core';
1+
import { Component, OnInit, OnDestroy } from '@angular/core';
22
import { equalArray } from 'src/app/util/util';
33
import { LoaderService } from 'src/app/service/loader/data-loader.service';
44
import * as d3 from 'd3';
5-
import { Router } from '@angular/router';
5+
import { Router, ActivatedRoute } from '@angular/router';
6+
import { Location } from '@angular/common';
67
import { MatChip } from '@angular/material/chips';
8+
import { Subject } from 'rxjs';
9+
import { takeUntil, distinctUntilChanged } from 'rxjs/operators';
710
import * as md from 'markdown-it';
811
import {
912
ModalMessageComponent,
@@ -22,7 +25,7 @@ import { ThemeService } from '../../service/theme.service';
2225
templateUrl: './circular-heatmap.component.html',
2326
styleUrls: ['./circular-heatmap.component.css'],
2427
})
25-
export class CircularHeatmapComponent implements OnInit {
28+
export class CircularHeatmapComponent implements OnInit, OnDestroy {
2629
Routing: string = '/activity-description';
2730
markdown: md = md();
2831
maxLevelOfMaturity: number = -1;
@@ -52,10 +55,15 @@ export class CircularHeatmapComponent implements OnInit {
5255
theme: string;
5356
theme_colors!: Record<string, string>;
5457

58+
private destroy$ = new Subject<void>();
59+
5560
constructor(
5661
private loader: LoaderService,
5762
private sectorService: SectorService,
5863
private themeService: ThemeService,
64+
private router: Router,
65+
private route: ActivatedRoute,
66+
private location: Location,
5967
public modal: ModalMessageComponent
6068
) {
6169
this.theme = this.themeService.getTheme();
@@ -110,6 +118,9 @@ export class CircularHeatmapComponent implements OnInit {
110118
// For now, just draw the sectors (no activities yet)
111119
this.loadCircularHeatMap('#chart', this.allSectors, this.dimensionLabels, this.maxLevel);
112120
console.log(`${perfNow()}: Page loaded: Circular Heatmap`);
121+
122+
// Check if there's a URL fragment and open the corresponding activity
123+
this.checkUrlFragmentForActivity();
113124
})
114125
.catch(err => {
115126
this.displayMessage(new DialogInfo(err.message, 'An error occurred'));
@@ -119,7 +130,7 @@ export class CircularHeatmapComponent implements OnInit {
119130
});
120131
});
121132
// Reactively handle theme changes (if user toggles later)
122-
this.themeService.theme$.subscribe((theme: string) => {
133+
this.themeService.theme$.pipe(takeUntil(this.destroy$)).subscribe((theme: string) => {
123134
const css = getComputedStyle(document.body);
124135
this.theme_colors = {
125136
background: css.getPropertyValue('--heatmap-background').trim(),
@@ -133,6 +144,22 @@ export class CircularHeatmapComponent implements OnInit {
133144
});
134145
}
135146

147+
ngOnDestroy(): void {
148+
this.destroy$.next();
149+
this.destroy$.complete();
150+
}
151+
152+
checkUrlFragmentForActivity() {
153+
// Check if there's a URL fragment that might be an activity UUID
154+
this.route.fragment
155+
.pipe(takeUntil(this.destroy$), distinctUntilChanged())
156+
.subscribe(fragment => {
157+
if (fragment && this.dataStore) {
158+
this.navigateToActivityByUuid(fragment);
159+
}
160+
});
161+
}
162+
136163
displayMessage(dialogInfo: DialogInfo) {
137164
this.modal.openDialog(dialogInfo);
138165
}
@@ -237,6 +264,14 @@ export class CircularHeatmapComponent implements OnInit {
237264
return this.sectorService.getSectorProgress(sector.activities);
238265
}
239266

267+
onDependencyClicked(activityName: string) {
268+
console.log(`${perfNow()}: Heat: Dependency clicked: '${activityName}'`);
269+
const activity = this.dataStore?.activityStore?.getActivityByName(activityName);
270+
if (activity?.uuid) {
271+
this.navigateToActivityByUuid(activity.uuid);
272+
}
273+
}
274+
240275
loadCircularHeatMap(
241276
dom_element_to_append_to: string,
242277
dataset: any,
@@ -540,38 +575,71 @@ export class CircularHeatmapComponent implements OnInit {
540575
console.log(`${perfNow()}: Heat: Card Panel closed: '${activity.name}'`);
541576
}
542577

543-
openActivityDetails(dimension: string, activityName: string) {
578+
openActivityDetails(uuid: string) {
544579
// Find the activity in the selected sector
545-
console.log(`${perfNow()}: Heat: Open Overlay: '${activityName}'`);
546-
if (!this.dataStore) {
547-
console.error(`Data store is not initialized. Cannot open activity ${activityName}`);
580+
if (!this.dataStore || !this.dataStore.activityStore) {
581+
console.error(`Data store is not initialized. Cannot open activity ${uuid}`);
548582
return;
549583
}
550584
if (!this.showActivityCard || !this.showActivityCard.activities) {
551585
this.showOverlay = true;
552586
return;
553587
}
554-
const activity = this.showActivityCard.activities.find(
555-
(a: any) => a.activityName === activityName || a.name === activityName
556-
);
588+
589+
const activity: Activity = this.dataStore.activityStore.getActivityByUuid(uuid);
557590
if (!activity) {
558591
this.showOverlay = true;
559592
return;
560593
}
594+
561595
// Prepare navigationExtras and details
562596
/* eslint-disable */
597+
console.log(`${perfNow()}: Heat: Open Overlay: '${activity.name}'`);
563598
this.showActivityDetails = activity;
564599
this.KnowledgeLabel = this.dataStore.getMetaString('knowledgeLabels', activity.difficultyOfImplementation.knowledge);
565600
this.TimeLabel = this.dataStore.getMetaString('labels', activity.difficultyOfImplementation.time);
566601
this.ResourceLabel = this.dataStore.getMetaString('labels', activity.difficultyOfImplementation.resources);
567602
this.UsefulnessLabel = this.dataStore.getMetaString('labels', activity.usefulness);
568603
this.showOverlay = true;
604+
605+
// Update URL with activity UUID as fragment
606+
if (activity.uuid) {
607+
this.router.navigate([], {
608+
relativeTo: this.route,
609+
fragment: activity.uuid,
610+
queryParamsHandling: 'preserve'
611+
});
612+
}
569613
/* eslint-enable */
570614
}
571615

616+
navigateToActivityByUuid(uuid: string) {
617+
console.log(`${perfNow()}: Heat: Attempting to open activity with UUID: ${uuid}`);
618+
if (!this.dataStore || !this.dataStore.activityStore) {
619+
console.error('Data store is not initialized. Cannot open activity by UUID');
620+
return;
621+
}
622+
const activity: Activity = this.dataStore.activityStore.getActivityByUuid(uuid);
623+
const sector = this.allSectors.find(s => s.activities.some(a => a.uuid === uuid));
624+
if (activity && sector) {
625+
this.selectedSector = sector;
626+
this.showActivityCard = sector;
627+
this.openActivityDetails(activity.uuid);
628+
} else {
629+
// Only close the overlay, do not update the URL
630+
this.showOverlay = false;
631+
console.warn(`Heat: Activity with UUID ${uuid} not found.`);
632+
}
633+
}
634+
572635
closeOverlay() {
636+
// Clear the URL fragment when closing overlay
637+
this.router.navigate([], {
638+
relativeTo: this.route,
639+
fragment: undefined,
640+
queryParamsHandling: 'preserve',
641+
});
573642
this.showOverlay = false;
574-
// console.log(`${perfNow()}: Heat: Close Overlay: '${this.old_activityDetails.name}'`);
575643
}
576644

577645
toggleFilters() {

src/assets/Markdown Files/TODO-v4.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
- Teams: Bug: Editing name, pushes the item last
2121
- Teams: Allow editing dates for progress stages
2222
### Heatmap:
23-
- Heatmap: Add #uuid to URL, and allow navigation on clicks in dependencies
2423
- Heatmap: Fix: asterisk marks when modified
2524
- ViewController needs to know about changes vs temp storage
2625
- Heatmap: Bug: Clicking on grey sector leaves cursor on that sector
@@ -69,6 +68,7 @@
6968
- Meta.yaml: Allow admins to customize the terms 'Team' and 'Group' (e.g. to 'App' and 'Portfolio')
7069

7170
# Done
71+
- Heatmap: Add #uuid to URL, and allow navigation on clicks in dependencies
7272
- Dependency: Make connecting nodes clickable for navigation
7373
- Dependency: Handle dependsOn uuid, not just name
7474
- Matrix: Dependency graph: Render in center of page

0 commit comments

Comments
 (0)