Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
5 changes: 5 additions & 0 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { ThemeService } from './service/theme.service';

@Component({
selector: 'app-root',
Expand All @@ -9,6 +10,10 @@ export class AppComponent implements OnInit {
title = 'DSOMM';
menuIsOpen: boolean = true;

constructor(private themeService: ThemeService) {
this.themeService.initTheme();
}

ngOnInit(): void {
let menuState: string | null = localStorage.getItem('state.menuIsOpen');
if (menuState === 'false') {
Expand Down
82 changes: 66 additions & 16 deletions src/app/component/circular-heatmap/circular-heatmap.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ModalMessageComponent,
DialogInfo,
} from '../modal-message/modal-message.component';
import { ThemeService } from '../../service/theme.service';
Comment thread
aguero-tech marked this conversation as resolved.
Comment thread
aguero-tech marked this conversation as resolved.
Comment thread
aguero-tech marked this conversation as resolved.

export interface activitySchema {
uuid: string;
Expand Down Expand Up @@ -63,27 +64,73 @@ export class CircularHeatmapComponent implements OnInit {
showOverlay: boolean;
showFilters: boolean;
markdown: md = md();
theme: string;
theme_colors: Record<string, string>;
themes: Record<string, Record<string, string>> = {
light: {
background: '#ffffff',
disabled: '#dddddd',
filled: 'green',
cursor: 'green',
},
night: {
background: '#dddddd',
disabled: '#888888',
filled: 'green',
cursor: 'green',
},
};
Comment thread
aguero-tech marked this conversation as resolved.
Outdated

constructor(
private yaml: ymlService,
private router: Router,
private themeService: ThemeService,
public modal: ModalMessageComponent
) {
this.showOverlay = false;
this.showFilters = true;
this.theme = 'light';
this.theme = this.themeService.getTheme();

this.theme_colors = this.themes[this.theme];
}

ngOnInit(): void {
console.log(`${this.perfNow()}s: ngOnInit`);
// Ensure that Levels and Teams load before MaturityData
// using promises, since ngOnInit does not support async/await
this.LoadMaturityLevels()
.then(() => this.LoadTeamsFromMetaYaml())
.then(() => this.LoadMaturityDataFromGeneratedYaml())
.then(() => {
console.log(`${this.perfNow()}s: set filters: ${this.chips?.length}`);
this.matChipsArray = this.chips.toArray();
});
const savedTheme = this.themeService.getTheme() || 'light';
this.themeService.setTheme(savedTheme); // sets .light-theme or .dark-theme

requestAnimationFrame(() => {
// Now the DOM has the correct class and CSS vars are live
const css = getComputedStyle(document.body);
this.theme_colors = {
background: css.getPropertyValue('--heatmap-background').trim(),
filled: css.getPropertyValue('--heatmap-filled').trim(),
disabled: css.getPropertyValue('--heatmap-disabled').trim(),
cursor: css.getPropertyValue('--heatmap-cursor').trim(),
stroke: css.getPropertyValue('--heatmap-stroke').trim(),
};

this.LoadMaturityLevels()
.then(() => this.LoadTeamsFromMetaYaml())
.then(() => this.LoadMaturityDataFromGeneratedYaml())
.then(() => {
this.matChipsArray = this.chips.toArray();
});
});

// Reactively handle theme changes (if user toggles later)
this.themeService.theme$.subscribe((theme: string) => {
const css = getComputedStyle(document.body);
this.theme_colors = {
background: css.getPropertyValue('--heatmap-background').trim(),
filled: css.getPropertyValue('--heatmap-filled').trim(),
disabled: css.getPropertyValue('--heatmap-disabled').trim(),
cursor: css.getPropertyValue('--heatmap-cursor').trim(),
stroke: css.getPropertyValue('--heatmap-stroke').trim(),
};

this.reColorHeatmap(); // repaint segments with new theme
});
}

@ViewChildren(MatChip) chips!: QueryList<MatChip>;
Expand Down Expand Up @@ -405,7 +452,6 @@ export class CircularHeatmapComponent implements OnInit {
.innerRadius(innerRadius)
.segmentHeight(segmentHeight)
.domain([0, 1])
.range(['white', 'green'])
.radialLabels(radial_labels)
.segmentLabels(segment_labels);

Expand Down Expand Up @@ -498,6 +544,7 @@ export class CircularHeatmapComponent implements OnInit {
var segmentLabels: any[] = [];

//console.log(segmentLabels)
let _self: any = this;

function chart(selection: any) {
selection.each(function (this: any, data: any) {
Expand Down Expand Up @@ -548,7 +595,7 @@ export class CircularHeatmapComponent implements OnInit {
.startAngle(sa)
.endAngle(ea)
)
.attr('stroke', '#252525')
.attr('stroke', _self.theme_colors['stroke'])
.attr('fill', function (d) {
return color(accessor(d));
});
Expand Down Expand Up @@ -611,7 +658,7 @@ export class CircularHeatmapComponent implements OnInit {
.append('path')
.attr('id', 'hover')
.attr('pointer-events', 'none')
.attr('stroke', 'green')
.attr('stroke', _self.theme_colors['cursor'])
Comment thread
aguero-tech marked this conversation as resolved.
Outdated
.attr('stroke-width', '7')
.attr('fill', 'transparent');
cursors
Expand Down Expand Up @@ -716,7 +763,7 @@ export class CircularHeatmapComponent implements OnInit {
noActivitytoGrey(): void {
for (var x = 0; x < this.ALL_CARD_DATA.length; x++) {
if (this.ALL_CARD_DATA[x]['Done%'] == -1) {
d3.select('#index-' + x).attr('fill', '#DCDCDC');
d3.select('#index-' + x).attr('fill', this.theme_colors['disabled']);
}
}
}
Expand Down Expand Up @@ -822,7 +869,7 @@ export class CircularHeatmapComponent implements OnInit {
var colorSector = d3
.scaleLinear<string, string>()
.domain([0, 1])
.range(['white', 'green']);
.range([this.theme_colors['background'], this.theme_colors['filled']]);

if (cntAll !== 0) {
this.ALL_CARD_DATA[index]['Done%'] = cntTrue / cntAll;
Expand All @@ -833,7 +880,10 @@ export class CircularHeatmapComponent implements OnInit {
} else {
this.ALL_CARD_DATA[index]['Done%'] = -1;
// console.log(`${this.ALL_CARD_DATA[index].SubDimension} ${this.ALL_CARD_DATA[index].Level} None`);
d3.select('#index-' + index).attr('fill', '#DCDCDC');
d3.select('#index-' + index).attr(
'fill',
this.theme_colors['disabled']
);
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/app/component/sidenav-buttons/sidenav-buttons.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<mat-nav-list>
<!-- Dynamic nav links -->
<a
mat-list-item
*ngFor="let option of Options; index as i"
Expand All @@ -7,3 +8,17 @@
<h3 mat-line>{{ Options[i] }}</h3>
</a>
</mat-nav-list>

<!-- Separate theme toggle outside nav-list -->
<mat-divider></mat-divider>

<mat-list>
<mat-list-item (click)="toggleTheme()" style="cursor: pointer">
<mat-icon mat-list-icon color="primary">
{{ isNightMode ? 'light_mode' : 'dark_mode' }}
</mat-icon>
<h3 mat-line>
{{ isNightMode ? 'Switch to Light Mode' : 'Switch to Dark Mode' }}
</h3>
</mat-list-item>
</mat-list>
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('SidenavButtonsComponent', () => {

it('check for navigation names being shown in the same order as options array', () => {
const HTMLElement: HTMLElement = fixture.nativeElement;
const NavigationList = HTMLElement.querySelectorAll('h3')!;
const NavigationList = HTMLElement.querySelectorAll('a > h3')!;
let NavigationNamesBeingShown = [];
for (var x = 0; x < NavigationList.length; x += 1) {
NavigationNamesBeingShown.push(NavigationList[x].textContent);
Expand Down
32 changes: 29 additions & 3 deletions src/app/component/sidenav-buttons/sidenav-buttons.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { ThemeService } from '../../service/theme.service';

@Component({
selector: 'app-sidenav-buttons',
templateUrl: './sidenav-buttons.component.html',
styleUrls: ['./sidenav-buttons.component.css'],
})
export class SidenavButtonsComponent {
export class SidenavButtonsComponent implements OnInit {
Options: string[] = [
'Overview',
'Matrix',
Expand Down Expand Up @@ -33,5 +34,30 @@ export class SidenavButtonsComponent {
'/about',
'/userday',
];
constructor() {}

isNightMode = false;

constructor(private themeService: ThemeService) {}

ngOnInit(): void {
const themePref = localStorage.getItem('theme');
this.isNightMode = themePref === 'dark';
this.applyTheme();
}

toggleTheme(): void {
console.log('[toggleTheme] Triggered');

this.isNightMode = !this.isNightMode;
const newTheme = this.isNightMode ? 'dark' : 'light';
this.themeService.setTheme(newTheme);
}

private applyTheme(): void {
if (this.isNightMode) {
document.body.classList.add('night-mode');
} else {
document.body.classList.remove('night-mode');
}
}
}
33 changes: 33 additions & 0 deletions src/app/service/theme.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class ThemeService {
private themeSubject = new BehaviorSubject<string>('light');
public readonly theme$ = this.themeSubject.asObservable();

constructor() {
const savedTheme = localStorage.getItem('theme') || 'light';
this.setTheme(savedTheme);
}

initTheme(): void {
const saved = localStorage.getItem('theme') || 'light';
this.setTheme(saved);
}

setTheme(theme: string): void {
document.body.classList.remove('light-theme', 'dark-theme');
document.body.classList.add(`${theme}-theme`);

// Force this before other reads
requestAnimationFrame(() => {
this.themeSubject.next(theme);
localStorage.setItem('theme', theme);
});
}

getTheme(): string {
return this.themeSubject.value;
}
}
Loading