Skip to content

Commit

Permalink
Add theme settings
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilipAB committed Oct 25, 2024
1 parent 0e36956 commit 9526918
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 26 deletions.
29 changes: 18 additions & 11 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import { Component } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import '@cds/core/icon/register.js';
import { ClarityIcons } from '@cds/core/icon';
import { Router } from '@angular/router';
import { AppConfigService } from './app-config.service';
import { Subscription } from 'rxjs';
import { ThemeService } from './data/theme.service';

@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>',
})
export class AppComponent {

export class AppComponent implements OnInit, OnDestroy {
private config = this.configService.getConfig();
public logo = this.config.logo || '/assets/default/logo.svg';
private logo = this.config.logo || '/assets/default/logo.svg';
private themeSubscription: Subscription;

constructor(
public router: Router,
public configService: AppConfigService
) {
this.configService.getLogo(this.logo)
.then((obj: string) => {
ClarityIcons.addIcons(['logo', obj]);
})
private router: Router,
private configService: AppConfigService,
private themeService: ThemeService
) {}
ngOnInit(): void {
this.configService.getLogo(this.logo).then((obj: string) => {
ClarityIcons.addIcons(['logo', obj]);
});
this.themeSubscription = this.themeService.listenToThemeChanges();
}

ngOnDestroy(): void {
this.themeSubscription.unsubscribe();
}
}
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ import { HiddenMdComponent } from './step/hidden-md-component/hidden-md.componen
import { GlossaryMdComponent } from './step/glossary-md-component/glossary-md.component';
import { MermaidMdComponent } from './step/mermaid-md-component/mermaid-md.component';
import { NoteMdComponent } from './step/note-md-component/note-md.component';
import { ThemeService } from './data/theme.service';

ClarityIcons.addIcons(
plusIcon,
Expand Down Expand Up @@ -348,6 +349,7 @@ export function jwtOptionsFactory(): JwtConfig {
AppConfigService,
ProgressService,
PredefinedServiceService,
ThemeService,
TypedSettingsService,
{
provide: APP_INITIALIZER,
Expand Down
22 changes: 18 additions & 4 deletions src/app/data/forms.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { FormArray, FormControl, FormGroup } from '@angular/forms';

export type GenericKeyValueGroup<T extends string | number | boolean> = FormGroup<{
key: FormControl<string>;
value: FormControl<T>;
}>;
export type GenericKeyValueGroup<T extends string | number | boolean> =
FormGroup<{
key: FormControl<string>;
value: FormControl<T>;
}>;

export type CourseDetailFormGroup = FormGroup<{
course_name: FormControl<string | null>;
Expand Down Expand Up @@ -46,3 +47,16 @@ export type GenericKeyValueMapArray =
| FormArray<GenericKeyValueGroup<string>>
| FormArray<GenericKeyValueGroup<number>>
| FormArray<GenericKeyValueGroup<boolean>>;

export type SettingFormGroup = FormGroup<{
terminal_theme: FormControl<
| 'default'
| 'Solarized_Light'
| 'Solarized_Dark'
| 'Solarized_Dark_Higher_Contrast'
| 'GitHub'
| 'Dichromatic'
>;
hide_usernames_status: FormControl<boolean>;
theme: FormControl<'dark' | 'light' | 'system'>;
}>;
25 changes: 17 additions & 8 deletions src/app/data/settings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import {
GargantuaClientFactory,
} from '../data/gargantua.service';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { SettingFormGroup } from './forms';

export interface Settings {
terminal_theme: (typeof themes)[number]['id'];
hide_usernames_status: boolean;
theme: 'dark' | 'light' | 'system';
}

/**
Expand All @@ -33,10 +35,14 @@ export class SettingsService {
private subject = new Subject<Readonly<Settings>>();
readonly settings$ = concat(this.fetch(), this.subject).pipe(shareReplay(1));

public settingsForm: FormGroup = new FormGroup({
terminal_theme: new FormControl<typeof themes[number]['id'] | null> (null, [Validators.required,]),
public settingsForm: SettingFormGroup = new FormGroup({
terminal_theme: new FormControl<(typeof themes)[number]['id'] | null>(
null,
[Validators.required]
),
hide_usernames_status: new FormControl<boolean>(false),
})
theme: new FormControl<'dark' | 'light' | 'system'>('system'),
});

fetch() {
return this.garg.get('/settings').pipe(
Expand All @@ -46,11 +52,14 @@ export class SettingsService {
? s
: ({
terminal_theme: themes[0].id,
hide_usernames_status: false
} as Settings),
hide_usernames_status: false,
theme: 'system',
} as Settings)
),
tap((s: Settings) => {
s.hide_usernames_status = JSON.parse(String(s.hide_usernames_status ?? false));
s.hide_usernames_status = JSON.parse(
String(s.hide_usernames_status ?? false)
);
this.settingsForm.patchValue(s);
this.subject.next(s);
}),
Expand All @@ -68,7 +77,7 @@ export class SettingsService {
return throwError(() => e.error);
}),
tap(() => {
this.settingsForm.patchValue(newSettings);
this.settingsForm.patchValue(newSettings);
this.subject.next(newSettings);
})
);
Expand All @@ -83,7 +92,7 @@ export class SettingsService {
);
}

getForm() {
getForm(): SettingFormGroup {
return this.settingsForm;
}
}
55 changes: 55 additions & 0 deletions src/app/data/theme.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Injectable, Renderer2, RendererFactory2 } from '@angular/core';
import { SettingsService, Settings } from './settings.service';
import { fromEventPattern, Subscription } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class ThemeService {
private renderer: Renderer2;

constructor(
private rendererFactory: RendererFactory2,
private settingsService: SettingsService
) {
this.renderer = this.rendererFactory.createRenderer(null, null);
}

listenToThemeChanges() {
let mediaQuerySubscription: Subscription | null = null;

return this.settingsService.settings$.subscribe((settings: Settings) => {
if (settings.theme === 'system') {
const darkModeMediaQuery = window.matchMedia(
'(prefers-color-scheme: dark)'
);
// Applying the current system theme
this.setTheme(darkModeMediaQuery.matches ? 'dark' : 'light');

// If system theme listener is not set up already, do it here
if (!mediaQuerySubscription) {
mediaQuerySubscription = fromEventPattern<MediaQueryListEvent>(
(handler) => darkModeMediaQuery.addEventListener('change', handler),
(handler) =>
darkModeMediaQuery.removeEventListener('change', handler)
).subscribe((event) => {
this.setTheme(event.matches ? 'dark' : 'light');
});
}
} else {
// Applying the user-defined theme directly
this.setTheme(settings.theme);

// Unsubscribing from media query listener if it exists
if (mediaQuerySubscription) {
mediaQuerySubscription.unsubscribe();
mediaQuerySubscription = null;
}
}
});
}

private setTheme(theme: 'light' | 'dark') {
this.renderer.setAttribute(document.body, 'cds-theme', theme);
}
}
21 changes: 21 additions & 0 deletions src/app/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,27 @@ <h3 class="modal-title">Settings</h3>
</clr-tab-content>
<clr-tab-content> </clr-tab-content>
</clr-tab>
<clr-tab>
<button clrTabLink>Theme</button>
<clr-tab-content>
<clr-combobox-container>
<label>Theme</label>
<clr-combobox
name="Theme"
required
placeholder="system"
formControlName="theme"
>
<clr-options>
<clr-option clrValue="light">light</clr-option>
<clr-option clrValue="dark">dark</clr-option>
<clr-option clrValue="system"> system</clr-option>
</clr-options>
</clr-combobox>
</clr-combobox-container>
</clr-tab-content>
<clr-tab-content> </clr-tab-content>
</clr-tab>
</clr-tabs>
</form>
</ng-container>
Expand Down
8 changes: 5 additions & 3 deletions src/app/header/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { JwtHelperService } from '@auth0/angular-jwt';
import { AppConfigService } from '../app-config.service';
import { RbacService } from '../data/rbac.service';
import { Title } from '@angular/platform-browser';
import { FormGroup } from '@angular/forms';
import { themes } from '../step/terminal-themes/themes';
import { first } from 'rxjs/operators';
import { SettingsService } from '../data/settings.service';
import { SettingFormGroup } from '../data/forms';

@Component({
selector: '[app-header]',
Expand All @@ -26,7 +26,7 @@ export class HeaderComponent implements OnInit {
public configurationRbac: boolean = false;

public fetchingSettings = false;
public settingsForm: FormGroup;
public settingsForm: SettingFormGroup;
public hide_usernames_status: boolean;
public isButtonDisabled: boolean = false;

Expand Down Expand Up @@ -109,10 +109,12 @@ export class HeaderComponent implements OnInit {
({
terminal_theme = 'default',
hide_usernames_status = false,
theme = 'system',
}) => {
this.settingsForm.setValue({
terminal_theme,
hide_usernames_status
hide_usernames_status,
theme
});

this.fetchingSettings = false;
Expand Down

0 comments on commit 9526918

Please sign in to comment.