diff --git a/src/app/app.component.html b/src/app/app.component.html index 1cc32689..af1197e3 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,18 +1,11 @@ - - +
- Fork me on GitHubFork me on GitHub
+ + + -
+ \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 996312b7..6837c641 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -3,19 +3,41 @@ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.css'], + styleUrls: ['./app.component.css'], // This is for component-specific styles. }) export class AppComponent implements OnInit { title = 'DSOMM'; + isNightMode = false; menuIsOpen: boolean = true; ngOnInit(): void { + // Load menu state let menuState: string | null = localStorage.getItem('state.menuIsOpen'); + if (menuState === 'false') { setTimeout(() => { this.menuIsOpen = false; }, 600); } + + // Load night mode state + let nightModeState: string | null = + localStorage.getItem('state.isNightMode'); + + if (nightModeState === 'true') { + this.isNightMode = true; + document.body.classList.add('night-mode'); + } + } + + toggleTheme(): void { + this.isNightMode = !this.isNightMode; + if (this.isNightMode) { + document.body.classList.add('night-mode'); + } else { + document.body.classList.remove('night-mode'); + } + localStorage.setItem('state.isNightMode', this.isNightMode.toString()); } toggleMenu(): void { diff --git a/src/app/component/matrix/matrix.component.html b/src/app/component/matrix/matrix.component.html index 7831cfe5..9eed13a0 100644 --- a/src/app/component/matrix/matrix.component.html +++ b/src/app/component/matrix/matrix.component.html @@ -6,13 +6,26 @@ + [selected]="currentSubDimensions.includes(subDimension)"> {{ subDimension }} + + + + + Activity Tag Filter diff --git a/src/app/component/matrix/matrix.component.ts b/src/app/component/matrix/matrix.component.ts index c91e59ee..37f1abe5 100644 --- a/src/app/component/matrix/matrix.component.ts +++ b/src/app/component/matrix/matrix.component.ts @@ -1,4 +1,11 @@ -import { Component, OnInit, ElementRef, ViewChild } from '@angular/core'; +import { + Component, + OnInit, + ElementRef, + ViewChild, + ViewChildren, + QueryList, +} from '@angular/core'; import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { FormControl } from '@angular/forms'; import { Observable } from 'rxjs'; @@ -16,6 +23,7 @@ export interface MatrixElement { Dimension: string; SubDimension: string; } + @UntilDestroy() @Component({ selector: 'app-matrix', @@ -24,14 +32,10 @@ export interface MatrixElement { }) export class MatrixComponent implements OnInit { MATRIX_DATA: MatrixElement[] = []; - Routing: string = '/activity-description'; - YamlObject: any; levels: string[] = []; - displayedColumns: string[] = ['Dimension', 'SubDimension']; - lvlColumn: string[] = []; allRows: string[] = []; dataSource: any = new MatTableDataSource(this.MATRIX_DATA); @@ -39,6 +43,21 @@ export class MatrixComponent implements OnInit { activityVisible: string[] = []; allDimensionNames: string[] = []; + // For autocomplete filters + filteredSubDimension: Observable; + filteredActivities: Observable; + autoCompeteResults: string[] = []; + autoCompleteActivityResults: string[] = []; + separatorKeysCodes: number[] = [ENTER, COMMA]; + rowCtrl = new FormControl(''); + rowCtrlActivity = new FormControl(''); + + listTags: string[] = []; + currentTags: string[] = []; + + listSubDimension: string[] = []; + currentSubDimensions: string[] = []; + constructor(private yaml: ymlService, private router: Router) { this.filteredSubDimension = this.rowCtrl.valueChanges.pipe( startWith(null), @@ -55,61 +74,53 @@ export class MatrixComponent implements OnInit { ) ); } + reload() { window.location.reload(); } - // function to initialize if level columns exists ngOnInit(): void { this.yaml.setURI('./assets/YAML/meta.yaml'); - // Function sets column header + // Set column header information this.yaml.getJson().subscribe(data => { this.YamlObject = data; - // Levels header this.levels = this.YamlObject['strings']['en']['maturity_levels']; - // pushes Levels in displayed column for (let k = 1; k <= this.levels.length; k++) { this.displayedColumns.push('level' + k); this.lvlColumn.push('level' + k); } }); - var activitySet = new Set(); + const activitySet = new Set(); - //gets value from generated folder + // Load generated data this.yaml.setURI('./assets/YAML/generated/generated.yaml'); - // Function sets data this.yaml.getJson().subscribe(data => { this.YamlObject = data; - this.allDimensionNames = Object.keys(this.YamlObject); for (let i = 0; i < this.allDimensionNames.length; i++) { - var subdimensionsInCurrentDimension = Object.keys( + const subdimensionsInCurrentDimension = Object.keys( this.YamlObject[this.allDimensionNames[i]] ); - for (let j = 0; j < subdimensionsInCurrentDimension.length; j++) { - var temp: any = { + let temp: any = { Dimension: this.allDimensionNames[i], SubDimension: subdimensionsInCurrentDimension[j], }; for (let k = 0; k < this.levels.length; k++) { - temp = { - ...temp, - [this.lvlColumn[k] as keyof number]: [], - }; + temp = { ...temp, [this.lvlColumn[k] as keyof number]: [] }; } - var activityInCurrentSubDimension: string[] = Object.keys( + const activityInCurrentSubDimension: string[] = Object.keys( this.YamlObject[this.allDimensionNames[i]][ subdimensionsInCurrentDimension[j] ] ); for (let a = 0; a < activityInCurrentSubDimension.length; a++) { - var currentActivityName = activityInCurrentSubDimension[a]; - var tagsInCurrentActivity: string[] = + const currentActivityName = activityInCurrentSubDimension[a]; + const tagsInCurrentActivity: string[] = this.YamlObject[this.allDimensionNames[i]][ subdimensionsInCurrentDimension[j] ][currentActivityName].tags; @@ -120,11 +131,10 @@ export class MatrixComponent implements OnInit { } try { - var lvlOfActivity: number = + const lvlOfActivity: number = this.YamlObject[this.allDimensionNames[i]][ subdimensionsInCurrentDimension[j] ][currentActivityName]['level']; - ( temp[ this.lvlColumn[lvlOfActivity - 1] as keyof number @@ -150,9 +160,13 @@ export class MatrixComponent implements OnInit { chipsControl = new FormControl(['chipsControl']); chipList!: MatChipList; - // @ViewChild(MatChip) - listTags: string[] = []; - currentTags: string[] = []; + @ViewChild('rowInput') rowInput!: ElementRef; + @ViewChild('activityInput') activityInput!: ElementRef; + + // Query only the subdimension chips using the unique template reference (e.g. "chipRef") + @ViewChildren('chipRef') subChips!: QueryList; + + // --- Tag Chips Methods --- createActivityTags(activitySet: Set): void { activitySet.forEach(tag => { this.listTags.push(tag); @@ -167,16 +181,15 @@ export class MatrixComponent implements OnInit { if (chip.selected) { this.currentTags = [...this.currentTags, chip.value]; this.activityVisible = this.currentTags; - this.updateActivitesBeingDisplayed(); } else { this.currentTags = this.currentTags.filter(o => o !== chip.value); this.activityVisible = this.currentTags; - this.updateActivitesBeingDisplayed(); } + this.updateActivitesBeingDisplayed(); } - listSubDimension: string[] = []; - currentSubDimensions: string[] = []; + // --- SubDimension Chips Methods --- + createSubDimensionList(): void { let i = 0; while (i < this.MATRIX_DATA.length) { @@ -190,61 +203,103 @@ export class MatrixComponent implements OnInit { } } - toggleSubDimensionSelection(chip: MatChip) { - chip.toggleSelected(); - if (chip.selected) { - this.currentSubDimensions = [...this.currentSubDimensions, chip.value]; - this.subDimensionVisible = this.currentSubDimensions; - this.selectedSubDimension(chip.value); - } else { + // Toggle subdimension selection based on its string value + toggleSubDimensionSelection(subDimension: string): void { + if (this.currentSubDimensions.includes(subDimension)) { + // Remove the subdimension from the filter this.currentSubDimensions = this.currentSubDimensions.filter( - o => o !== chip.value + sd => sd !== subDimension ); - this.subDimensionVisible = this.currentSubDimensions; - this.removeSubDimensionFromFilter(chip.value); + this.removeSubDimensionFromFilter(subDimension); + } else { + // Add the subdimension to the filter + this.currentSubDimensions = [...this.currentSubDimensions, subDimension]; + this.selectedSubDimension(subDimension); } + // Update the filter and displayed activities + this.subDimensionVisible = this.currentSubDimensions; + this.updateActivitesBeingDisplayed(); } - //chips - separatorKeysCodes: number[] = [ENTER, COMMA]; - rowCtrl = new FormControl(''); - rowCtrlActivity = new FormControl(''); - filteredSubDimension: Observable; - filteredActivities: Observable; - autoCompeteResults: string[] = []; - autoCompleteActivityResults: string[] = []; + // Method to reset/deselect all subdimension filters + resetSubDimensionFilters(): void { + this.subChips.forEach((chip: MatChip) => { + chip.selected = false; + }); + // Also clear the filter arrays and update the table + this.currentSubDimensions = []; + this.subDimensionVisible = []; + this.updateActivitesBeingDisplayed(); + } - @ViewChild('rowInput') rowInput!: ElementRef; - @ViewChild('activityInput') activityInput!: ElementRef; + // When a subdimension is selected manually, update the visible filter + selectedSubDimension(value: string): void { + if (!this.subDimensionVisible.includes(value)) { + this.subDimensionVisible.push(value); + } + this.updateActivitesBeingDisplayed(); + } + + // Method to select all subdimension filters + selectAllSubDimensionFilters(): void { + // Set the current filters to include all subdimensions + this.currentSubDimensions = [...this.listSubDimension]; + // Update the visible filters + this.subDimensionVisible = [...this.listSubDimension]; + // Loop through each chip and mark it as selected + this.subChips.forEach((chip: MatChip) => { + chip.selected = true; + }); + // Refresh the activities displayed in the table + this.updateActivitesBeingDisplayed(); + } + + removeSubDimensionFromFilter(value: string): void { + const index = this.subDimensionVisible.indexOf(value); + if (index >= 0) { + this.subDimensionVisible.splice(index, 1); + } + this.autoCompeteResults.push(value); + this.updateActivitesBeingDisplayed(); + } + // --- Filtering Methods (for autocomplete) --- + private filterSubDimension(value: string): string[] { + return this.autoCompeteResults.filter( + row => row.toLowerCase().indexOf(value.toLowerCase()) === 0 + ); + } + + private filterActivity(value: string): string[] { + return this.autoCompleteActivityResults.filter( + activity => activity.toLowerCase().indexOf(value.toLowerCase()) === 0 + ); + } + + // --- Table Data Update --- updateActivitesBeingDisplayed(): void { - // Iterate over all objects and create new MATRIX_DATA - var updatedActivities: any = []; + const updatedActivities: any = []; for (let i = 0; i < this.allDimensionNames.length; i++) { - var subdimensionsInCurrentDimension = Object.keys( + const subdimensionsInCurrentDimension = Object.keys( this.YamlObject[this.allDimensionNames[i]] ); - for (let j = 0; j < subdimensionsInCurrentDimension.length; j++) { - var temp: any = { + let temp: any = { Dimension: this.allDimensionNames[i], SubDimension: subdimensionsInCurrentDimension[j], }; for (let k = 0; k < this.levels.length; k++) { - temp = { - ...temp, - [this.lvlColumn[k] as keyof number]: [], - }; + temp = { ...temp, [this.lvlColumn[k] as keyof number]: [] }; } - var activityInCurrentSubDimension: string[] = Object.keys( + const activityInCurrentSubDimension: string[] = Object.keys( this.YamlObject[this.allDimensionNames[i]][ subdimensionsInCurrentDimension[j] ] ); for (let a = 0; a < activityInCurrentSubDimension.length; a++) { - var currentActivityName = activityInCurrentSubDimension[a]; - var tagsInCurrentActivity: string[] = + const currentActivityName = activityInCurrentSubDimension[a]; + const tagsInCurrentActivity: string[] = this.YamlObject[this.allDimensionNames[i]][ subdimensionsInCurrentDimension[j] ][currentActivityName].tags; @@ -258,11 +313,10 @@ export class MatrixComponent implements OnInit { } if (flag === 1) { try { - var lvlOfActivity: number = + const lvlOfActivity: number = this.YamlObject[this.allDimensionNames[i]][ subdimensionsInCurrentDimension[j] ][currentActivityName]['level']; - ( temp[ this.lvlColumn[lvlOfActivity - 1] as keyof number @@ -278,38 +332,10 @@ export class MatrixComponent implements OnInit { } } } - this.dataSource.data = JSON.parse(JSON.stringify(updatedActivities)); } - removeSubDimensionFromFilter(row: string): void { - let index = this.subDimensionVisible.indexOf(row); - if (index >= 0) { - this.subDimensionVisible.splice(index, 1); - } - this.autoCompeteResults.push(row); - this.updateActivitesBeingDisplayed(); - } - - //Add chips - selectedSubDimension(value: string): void { - this.subDimensionVisible.push(value); - this.updateActivitesBeingDisplayed(); - } - - private filterSubDimension(value: string): string[] { - return this.autoCompeteResults.filter( - row => row.toLowerCase().indexOf(value.toLowerCase()) === 0 - ); - } - private filterActivity(value: string): string[] { - return this.autoCompleteActivityResults.filter( - activity => activity.toLowerCase().indexOf(value.toLowerCase()) === 0 - ); - } - - // activity description routing + providing parameters - + // --- Routing --- navigate( uuid: String, dim: string, @@ -317,7 +343,7 @@ export class MatrixComponent implements OnInit { lvl: Number, activityName: string ) { - let navigationExtras: NavigationExtras = { + const navigationExtras: NavigationExtras = { queryParams: { uuid: uuid, dimension: dim, diff --git a/src/custom-theme.scss b/src/custom-theme.scss index 85ffe50c..d1b590f5 100644 --- a/src/custom-theme.scss +++ b/src/custom-theme.scss @@ -1,44 +1,80 @@ - -// Custom Theming for Angular Material -// For more information: https://material.angular.io/guide/theming @use '@angular/material' as mat; -// Plus imports for other components in your app. -// Include the common styles for Angular Material. We include this here so that you only -// have to load a single css file for Angular Material in your app. -// Be sure that you only ever include this mixin once! +// ---------------------------------------------- +// Custom Theme Maps for Body Styles +// ---------------------------------------------- +$light-theme: ( + background: white, + text: black, + link: blue +); -$custom-typography: mat.define-typography-level( - $font-family: montserrat, - $font-weight: 400, - $font-size: 1rem, - $line-height: 1, - $letter-spacing: normal, +$custom-dark-theme: ( // renamed to avoid conflict with Angular Material variables + background: #2c2c2c, + text: #e0e0e0, + link: #bb86fc ); +// ---------------------------------------------- +// Angular Material Core Styles & Typography +// ---------------------------------------------- +$custom-typography: mat.define-typography-level( + $font-family: montserrat, + $font-weight: 400, + $font-size: 1rem, + $line-height: 1, + $letter-spacing: normal, +); @include mat.core($custom-typography); -// Define the palettes for your theme using the Material Design palettes available in palette.scss -// (imported above). For each palette, you can optionally specify a default, lighter, and darker -// hue. Available color palettes: https://material.io/design/color/ -$DSOMM-primary: mat.define-palette(mat.$green-palette,400); +// ---------------------------------------------- +// Angular Material Palettes +// ---------------------------------------------- +$DSOMM-primary: mat.define-palette(mat.$green-palette, 400); $DSOMM-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400); - -// The warn palette is optional (defaults to red). $DSOMM-warn: mat.define-palette(mat.$red-palette); -// Create the theme object. A theme consists of configurations for individual -// theming systems such as "color" or "typography". -$DSOMM-theme: mat.define-light-theme(( - color: ( - primary: $DSOMM-primary, - accent: $DSOMM-accent, - warn: $DSOMM-warn, - ) +// ---------------------------------------------- +// Define Angular Material Light and Dark Themes +// ---------------------------------------------- +$DSOMM-light-theme: mat.define-light-theme(( + color: ( + primary: $DSOMM-primary, + accent: $DSOMM-accent, + warn: $DSOMM-warn, + ) )); -// Include theme styles for core and each component used in your app. -// Alternatively, you can import and @include the theme mixins for each component -// that you are using. -@include mat.all-component-themes($DSOMM-theme); +$DSOMM-dark-theme: mat.define-dark-theme(( + color: ( + primary: $DSOMM-primary, // using the same palette; adjust if needed + accent: $DSOMM-accent, + warn: $DSOMM-warn, + ) +)); + +// Include Angular Material styles for the light theme by default +@include mat.all-component-themes($DSOMM-light-theme); + +// ---------------------------------------------- +// Mixin to Apply Custom Body Styles +// ---------------------------------------------- +@mixin apply-theme($theme) { + background-color: map-get($theme, background); + color: map-get($theme, text); + + a { + color: map-get($theme, link); + } +} + +// Apply custom light theme styles to the body by default +body { + @include apply-theme($light-theme); +} +// When night mode is active, override with custom dark theme styles and include Angular Material dark theme +body.night-mode { + @include apply-theme($custom-dark-theme); + @include mat.all-component-themes($DSOMM-dark-theme); +} \ No newline at end of file diff --git a/src/styles.css b/src/styles.css index 3f9148db..815187b5 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,8 +1,31 @@ -/* You can add global styles to this file, and also import other style files */ +/* Global Styles and Reset */ +html, body { + height: 100%; + margin: 0; + font-family: Roboto, "Helvetica Neue", sans-serif; +} + +/* Light Theme Variables (Default) */ +:root { + --bg-color: white; + --text-color: black; + --link-color: blue; +} -html, body { height: 100%; } -body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } +/* Dark Theme Variables */ +body.night-mode { + --bg-color: #121212; + --text-color: #e0e0e0; + --link-color: blue; +} + +/* Apply Theme Variables */ +body { + background-color: var(--bg-color); + color: var(--text-color); +} +/* Existing Styles */ .userday table :is(td, th) { border: 1px solid black; padding: 0.3em; @@ -18,6 +41,7 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } margin-right: 10px; } -.usage-dimensions img { - max-width: 40rem; +/* Link Colors */ +a { + color: var(--link-color); } \ No newline at end of file