From 67a59e9e0b6431fea39717f308b26c636f52697a Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Thu, 21 Aug 2025 16:15:34 +0200 Subject: [PATCH 01/21] create theme service --- frontend/src/app/core/menu/menu.component.ts | 4 ++- .../src/app/services/theme.service.spec.ts | 16 +++++++++++ frontend/src/app/services/theme.service.ts | 27 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/services/theme.service.spec.ts create mode 100644 frontend/src/app/services/theme.service.ts diff --git a/frontend/src/app/core/menu/menu.component.ts b/frontend/src/app/core/menu/menu.component.ts index 19c1059d9..bfa2bcaa8 100644 --- a/frontend/src/app/core/menu/menu.component.ts +++ b/frontend/src/app/core/menu/menu.component.ts @@ -6,6 +6,7 @@ import { environment } from '@environments/environment'; import { AuthService } from '@services/auth.service'; import { filter, map } from 'rxjs/operators'; import { navIcons, userIcons } from '@shared/icons'; +import { ThemeService } from '@services/theme.service'; @Component({ selector: 'ia-menu', @@ -34,7 +35,8 @@ export class MenuComponent implements OnDestroy, OnInit { constructor( private authService: AuthService, private router: Router, - private route: ActivatedRoute + private route: ActivatedRoute, + private themeService: ThemeService, ) {} ngOnDestroy() { diff --git a/frontend/src/app/services/theme.service.spec.ts b/frontend/src/app/services/theme.service.spec.ts new file mode 100644 index 000000000..102938f6f --- /dev/null +++ b/frontend/src/app/services/theme.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ThemeService } from './theme.service'; + +describe('ThemeService', () => { + let service: ThemeService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ThemeService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/services/theme.service.ts b/frontend/src/app/services/theme.service.ts new file mode 100644 index 000000000..45bfa1b3d --- /dev/null +++ b/frontend/src/app/services/theme.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, combineLatest, fromEvent, map, Observable, startWith } from 'rxjs'; + +enum Theme { + DARK = 'dark', + LIGHT = 'light', +} + +@Injectable({ + providedIn: 'root' +}) +export class ThemeService { + selection = new BehaviorSubject(null); + systemTheme$: Observable; + theme$: Observable; + + constructor() { + const query = window.matchMedia('(prefers-color-scheme: dark)'); + this.systemTheme$ = fromEvent(query, 'change').pipe( + startWith(query), + map(list => list.matches ? Theme.DARK : Theme.LIGHT) + ); + this.theme$ = combineLatest([this.selection, this.systemTheme$]).pipe( + map(([selection, system]) => selection || system) + ); + } +} From ea8cf864137e48544ac3dc3acae254234e052ab0 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Thu, 21 Aug 2025 16:45:12 +0200 Subject: [PATCH 02/21] set theme --- frontend/src/app/services/theme.service.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/services/theme.service.ts b/frontend/src/app/services/theme.service.ts index 45bfa1b3d..1822a7ec9 100644 --- a/frontend/src/app/services/theme.service.ts +++ b/frontend/src/app/services/theme.service.ts @@ -14,7 +14,9 @@ export class ThemeService { systemTheme$: Observable; theme$: Observable; - constructor() { + constructor( + + ) { const query = window.matchMedia('(prefers-color-scheme: dark)'); this.systemTheme$ = fromEvent(query, 'change').pipe( startWith(query), @@ -23,5 +25,11 @@ export class ThemeService { this.theme$ = combineLatest([this.selection, this.systemTheme$]).pipe( map(([selection, system]) => selection || system) ); + this.theme$.subscribe((theme) => this.setTheme(theme)); + } + + setTheme(theme: Theme) { + const root = (document.getRootNode() as Document).documentElement; + root.setAttribute('data-theme', theme); } } From b3132b2e9a046df516416a9a882b72130c00f42c Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Thu, 21 Aug 2025 16:52:54 +0200 Subject: [PATCH 03/21] switch logo based on theme --- .../src/app/core/footer/footer.component.html | 7 ++++++- .../src/app/core/footer/footer.component.ts | 7 +++++-- .../UU-CDH_logo_EN_def_UU_CDH_logo_EN_white.png | Bin 0 -> 39789 bytes 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 frontend/src/assets/UU-CDH_logo_EN_def_UU_CDH_logo_EN_white.png diff --git a/frontend/src/app/core/footer/footer.component.html b/frontend/src/app/core/footer/footer.component.html index eda42927f..ad1c9af2c 100644 --- a/frontend/src/app/core/footer/footer.component.html +++ b/frontend/src/app/core/footer/footer.component.html @@ -6,7 +6,12 @@ Textcavator is developed by

- Centre for Digital Humanities, Utrecht University + Centre for Digital Humanities, Utrecht University
diff --git a/frontend/src/app/core/footer/footer.component.ts b/frontend/src/app/core/footer/footer.component.ts index 6ba02f98a..62a426967 100644 --- a/frontend/src/app/core/footer/footer.component.ts +++ b/frontend/src/app/core/footer/footer.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { environment } from '@environments/environment'; +import { ThemeService } from '@services/theme.service'; @Component({ selector: 'ia-footer', @@ -10,7 +11,9 @@ import { environment } from '@environments/environment'; }) export class FooterComponent { environment = environment as any; + theme$ = this.themeService.theme$; - constructor() { } - + constructor( + private themeService: ThemeService + ) { } } diff --git a/frontend/src/assets/UU-CDH_logo_EN_def_UU_CDH_logo_EN_white.png b/frontend/src/assets/UU-CDH_logo_EN_def_UU_CDH_logo_EN_white.png new file mode 100644 index 0000000000000000000000000000000000000000..a85b8a6717624dd4db44eb6eb63ea668224c8055 GIT binary patch literal 39789 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 literal 0 HcmV?d00001 From 8f83dcbe029f554f78fb3dc1f57f9c6c6cc1d081 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Thu, 21 Aug 2025 17:16:01 +0200 Subject: [PATCH 04/21] set barchart theme --- frontend/src/app/services/theme.service.ts | 2 +- .../src/app/visualization/barchart/barchart.directive.ts | 2 ++ frontend/src/app/visualization/chartjs-utils.ts | 9 +++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/visualization/chartjs-utils.ts diff --git a/frontend/src/app/services/theme.service.ts b/frontend/src/app/services/theme.service.ts index 1822a7ec9..9d5fbe802 100644 --- a/frontend/src/app/services/theme.service.ts +++ b/frontend/src/app/services/theme.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, combineLatest, fromEvent, map, Observable, startWith } from 'rxjs'; -enum Theme { +export enum Theme { DARK = 'dark', LIGHT = 'light', } diff --git a/frontend/src/app/visualization/barchart/barchart.directive.ts b/frontend/src/app/visualization/barchart/barchart.directive.ts index fe23a8ea0..9a0419d72 100644 --- a/frontend/src/app/visualization/barchart/barchart.directive.ts +++ b/frontend/src/app/visualization/barchart/barchart.directive.ts @@ -30,6 +30,7 @@ import { takeUntil } from 'rxjs/operators'; import { DateHistogramResult, TermsResult } from '@models/aggregation'; import { ComparedQueries } from '@models/compared-queries'; import { RouterStoreService } from '@app/store/router-store.service'; +import { setThemefaults } from '../chartjs-utils'; const hintSeenSessionStorageKey = 'hasSeenTimelineZoomingHint'; const hintHidingMinDelay = 500; // milliseconds @@ -170,6 +171,7 @@ export abstract class BarchartDirective< chartDefault.elements.bar.hoverBackgroundColor = selectColor(); chartDefault.plugins.tooltip.displayColors = false; chartDefault.plugins.tooltip.intersect = false; + setThemefaults(chartDefault); this.comparedQueries = new ComparedQueries(this.routerStoreService); this.comparedQueries.allQueries$.subscribe(this.updateQueries.bind(this)); } diff --git a/frontend/src/app/visualization/chartjs-utils.ts b/frontend/src/app/visualization/chartjs-utils.ts new file mode 100644 index 000000000..8f6dd2992 --- /dev/null +++ b/frontend/src/app/visualization/chartjs-utils.ts @@ -0,0 +1,9 @@ + +import { Defaults } from 'chart.js'; + +export const setThemefaults = (defaults: Defaults) => { + const style = window.getComputedStyle(document.body); + + defaults.color = style.getPropertyValue('--bulma-text-strong'); + defaults.borderColor = style.getPropertyValue('--bulma-border'); +} From d42985d958d9e58e36d2e34394cd780d1e93edca Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Wed, 27 Aug 2025 16:17:43 +0200 Subject: [PATCH 05/21] set defaults in ngram chart --- .../src/app/visualization/ngram/joyplot/joyplot.component.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/visualization/ngram/joyplot/joyplot.component.ts b/frontend/src/app/visualization/ngram/joyplot/joyplot.component.ts index 1e1dc7979..3bae60c23 100644 --- a/frontend/src/app/visualization/ngram/joyplot/joyplot.component.ts +++ b/frontend/src/app/visualization/ngram/joyplot/joyplot.component.ts @@ -3,6 +3,7 @@ import { Chart, ChartData, ChartOptions } from 'chart.js'; import * as _ from 'lodash'; import { NgramResults } from '@models'; import { selectColor } from '@utils/select-color'; +import { setThemefaults } from 'app/visualization/chartjs-utils'; @Component({ selector: 'ia-joyplot', @@ -28,7 +29,9 @@ export class JoyplotComponent implements OnChanges { chartOptions: ChartOptions; chart: Chart; - constructor() { } + constructor() { + setThemefaults(Chart.defaults); + } get multipleTimeIntervals(): boolean { return this.timeLabels && this.timeLabels.length > 1; From a34dc4c8e862401cf0731cfb3dda51438464d371 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Wed, 27 Aug 2025 16:27:51 +0200 Subject: [PATCH 06/21] refresh similarity chart on theme change --- frontend/src/app/services/theme.service.ts | 3 +++ .../similarity-chart.component.ts | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/services/theme.service.ts b/frontend/src/app/services/theme.service.ts index 9d5fbe802..637be1d68 100644 --- a/frontend/src/app/services/theme.service.ts +++ b/frontend/src/app/services/theme.service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, combineLatest, fromEvent, map, Observable, startWith } from 'rxjs'; +import { setThemefaults } from 'app/visualization/chartjs-utils'; +import { Chart } from 'chart.js'; export enum Theme { DARK = 'dark', @@ -31,5 +33,6 @@ export class ThemeService { setTheme(theme: Theme) { const root = (document.getRootNode() as Document).documentElement; root.setAttribute('data-theme', theme); + setThemefaults(Chart.defaults); } } diff --git a/frontend/src/app/word-models/similarity-chart/similarity-chart.component.ts b/frontend/src/app/word-models/similarity-chart/similarity-chart.component.ts index a3ea815ab..eb01edd11 100644 --- a/frontend/src/app/word-models/similarity-chart/similarity-chart.component.ts +++ b/frontend/src/app/word-models/similarity-chart/similarity-chart.component.ts @@ -2,9 +2,11 @@ import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@ import { Chart, ChartData, ChartOptions, ChartType, Filler, TooltipItem } from 'chart.js'; import Zoom from 'chartjs-plugin-zoom'; import * as _ from 'lodash'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, combineLatest, withLatestFrom } from 'rxjs'; import { selectColor } from '@utils/select-color'; import { FreqTableHeaders, WordSimilarity } from '@models'; +import { ThemeService } from '@services/theme.service'; +import { setThemefaults } from 'app/visualization/chartjs-utils'; /** * Child component of the related words and compare similarity graphs. @@ -39,10 +41,18 @@ export class SimilarityChartComponent implements OnInit, OnChanges, OnDestroy { currentTimeIndex = undefined; - constructor() {} + constructor( + private themeService: ThemeService, + ) {} ngOnInit(): void { this.graphStyle.subscribe(this.updateChart.bind(this)); + this.themeService.theme$.pipe( + withLatestFrom(this.graphStyle), + ).subscribe(([theme, style]) => { + setThemefaults(Chart.defaults); + this.updateChart(style); + }); } ngOnDestroy(): void { @@ -202,7 +212,7 @@ export class SimilarityChartComponent implements OnInit, OnChanges, OnDestroy { const options: ChartOptions = { elements: { line: { - tension: 0, // disables bezier curves + tension: 0, // dthis.chart.update();isables bezier curves }, point: { radius: 0, // hide points From 709d7ec40d319ddf9b40d2ff51e3390e7de5e081 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Wed, 27 Aug 2025 17:21:36 +0200 Subject: [PATCH 07/21] set all chart.js themes from theme service --- frontend/src/app/services/theme.service.ts | 16 ++++++++++++++-- .../visualization/barchart/barchart.directive.ts | 2 -- frontend/src/app/visualization/chartjs-utils.ts | 9 --------- .../ngram/joyplot/joyplot.component.ts | 5 +---- .../similarity-chart.component.ts | 14 ++------------ 5 files changed, 17 insertions(+), 29 deletions(-) delete mode 100644 frontend/src/app/visualization/chartjs-utils.ts diff --git a/frontend/src/app/services/theme.service.ts b/frontend/src/app/services/theme.service.ts index 637be1d68..ad5ff48a3 100644 --- a/frontend/src/app/services/theme.service.ts +++ b/frontend/src/app/services/theme.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, combineLatest, fromEvent, map, Observable, startWith } from 'rxjs'; -import { setThemefaults } from 'app/visualization/chartjs-utils'; import { Chart } from 'chart.js'; +import _ from 'lodash'; export enum Theme { DARK = 'dark', @@ -33,6 +33,18 @@ export class ThemeService { setTheme(theme: Theme) { const root = (document.getRootNode() as Document).documentElement; root.setAttribute('data-theme', theme); - setThemefaults(Chart.defaults); + this.setChartJSTheme(); + } + + setChartJSTheme() { + const style = window.getComputedStyle(document.body); + Chart.defaults.color = () => style.getPropertyValue('--bulma-text-strong'); + Chart.defaults.borderColor = () => style.getPropertyValue('--bulma-border'); + + const active = _.values(Chart.instances); + for (let chart of active) { + chart.update(); + } + } } diff --git a/frontend/src/app/visualization/barchart/barchart.directive.ts b/frontend/src/app/visualization/barchart/barchart.directive.ts index 9a0419d72..fe23a8ea0 100644 --- a/frontend/src/app/visualization/barchart/barchart.directive.ts +++ b/frontend/src/app/visualization/barchart/barchart.directive.ts @@ -30,7 +30,6 @@ import { takeUntil } from 'rxjs/operators'; import { DateHistogramResult, TermsResult } from '@models/aggregation'; import { ComparedQueries } from '@models/compared-queries'; import { RouterStoreService } from '@app/store/router-store.service'; -import { setThemefaults } from '../chartjs-utils'; const hintSeenSessionStorageKey = 'hasSeenTimelineZoomingHint'; const hintHidingMinDelay = 500; // milliseconds @@ -171,7 +170,6 @@ export abstract class BarchartDirective< chartDefault.elements.bar.hoverBackgroundColor = selectColor(); chartDefault.plugins.tooltip.displayColors = false; chartDefault.plugins.tooltip.intersect = false; - setThemefaults(chartDefault); this.comparedQueries = new ComparedQueries(this.routerStoreService); this.comparedQueries.allQueries$.subscribe(this.updateQueries.bind(this)); } diff --git a/frontend/src/app/visualization/chartjs-utils.ts b/frontend/src/app/visualization/chartjs-utils.ts deleted file mode 100644 index 8f6dd2992..000000000 --- a/frontend/src/app/visualization/chartjs-utils.ts +++ /dev/null @@ -1,9 +0,0 @@ - -import { Defaults } from 'chart.js'; - -export const setThemefaults = (defaults: Defaults) => { - const style = window.getComputedStyle(document.body); - - defaults.color = style.getPropertyValue('--bulma-text-strong'); - defaults.borderColor = style.getPropertyValue('--bulma-border'); -} diff --git a/frontend/src/app/visualization/ngram/joyplot/joyplot.component.ts b/frontend/src/app/visualization/ngram/joyplot/joyplot.component.ts index 3bae60c23..d9bbf419c 100644 --- a/frontend/src/app/visualization/ngram/joyplot/joyplot.component.ts +++ b/frontend/src/app/visualization/ngram/joyplot/joyplot.component.ts @@ -3,7 +3,6 @@ import { Chart, ChartData, ChartOptions } from 'chart.js'; import * as _ from 'lodash'; import { NgramResults } from '@models'; import { selectColor } from '@utils/select-color'; -import { setThemefaults } from 'app/visualization/chartjs-utils'; @Component({ selector: 'ia-joyplot', @@ -29,9 +28,7 @@ export class JoyplotComponent implements OnChanges { chartOptions: ChartOptions; chart: Chart; - constructor() { - setThemefaults(Chart.defaults); - } + constructor() {} get multipleTimeIntervals(): boolean { return this.timeLabels && this.timeLabels.length > 1; diff --git a/frontend/src/app/word-models/similarity-chart/similarity-chart.component.ts b/frontend/src/app/word-models/similarity-chart/similarity-chart.component.ts index eb01edd11..1514ae62b 100644 --- a/frontend/src/app/word-models/similarity-chart/similarity-chart.component.ts +++ b/frontend/src/app/word-models/similarity-chart/similarity-chart.component.ts @@ -2,11 +2,9 @@ import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@ import { Chart, ChartData, ChartOptions, ChartType, Filler, TooltipItem } from 'chart.js'; import Zoom from 'chartjs-plugin-zoom'; import * as _ from 'lodash'; -import { BehaviorSubject, combineLatest, withLatestFrom } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { selectColor } from '@utils/select-color'; import { FreqTableHeaders, WordSimilarity } from '@models'; -import { ThemeService } from '@services/theme.service'; -import { setThemefaults } from 'app/visualization/chartjs-utils'; /** * Child component of the related words and compare similarity graphs. @@ -41,18 +39,10 @@ export class SimilarityChartComponent implements OnInit, OnChanges, OnDestroy { currentTimeIndex = undefined; - constructor( - private themeService: ThemeService, - ) {} + constructor() {} ngOnInit(): void { this.graphStyle.subscribe(this.updateChart.bind(this)); - this.themeService.theme$.pipe( - withLatestFrom(this.graphStyle), - ).subscribe(([theme, style]) => { - setThemefaults(Chart.defaults); - this.updateChart(style); - }); } ngOnDestroy(): void { From 46f6dd835a33b6474e3536c5c4f14fbb21464e06 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Wed, 27 Aug 2025 18:16:50 +0200 Subject: [PATCH 08/21] adjust neighbor_network to theme --- backend/wordmodels/neighbor_network.py | 18 +++++++++------- .../neighbor-network.component.html | 2 ++ .../neighbor-network.component.ts | 21 ++++++++++++++++--- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/backend/wordmodels/neighbor_network.py b/backend/wordmodels/neighbor_network.py index 7ccbfae19..1f58d4cb3 100644 --- a/backend/wordmodels/neighbor_network.py +++ b/backend/wordmodels/neighbor_network.py @@ -95,7 +95,6 @@ def _graph_vega_doc(timeframes, nodes, links): "height": 500, "padding": 0, "autosize": "none", - "signals": [ { "name": "cx", "update": "width / 2" }, { "name": "cy", "update": "height / 2" }, @@ -154,7 +153,14 @@ def _graph_vega_doc(timeframes, nodes, links): "on": [ {"events": {"signal": "fix"}, "update": "fix && fix.length"} ] - } + }, + { + 'name': 'theme', + 'description': 'Current site theme (light/dark)', + 'bind': { + 'element': '#current-theme', + } + }, ], "data": [ @@ -181,13 +187,13 @@ def _graph_vega_doc(timeframes, nodes, links): ] } ], - "scales": [ { 'name': 'link-color', 'type': 'linear', 'domain': {"data": "link-data", "field": "value"}, 'range': {'scheme': 'greys'}, + 'reverse': { 'signal': 'theme === "dark"' }, }, { 'name': 'text-weight', @@ -196,13 +202,11 @@ def _graph_vega_doc(timeframes, nodes, links): 'range': ['bold', 'normal'], } ], - "marks": [ { "name": "nodes", "type": "text", "zindex": 1, - "from": {"data": "node-data"}, "on": [ { @@ -218,7 +222,6 @@ def _graph_vega_doc(timeframes, nodes, links): "encode": { "enter": { "fontSize": {"value": 15}, - "fill": {"value": "black"}, "text": {"field": "term"}, "baseline": {"value": "middle"}, "align": {"value": "center"}, @@ -227,7 +230,8 @@ def _graph_vega_doc(timeframes, nodes, links): }, }, "update": { - "cursor": {"value": "pointer"} + "cursor": {"value": "pointer"}, + "fill": {'signal': 'theme === "dark" ? "white" : "black"'}, }, }, "transform": [ diff --git a/frontend/src/app/word-models/neighbor-network/neighbor-network.component.html b/frontend/src/app/word-models/neighbor-network/neighbor-network.component.html index dd2e7e55e..751470c44 100644 --- a/frontend/src/app/word-models/neighbor-network/neighbor-network.component.html +++ b/frontend/src/app/word-models/neighbor-network/neighbor-network.component.html @@ -1,6 +1,8 @@
+ +
diff --git a/frontend/src/app/word-models/neighbor-network/neighbor-network.component.ts b/frontend/src/app/word-models/neighbor-network/neighbor-network.component.ts index 7aa5ae8c0..809e536d2 100644 --- a/frontend/src/app/word-models/neighbor-network/neighbor-network.component.ts +++ b/frontend/src/app/word-models/neighbor-network/neighbor-network.component.ts @@ -1,6 +1,7 @@ -import { Component, ElementRef, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; import { Corpus, FreqTableHeaders } from '@models'; import { WordmodelsService } from '@services'; +import { Theme, ThemeService } from '@services/theme.service'; import { BehaviorSubject } from 'rxjs'; import embed from 'vega-embed'; @@ -10,12 +11,13 @@ import embed from 'vega-embed'; styleUrl: './neighbor-network.component.scss', standalone: false, }) -export class NeighborNetworkComponent implements OnChanges { +export class NeighborNetworkComponent implements OnChanges, AfterViewInit { @Input({required: true}) corpus!: Corpus; @Input({required: true}) queryText!: string; @Input() asTable: boolean; @ViewChild('chart') chart!: ElementRef; + @ViewChild('theme') themeInput!: ElementRef; data: any; @@ -36,12 +38,19 @@ export class NeighborNetworkComponent implements OnChanges { key: 'similarity', label: 'Similarity' } - ] + ]; constructor( private wordModelsService: WordmodelsService, + private themeService: ThemeService, ) { } + ngAfterViewInit() { + this.themeService.theme$.subscribe( + theme => this.updateTheme(theme) + ); + } + ngOnChanges(changes: SimpleChanges): void { this.getData() } @@ -73,4 +82,10 @@ export class NeighborNetworkComponent implements OnChanges { console.error(error); }); } + + updateTheme(theme: Theme) { + const el = this.themeInput.nativeElement as HTMLInputElement; + el.value = theme; + el.dispatchEvent(new Event('input')); + } } From db94b14cabcbc7ebfc60013cd346c30f276d4a34 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Thu, 28 Aug 2025 11:20:53 +0200 Subject: [PATCH 09/21] outfactor theme indicator element to directive --- .../theme-indicator.directive.ts | 37 +++++++++++++++++++ .../app/visualization/visualization.module.ts | 3 ++ .../neighbor-network.component.html | 2 +- .../neighbor-network.component.ts | 16 +------- 4 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 frontend/src/app/visualization/theme-indicator.directive.ts diff --git a/frontend/src/app/visualization/theme-indicator.directive.ts b/frontend/src/app/visualization/theme-indicator.directive.ts new file mode 100644 index 000000000..45342a26f --- /dev/null +++ b/frontend/src/app/visualization/theme-indicator.directive.ts @@ -0,0 +1,37 @@ +import { DestroyRef, Directive, ElementRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { Theme, ThemeService } from '@services/theme.service'; + + +/** + * Lets the host element function as an indicator of the site theme (light/dark). The + * element will set the theme as its value and fire an input event when the theme + * changes. + * + * Can be used to let Vega visualisations observe the site theme. The element will be + * hidden from the user. + */ +@Directive({ + selector: 'input[iaThemeIndicator]', + standalone: false, + host: { id: 'current-theme', hidden: '' } +}) +export class ThemeIndicatorDirective implements OnInit { + constructor( + private themeService: ThemeService, + private el: ElementRef, + private destroyRef: DestroyRef, + ) {} + + ngOnInit() { + this.themeService.theme$.pipe( + takeUntilDestroyed(this.destroyRef), + ).subscribe(theme => this.setTheme(theme)); + } + + setTheme(theme: Theme) { + const el = this.el.nativeElement as HTMLInputElement; + el.value = theme; + el.dispatchEvent(new Event('input')); + } +} diff --git a/frontend/src/app/visualization/visualization.module.ts b/frontend/src/app/visualization/visualization.module.ts index 1c57a0697..79bc3b121 100644 --- a/frontend/src/app/visualization/visualization.module.ts +++ b/frontend/src/app/visualization/visualization.module.ts @@ -23,6 +23,7 @@ import { VisualizationFooterComponent } from './visualization-footer/visualizati import { VisualizationComponent } from './visualization.component'; import { WordcloudComponent } from './wordcloud/wordcloud.component'; import { MapComponent } from './map/map.component'; +import { ThemeIndicatorDirective } from './theme-indicator.directive'; @NgModule({ declarations: [ @@ -39,12 +40,14 @@ import { MapComponent } from './map/map.component'; VisualizationComponent, PaletteSelectComponent, MapComponent, + ThemeIndicatorDirective, ], exports: [ TermComparisonEditorComponent, VisualizationFooterComponent, FreqtableComponent, VisualizationComponent, + ThemeIndicatorDirective ], imports: [ AutoCompleteModule, ChartModule, diff --git a/frontend/src/app/word-models/neighbor-network/neighbor-network.component.html b/frontend/src/app/word-models/neighbor-network/neighbor-network.component.html index 751470c44..976591c0c 100644 --- a/frontend/src/app/word-models/neighbor-network/neighbor-network.component.html +++ b/frontend/src/app/word-models/neighbor-network/neighbor-network.component.html @@ -1,7 +1,7 @@
- + diff --git a/frontend/src/app/word-models/neighbor-network/neighbor-network.component.ts b/frontend/src/app/word-models/neighbor-network/neighbor-network.component.ts index 809e536d2..4636214b4 100644 --- a/frontend/src/app/word-models/neighbor-network/neighbor-network.component.ts +++ b/frontend/src/app/word-models/neighbor-network/neighbor-network.component.ts @@ -11,13 +11,12 @@ import embed from 'vega-embed'; styleUrl: './neighbor-network.component.scss', standalone: false, }) -export class NeighborNetworkComponent implements OnChanges, AfterViewInit { +export class NeighborNetworkComponent implements OnChanges { @Input({required: true}) corpus!: Corpus; @Input({required: true}) queryText!: string; @Input() asTable: boolean; @ViewChild('chart') chart!: ElementRef; - @ViewChild('theme') themeInput!: ElementRef; data: any; @@ -42,15 +41,8 @@ export class NeighborNetworkComponent implements OnChanges, AfterViewInit { constructor( private wordModelsService: WordmodelsService, - private themeService: ThemeService, ) { } - ngAfterViewInit() { - this.themeService.theme$.subscribe( - theme => this.updateTheme(theme) - ); - } - ngOnChanges(changes: SimpleChanges): void { this.getData() } @@ -82,10 +74,4 @@ export class NeighborNetworkComponent implements OnChanges, AfterViewInit { console.error(error); }); } - - updateTheme(theme: Theme) { - const el = this.themeInput.nativeElement as HTMLInputElement; - el.value = theme; - el.dispatchEvent(new Event('input')); - } } From 873c57724de2889ba05f087496c0a45e3871dc97 Mon Sep 17 00:00:00 2001 From: Luka van der Plas Date: Thu, 28 Aug 2025 11:52:14 +0200 Subject: [PATCH 10/21] add theme toggle button to menu --- .../src/app/core/menu/menu.component.html | 6 ++++ frontend/src/app/core/menu/menu.component.ts | 30 +++++++++++++++++-- frontend/src/app/shared/icons.ts | 9 ++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/core/menu/menu.component.html b/frontend/src/app/core/menu/menu.component.html index 6946e2b89..72091d858 100644 --- a/frontend/src/app/core/menu/menu.component.html +++ b/frontend/src/app/core/menu/menu.component.html @@ -58,6 +58,12 @@