Skip to content

Commit 8025d93

Browse files
feat(key-storage): adds key-storage feature
1 parent d5ccb64 commit 8025d93

16 files changed

+217
-123
lines changed

Diff for: apps/www/src/app/app.module.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { NgModule } from '@angular/core';
2-
import { BrowserModule } from '@angular/platform-browser';
3-
import { RouterModule } from '@angular/router';
1+
import {NgModule} from '@angular/core';
2+
import {BrowserModule} from '@angular/platform-browser';
3+
import {RouterModule} from '@angular/router';
44

5-
import { AppComponent } from './app.component';
6-
import { appRoutes } from './app.routes';
5+
import {AppComponent} from './app.component';
6+
import {appRoutes} from './app.routes';
77
import {MastheadComponent} from "./components/masthead";
88

99
@NgModule({
@@ -16,4 +16,5 @@ import {MastheadComponent} from "./components/masthead";
1616
providers: [],
1717
bootstrap: [AppComponent],
1818
})
19-
export class AppModule {}
19+
export class AppModule {
20+
}

Diff for: apps/www/src/app/components/masthead/masthead.component.ts

+22-18
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import {Component, HostBinding, OnDestroy} from "@angular/core";
1+
import {Component, HostBinding, OnDestroy, OnInit} from "@angular/core";
22
import {FormControl, ReactiveFormsModule} from "@angular/forms";
3+
import {Subscription} from "rxjs";
34
import {SegmentedControlButtonComponent, SegmentedControlComponent} from "../segmented-control";
45
import {RadioValueAccessorDirective} from "../../library-components/directives";
56
import {ColorModeService, ColourMode} from "../../library-components/services";
67
import {ContainerComponent} from "../container";
78
import {IconComponent} from "../icon";
8-
import {distinctUntilChanged} from "rxjs";
9-
109

1110
@Component({
1211
// eslint-disable-next-line @angular-eslint/component-selector
@@ -23,24 +22,15 @@ import {distinctUntilChanged} from "rxjs";
2322
],
2423
standalone: true
2524
})
26-
export class MastheadComponent implements OnDestroy {
25+
export class MastheadComponent implements OnInit, OnDestroy {
2726
protected ColorMode = ColourMode;
27+
protected mode = new FormControl(ColourMode.AUTO);
2828
protected expanded = false;
29-
protected mode = new FormControl<ColourMode>(ColourMode.AUTO);
30-
31-
protected mode$ = this.mode
32-
.valueChanges
33-
.pipe(distinctUntilChanged())
34-
.subscribe((value) => value
35-
? this.colorModeService.set(value)
36-
: this.colorModeService.set(ColourMode.AUTO));
3729

38-
protected globalMode$ = this.colorModeService
39-
.asObservable
40-
.subscribe((value) => this.mode.setValue(value))
30+
private mode$!: Subscription;
31+
private storageMode$!: Subscription;
4132

42-
@HostBinding('class.masthead')
43-
readonly hbClassMasthead = true;
33+
@HostBinding('class.masthead') readonly hbClassMasthead = true;
4434

4535
@HostBinding('class.masthead--expanded')
4636
get hbClassMastheadExpanded() {
@@ -50,11 +40,25 @@ export class MastheadComponent implements OnDestroy {
5040
constructor(protected colorModeService: ColorModeService) {
5141
}
5242

43+
ngOnInit() {
44+
this.mode$ = this.mode.valueChanges.subscribe(this.onValueChange);
45+
this.storageMode$ = this.colorModeService.asObservable.subscribe(this.onStorageChange);
46+
}
47+
5348
ngOnDestroy() {
5449
this.mode$.unsubscribe();
55-
this.globalMode$.unsubscribe();
50+
this.storageMode$.unsubscribe();
5651
}
5752

53+
onStorageChange = (value: ColourMode) => {
54+
this.mode.setValue(value);
55+
}
56+
57+
onValueChange = (value: ColourMode | null) =>
58+
value
59+
? this.colorModeService.set(value)
60+
: this.colorModeService.set(ColourMode.AUTO)
61+
5862
toggleExpanded() {
5963
this.expanded = !this.expanded;
6064
}

Diff for: apps/www/src/app/library-components/services/color-mode.service.ts

-93
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {Inject, Injectable} from "@angular/core";
2+
import {DOCUMENT} from "@angular/common";
3+
import {BehaviorSubject, distinctUntilChanged} from "rxjs";
4+
5+
import {ColorModeStore} from "./color-mode.storage";
6+
import {ColourMode} from "./storage-mode.interface";
7+
8+
import type {KeyStorage} from "../../utilities/key-storage";
9+
10+
@Injectable({
11+
providedIn: 'root'
12+
})
13+
export class ColorModeService {
14+
private _mode = new BehaviorSubject<ColourMode>(this.storage.getItem());
15+
public asObservable = this._mode.pipe(distinctUntilChanged());
16+
public value = this._mode.value;
17+
18+
constructor(
19+
@Inject(DOCUMENT) private document: Document,
20+
@Inject(ColorModeStore) private storage: KeyStorage<ColourMode>,
21+
) {
22+
}
23+
24+
public set(mode: ColourMode) {
25+
if (mode === ColourMode.LIGHT) {
26+
return this.setLight();
27+
}
28+
29+
if (mode === ColourMode.DARK) {
30+
return this.setDark();
31+
}
32+
33+
return this.setAuto();
34+
}
35+
36+
private setAuto() {
37+
this._mode.next(ColourMode.AUTO);
38+
this.storage.clear();
39+
this.document.body.classList?.remove(ColourMode.LIGHT);
40+
this.document.body.classList?.remove(ColourMode.DARK);
41+
}
42+
43+
private setDark() {
44+
this._mode.next(ColourMode.DARK);
45+
this.storage.setItem(ColourMode.DARK);
46+
this.document.body.classList?.remove(ColourMode.LIGHT);
47+
this.document.body.classList?.add(ColourMode.DARK);
48+
}
49+
50+
private setLight() {
51+
this._mode.next(ColourMode.LIGHT);
52+
this.storage.setItem(ColourMode.LIGHT);
53+
this.document.body.classList?.remove(ColourMode.DARK);
54+
this.document.body.classList?.add(ColourMode.LIGHT);
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import {createKeyStorage} from "../../utilities/key-storage";
2+
import {ColourMode} from "./storage-mode.interface";
3+
4+
export const ColorModeStore = createKeyStorage<ColourMode>('color-mode', ColourMode.AUTO);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './color-mode.service';
2+
export * from './color-mode.storage';
3+
export * from './storage-mode.interface';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export enum ColourMode {
2+
LIGHT = 'light',
3+
DARK = 'dark',
4+
AUTO = 'auto'
5+
}
6+
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export * from './color-mode.service';
1+
export * from './color-mode';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {InjectionToken} from "@angular/core";
2+
import {KeyStorage} from "./key-storage";
3+
import {localStorageStore} from "./stores";
4+
5+
export const createKeyStorage = <T = unknown>(
6+
key: string,
7+
defaultValue: T,
8+
store: Storage = localStorageStore,
9+
) =>
10+
new InjectionToken(
11+
`storage-key-${key}`, {
12+
providedIn: 'root',
13+
factory: () => new KeyStorage<T>(key, defaultValue, store)
14+
});
15+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './stores';
2+
export * from './key-storage';
3+
export * from './create-key-storage';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export class KeyStorage<T = unknown> {
2+
3+
constructor(
4+
private _key: string,
5+
private _defaultValue: T,
6+
private _store: Storage,
7+
) {
8+
}
9+
10+
getItem(): T {
11+
const data = this._store.getItem(this._key);
12+
return data
13+
? JSON.parse(data)
14+
: this._defaultValue;
15+
}
16+
17+
setItem(value: T): void {
18+
const data = JSON.stringify(value);
19+
this._store.setItem(this._key, data);
20+
}
21+
22+
clear(): void {
23+
this._store.removeItem(this._key);
24+
}
25+
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export class InMemoryStore implements Storage {
2+
private store = new Map<string, string>()
3+
4+
get length(): number {
5+
return this.store.size;
6+
}
7+
8+
key(index: number): string | null {
9+
const keys = Object.keys(Object.fromEntries(this.store));
10+
return keys[index] || null;
11+
}
12+
13+
getItem(key: string): string | null {
14+
return this.store.get(key) || null;
15+
}
16+
17+
setItem(key: string, value: string): void {
18+
this.store.set(key, value);
19+
}
20+
21+
removeItem(key: string): void {
22+
this.store.delete(key);
23+
}
24+
25+
clear(): void {
26+
this.store.clear();
27+
}
28+
}
29+
30+
export const inMemoryStore = new InMemoryStore();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './in-memory.store';
2+
export * from './local-storage.store';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {inMemoryStore} from "./in-memory.store";
2+
3+
/**
4+
* LocalStorageStore
5+
* Falls back to in-memory storage if local storage isn't available
6+
*/
7+
export class LocalStorageStore implements Storage {
8+
9+
private _store = globalThis.localStorage || inMemoryStore;
10+
11+
get length(): number {
12+
return this._store.length
13+
}
14+
15+
key(index: number): string | null {
16+
return this._store.key(index);
17+
}
18+
19+
getItem(key: string): string | null {
20+
return this._store.getItem(key);
21+
}
22+
23+
setItem(key: string, value: string): void {
24+
return this._store.setItem(key, value);
25+
}
26+
27+
removeItem(key: string): void {
28+
return this._store.removeItem(key);
29+
}
30+
31+
clear(): void {
32+
return this._store.clear();
33+
}
34+
}
35+
36+
export const localStorageStore = new LocalStorageStore();

0 commit comments

Comments
 (0)