add user preference service

This commit is contained in:
Dan Percic 2022-07-25 17:20:05 +03:00
parent 2bc66542a6
commit c651a6d863
8 changed files with 146 additions and 2 deletions

View File

@ -58,6 +58,7 @@
"no-param-reassign": "error",
"no-dupe-class-members": "off",
"no-redeclare": "off",
"no-unused-vars": "off",
"consistent-return": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/lines-between-class-members": "off"

View File

@ -1,4 +1,4 @@
import { NgModule } from '@angular/core';
import { ModuleWithProviders, NgModule, Provider } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core';
@ -36,6 +36,9 @@ import { DragDropFileUploadDirective } from './upload-file/drag-drop-file-upload
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { ConfirmationDialogComponent } from './dialog';
import { MatTooltipModule } from '@angular/material/tooltip';
import { DefaultUserPreferenceService } from './services/default-user-preference.service';
import { CommonUiOptions } from './utils/types/common-ui-options';
import { BaseUserPreferenceService } from './services';
const matModules = [MatIconModule, MatProgressSpinnerModule, MatButtonModule, MatDialogModule, MatCheckboxModule, MatTooltipModule];
const modules = [
@ -73,4 +76,23 @@ const pipes = [SortByPipe, HumanizePipe, CapitalizePipe, LogPipe];
imports: [CommonModule, ...matModules, ...modules, FormsModule, ReactiveFormsModule, KeycloakAngularModule, MatProgressBarModule],
exports: [...components, ...pipes, ...modules, LogPipe],
})
export class CommonUiModule {}
export class CommonUiModule {
static forRoot<T extends BaseUserPreferenceService>(options?: CommonUiOptions<T>): ModuleWithProviders<CommonUiModule> {
const userPreferenceService = this._getUserPreferenceService(options);
return {
ngModule: CommonUiModule,
providers: [userPreferenceService],
};
}
private static _getUserPreferenceService<T extends BaseUserPreferenceService>(options?: CommonUiOptions<T>): Provider {
if (options?.existingUserPreferenceService) {
return {
provide: BaseUserPreferenceService,
useExisting: options.existingUserPreferenceService,
};
}
return { provide: BaseUserPreferenceService, useClass: DefaultUserPreferenceService };
}
}

View File

@ -0,0 +1,70 @@
import { Injectable } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { List, RequiredParam, Validate } from '../utils';
import { GenericService } from './generic.service';
export type UserAttributes = Record<string, List>;
const KEYS = {
language: 'Language',
theme: 'Theme',
} as const;
@Injectable()
export abstract class BaseUserPreferenceService extends GenericService<UserAttributes> {
protected abstract readonly _devFeaturesEnabledKey: string;
#userAttributes: UserAttributes = {};
get userAttributes(): UserAttributes {
return this.#userAttributes;
}
get areDevFeaturesEnabled(): boolean {
const value = sessionStorage.getItem(this._devFeaturesEnabledKey);
return value === 'true' ?? false;
}
getTheme(): string {
return this._getAttribute(KEYS.theme, 'light');
}
async saveTheme(theme: 'light' | 'dark'): Promise<void> {
await this._save(KEYS.theme, theme);
window.location.reload();
}
getLanguage(): string {
return this._getAttribute(KEYS.language);
}
async saveLanguage(language: string): Promise<void> {
await this._save(KEYS.language, language);
}
toggleDevFeatures(): void {
sessionStorage.setItem(this._devFeaturesEnabledKey, String(!this.areDevFeaturesEnabled));
window.location.reload();
}
async reload(): Promise<void> {
const attributes = await firstValueFrom(this.getAll<UserAttributes>());
this.#userAttributes = attributes ?? {};
}
@Validate()
savePreferences(@RequiredParam() body: List, @RequiredParam() key: string) {
return this._put(body, `${this._defaultModelPath}/${key}`);
}
private async _save(key: string, value: string): Promise<void> {
this.userAttributes[key] = [value];
await firstValueFrom(this.savePreferences([value], key));
}
private _getAttribute(key: string, defaultValue = ''): string {
if (this.userAttributes[key]?.length > 0) {
return this.userAttributes[key][0];
}
return defaultValue;
}
}

View File

@ -0,0 +1,9 @@
import { inject, Injectable } from '@angular/core';
import { BASE_HREF } from '../utils';
import { BaseUserPreferenceService } from './base-user-preference.service';
@Injectable()
export class DefaultUserPreferenceService extends BaseUserPreferenceService {
protected readonly _defaultModelPath = 'attributes';
protected readonly _devFeaturesEnabledKey = inject(BASE_HREF) + '.enable-dev-features';
}

View File

@ -5,3 +5,4 @@ export * from './generic.service';
export * from './composite-route.guard';
export * from './stats.service';
export * from './entities-map.service';
export * from './base-user-preference.service';

View File

@ -16,3 +16,4 @@ export * from './pruning-translation-loader';
export * from './custom-route-reuse.strategy';
export * from './headers-configuration';
export * from './context.component';
export * from './tokens';

34
src/lib/utils/tokens.ts Normal file
View File

@ -0,0 +1,34 @@
import { inject, InjectionToken } from '@angular/core';
import { PlatformLocation } from '@angular/common';
export const BASE_HREF = new InjectionToken<string>('BASE_HREF', {
factory: () => {
const baseUrl = inject(PlatformLocation).getBaseHrefFromDOM();
if (!baseUrl) {
return '';
}
if (baseUrl[baseUrl.length - 1] === '/') {
return baseUrl.substring(0, baseUrl.length - 1);
}
return baseUrl;
},
});
export type BaseHrefFn = (path: string) => string;
export const BASE_HREF_FN = new InjectionToken<BaseHrefFn>('Convert path function', {
factory: () => (path: string) => {
const baseUrl = inject(BASE_HREF);
if (!baseUrl) {
throw new Error('BASE_HREF is not defined');
}
if (path[0] === '/') {
return baseUrl + path;
}
return baseUrl + '/' + path;
},
});

View File

@ -0,0 +1,6 @@
import { Type } from '@angular/core';
import { BaseUserPreferenceService } from '../../services';
export interface CommonUiOptions<T extends BaseUserPreferenceService> {
existingUserPreferenceService: Type<T>;
}