Skip to content

Commit 87d6312

Browse files
authored
Merge pull request #381 from aguero-tech/forcontribution
Dark Theme v2.0
2 parents 6dc7d4a + 1277ef3 commit 87d6312

14 files changed

+373
-71
lines changed

src/app/app.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Component, OnInit } from '@angular/core';
2+
import { ThemeService } from './service/theme.service';
23

34
@Component({
45
selector: 'app-root',
@@ -9,6 +10,10 @@ export class AppComponent implements OnInit {
910
title = 'DSOMM';
1011
menuIsOpen: boolean = true;
1112

13+
constructor(private themeService: ThemeService) {
14+
this.themeService.initTheme();
15+
}
16+
1217
ngOnInit(): void {
1318
let menuState: string | null = localStorage.getItem('state.menuIsOpen');
1419
if (menuState === 'false') {

src/app/component/circular-heatmap/circular-heatmap.component.css

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,26 @@
7171
}
7272

7373
.overlay-close {
74-
border: black solid 1px;
75-
background-color: rgba(0, 0, 0, 0);
7674
border: none;
77-
color: black;
75+
background-color: rgba(0, 0, 0, 0);
7876
grid-column: 2/3;
7977
grid-row: 1/4;
8078
display: grid;
81-
justify-content: top;
79+
justify-content: center;
80+
align-items: start;
8281
margin-left: auto;
8382
}
83+
84+
/* overlay-close - light theme */
85+
:host-context(body.light-theme) .overlay-close {
86+
color: black;
87+
}
88+
89+
/* overlay-close - dark theme */
90+
:host-context(body.dark-theme) .overlay-close {
91+
color: white;
92+
}
93+
8494
.team-filter {
8595
padding: 0.4rem;
8696
grid-column: 2/3;
@@ -161,6 +171,6 @@ button.filter-toggle {
161171

162172
.overlay-details {
163173
width: 100%;
164-
}
174+
}
165175

166176
}

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

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ModalMessageComponent,
1616
DialogInfo,
1717
} from '../modal-message/modal-message.component';
18+
import { ThemeService } from '../../service/theme.service';
1819

1920
export interface activitySchema {
2021
uuid: string;
@@ -63,27 +64,56 @@ export class CircularHeatmapComponent implements OnInit {
6364
showOverlay: boolean;
6465
showFilters: boolean;
6566
markdown: md = md();
67+
theme: string;
68+
theme_colors!: Record<string, string>;
6669

6770
constructor(
6871
private yaml: ymlService,
6972
private router: Router,
73+
private themeService: ThemeService,
7074
public modal: ModalMessageComponent
7175
) {
7276
this.showOverlay = false;
7377
this.showFilters = true;
78+
this.theme = this.themeService.getTheme();
7479
}
7580

7681
ngOnInit(): void {
77-
console.log(`${this.perfNow()}s: ngOnInit`);
78-
// Ensure that Levels and Teams load before MaturityData
79-
// using promises, since ngOnInit does not support async/await
80-
this.LoadMaturityLevels()
81-
.then(() => this.LoadTeamsFromMetaYaml())
82-
.then(() => this.LoadMaturityDataFromGeneratedYaml())
83-
.then(() => {
84-
console.log(`${this.perfNow()}s: set filters: ${this.chips?.length}`);
85-
this.matChipsArray = this.chips.toArray();
86-
});
82+
const savedTheme = this.themeService.getTheme() || 'light';
83+
this.themeService.setTheme(savedTheme); // sets .light-theme or .dark-theme
84+
85+
requestAnimationFrame(() => {
86+
// Now the DOM has the correct class and CSS vars are live
87+
const css = getComputedStyle(document.body);
88+
this.theme_colors = {
89+
background: css.getPropertyValue('--heatmap-background').trim(),
90+
filled: css.getPropertyValue('--heatmap-filled').trim(),
91+
disabled: css.getPropertyValue('--heatmap-disabled').trim(),
92+
cursor: css.getPropertyValue('--heatmap-cursor').trim(),
93+
stroke: css.getPropertyValue('--heatmap-stroke').trim(),
94+
};
95+
96+
this.LoadMaturityLevels()
97+
.then(() => this.LoadTeamsFromMetaYaml())
98+
.then(() => this.LoadMaturityDataFromGeneratedYaml())
99+
.then(() => {
100+
this.matChipsArray = this.chips.toArray();
101+
});
102+
});
103+
104+
// Reactively handle theme changes (if user toggles later)
105+
this.themeService.theme$.subscribe((theme: string) => {
106+
const css = getComputedStyle(document.body);
107+
this.theme_colors = {
108+
background: css.getPropertyValue('--heatmap-background').trim(),
109+
filled: css.getPropertyValue('--heatmap-filled').trim(),
110+
disabled: css.getPropertyValue('--heatmap-disabled').trim(),
111+
cursor: css.getPropertyValue('--heatmap-cursor').trim(),
112+
stroke: css.getPropertyValue('--heatmap-stroke').trim(),
113+
};
114+
115+
this.reColorHeatmap(); // repaint segments with new theme
116+
});
87117
}
88118

89119
@ViewChildren(MatChip) chips!: QueryList<MatChip>;
@@ -405,7 +435,6 @@ export class CircularHeatmapComponent implements OnInit {
405435
.innerRadius(innerRadius)
406436
.segmentHeight(segmentHeight)
407437
.domain([0, 1])
408-
.range(['white', 'green'])
409438
.radialLabels(radial_labels)
410439
.segmentLabels(segment_labels);
411440

@@ -498,6 +527,7 @@ export class CircularHeatmapComponent implements OnInit {
498527
var segmentLabels: any[] = [];
499528

500529
//console.log(segmentLabels)
530+
let _self: any = this;
501531

502532
function chart(selection: any) {
503533
selection.each(function (this: any, data: any) {
@@ -548,7 +578,7 @@ export class CircularHeatmapComponent implements OnInit {
548578
.startAngle(sa)
549579
.endAngle(ea)
550580
)
551-
.attr('stroke', '#252525')
581+
.attr('stroke', _self.theme_colors['stroke'])
552582
.attr('fill', function (d) {
553583
return color(accessor(d));
554584
});
@@ -610,17 +640,11 @@ export class CircularHeatmapComponent implements OnInit {
610640
cursors
611641
.append('path')
612642
.attr('id', 'hover')
613-
.attr('pointer-events', 'none')
614-
.attr('stroke', 'green')
615-
.attr('stroke-width', '7')
616-
.attr('fill', 'transparent');
643+
.attr('pointer-events', 'none');
617644
cursors
618645
.append('path')
619646
.attr('id', 'selected')
620-
.attr('pointer-events', 'none')
621-
.attr('stroke', '#232323')
622-
.attr('stroke-width', '4')
623-
.attr('fill', 'transparent');
647+
.attr('pointer-events', 'none');
624648
});
625649
}
626650

@@ -716,7 +740,7 @@ export class CircularHeatmapComponent implements OnInit {
716740
noActivitytoGrey(): void {
717741
for (var x = 0; x < this.ALL_CARD_DATA.length; x++) {
718742
if (this.ALL_CARD_DATA[x]['Done%'] == -1) {
719-
d3.select('#index-' + x).attr('fill', '#DCDCDC');
743+
d3.select('#index-' + x).attr('fill', this.theme_colors['disabled']);
720744
}
721745
}
722746
}
@@ -822,7 +846,7 @@ export class CircularHeatmapComponent implements OnInit {
822846
var colorSector = d3
823847
.scaleLinear<string, string>()
824848
.domain([0, 1])
825-
.range(['white', 'green']);
849+
.range([this.theme_colors['background'], this.theme_colors['filled']]);
826850

827851
if (cntAll !== 0) {
828852
this.ALL_CARD_DATA[index]['Done%'] = cntTrue / cntAll;
@@ -833,7 +857,10 @@ export class CircularHeatmapComponent implements OnInit {
833857
} else {
834858
this.ALL_CARD_DATA[index]['Done%'] = -1;
835859
// console.log(`${this.ALL_CARD_DATA[index].SubDimension} ${this.ALL_CARD_DATA[index].Level} None`);
836-
d3.select('#index-' + index).attr('fill', '#DCDCDC');
860+
d3.select('#index-' + index).attr(
861+
'fill',
862+
this.theme_colors['disabled']
863+
);
837864
}
838865
}
839866
}

src/app/component/dependency-graph/dependency-graph.component.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { Component, OnInit, Input } from '@angular/core';
1+
import { Component, OnInit, Input, ElementRef } from '@angular/core';
22
import * as d3 from 'd3';
33
import { ymlService } from 'src/app/service/yaml-parser/yaml-parser.service';
4+
import { Subscription } from 'rxjs';
5+
import { ThemeService } from '../../service/theme.service';
46

57
export interface graphNodes {
68
id: string;
@@ -35,10 +37,24 @@ export class DependencyGraphComponent implements OnInit {
3537
@Input() subDimension: string = '';
3638
@Input() activityName: string = '';
3739

38-
constructor(private yaml: ymlService) {}
40+
private themeSub: Subscription | undefined;
41+
currentTheme: string = 'light'; // default
42+
43+
constructor(
44+
private yaml: ymlService,
45+
private elementRef: ElementRef,
46+
private themeService: ThemeService
47+
) {}
3948

4049
ngOnInit(): void {
4150
this.yaml.setURI('./assets/YAML/generated/generated.yaml');
51+
52+
this.currentTheme = this.themeService.getTheme();
53+
this.themeSub = this.themeService.theme$.subscribe(theme => {
54+
this.currentTheme = theme;
55+
this.applyTextColor(theme);
56+
});
57+
4258
// Function sets data
4359
this.yaml.getJson().subscribe(data => {
4460
this.graphData = { nodes: [], links: [] };
@@ -108,7 +124,26 @@ export class DependencyGraphComponent implements OnInit {
108124
}
109125
}
110126

127+
applyTextColor(theme: string): void {
128+
const fill = theme === 'dark' ? '#ffffff' : '#000000';
129+
const selectedNodeColor = theme === 'dark' ? '#666666' : 'yellow';
130+
const defaultNodeColor = this.COLOR_OF_NODE;
131+
132+
d3.select(this.elementRef.nativeElement)
133+
.selectAll('text')
134+
.attr('fill', fill);
135+
136+
d3.select(this.elementRef.nativeElement)
137+
.selectAll('circle')
138+
.attr('fill', (d: any) =>
139+
d.id === this.activityName ? selectedNodeColor : defaultNodeColor
140+
);
141+
}
142+
111143
generateGraph(activity: string): void {
144+
const selectedNodeColor =
145+
this.currentTheme === 'dark' ? '#666666' : 'yellow';
146+
112147
let svg = d3.select('svg'),
113148
width = +svg.attr('width'),
114149
height = +svg.attr('height');
@@ -162,10 +197,9 @@ export class DependencyGraphComponent implements OnInit {
162197
node
163198
.append('circle')
164199
.attr('r', 10)
165-
.attr('fill', function (d) {
166-
if (d.id == activity) return 'yellow';
167-
else return defaultNodeColor;
168-
});
200+
.attr('fill', (d: any) =>
201+
d.id === this.activityName ? selectedNodeColor : defaultNodeColor
202+
);
169203

170204
node
171205
.append('text')
@@ -175,6 +209,8 @@ export class DependencyGraphComponent implements OnInit {
175209
return d.id;
176210
});
177211

212+
this.applyTextColor(this.currentTheme);
213+
178214
this.simulation.nodes(this.graphData['nodes']).on('tick', ticked);
179215

180216
this.simulation.force('link').links(this.graphData['links']);

src/app/component/matrix/matrix.component.css

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,20 @@
6161
.tags-activity {
6262
font-weight: 800;
6363
font-style: italic;
64-
color: rgb(0, 113, 151);
6564
font-size: 12px;
6665
}
66+
/*tag activity - light */
67+
:host-context(body.light-theme) .tags-activity,
68+
:host-context(body.light-theme) .tags-activity span {
69+
color: rgb(0, 113, 151);
70+
}
71+
72+
/*tag activity - dark */
73+
:host-context(body.dark-theme) .tags-activity,
74+
:host-context(body.dark-theme) .tags-activity span {
75+
color: #397af4;
76+
}
77+
6778
.reset-button {
6879
background-color: #66bb6a;
6980
display: block;

src/app/component/sidenav-buttons/sidenav-buttons.component.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<mat-nav-list>
2+
<!-- Dynamic nav links -->
23
<a
34
mat-list-item
45
*ngFor="let option of Options; index as i"
@@ -7,3 +8,17 @@
78
<h3 mat-line>{{ Options[i] }}</h3>
89
</a>
910
</mat-nav-list>
11+
12+
<!-- Separate theme toggle outside nav-list -->
13+
<mat-divider></mat-divider>
14+
15+
<mat-list>
16+
<mat-list-item (click)="toggleTheme()" style="cursor: pointer">
17+
<mat-icon mat-list-icon color="primary">
18+
{{ isNightMode ? 'light_mode' : 'dark_mode' }}
19+
</mat-icon>
20+
<h3 mat-line>
21+
{{ isNightMode ? 'Switch to Light Mode' : 'Switch to Dark Mode' }}
22+
</h3>
23+
</mat-list-item>
24+
</mat-list>

src/app/component/sidenav-buttons/sidenav-buttons.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe('SidenavButtonsComponent', () => {
3232

3333
it('check for navigation names being shown in the same order as options array', () => {
3434
const HTMLElement: HTMLElement = fixture.nativeElement;
35-
const NavigationList = HTMLElement.querySelectorAll('h3')!;
35+
const NavigationList = HTMLElement.querySelectorAll('a > h3')!;
3636
let NavigationNamesBeingShown = [];
3737
for (var x = 0; x < NavigationList.length; x += 1) {
3838
NavigationNamesBeingShown.push(NavigationList[x].textContent);

src/app/component/sidenav-buttons/sidenav-buttons.component.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { Component } from '@angular/core';
1+
import { Component, OnInit } from '@angular/core';
2+
import { ThemeService } from '../../service/theme.service';
23

34
@Component({
45
selector: 'app-sidenav-buttons',
56
templateUrl: './sidenav-buttons.component.html',
67
styleUrls: ['./sidenav-buttons.component.css'],
78
})
8-
export class SidenavButtonsComponent {
9+
export class SidenavButtonsComponent implements OnInit {
910
Options: string[] = [
1011
'Overview',
1112
'Matrix',
@@ -33,5 +34,19 @@ export class SidenavButtonsComponent {
3334
'/about',
3435
'/userday',
3536
];
36-
constructor() {}
37+
38+
isNightMode = false;
39+
40+
constructor(private themeService: ThemeService) {}
41+
42+
ngOnInit(): void {
43+
const currentTheme = this.themeService.getTheme();
44+
this.isNightMode = currentTheme === 'dark';
45+
}
46+
47+
toggleTheme(): void {
48+
this.isNightMode = !this.isNightMode;
49+
const newTheme = this.isNightMode ? 'dark' : 'light';
50+
this.themeService.setTheme(newTheme);
51+
}
3752
}

src/app/component/teams/teams.component.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ h3 {
3333
height: 100px;
3434
background-color: #66bb6a;
3535
border-radius: 10px;
36+
color: white;
3637
margin-right: 20px;
3738
display: flex; /* Use flex layout */
3839
justify-content: center; /* Center horizontally */

0 commit comments

Comments
 (0)