Merge branch 'RED-10183' into 'master'

RED-10183: use the user preferred language + component refactoring.

See merge request redactmanager/red-ui!610
This commit is contained in:
Dan Percic 2024-10-14 13:14:42 +02:00
commit 9836c2aab5
5 changed files with 64 additions and 64 deletions

View File

@ -20,6 +20,7 @@
<label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label> <label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label>
<mat-form-field> <mat-form-field>
<mat-select formControlName="language"> <mat-select formControlName="language">
<mat-select-trigger>{{ languageSelectLabel() | translate }}</mat-select-trigger>
<mat-option *ngFor="let language of languages" [value]="language"> <mat-option *ngFor="let language of languages" [value]="language">
{{ translations[language] | translate }} {{ translations[language] | translate }}
</mat-option> </mat-option>
@ -41,7 +42,7 @@
<div class="dialog-actions"> <div class="dialog-actions">
<iqser-icon-button <iqser-icon-button
[disabled]="form.invalid || !(profileChanged || languageChanged || themeChanged)" [disabled]="disabled"
[label]="'user-profile-screen.actions.save' | translate" [label]="'user-profile-screen.actions.save' | translate"
[submit]="true" [submit]="true"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed } from '@angular/core';
import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { FormGroup, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { import {
BaseFormComponent, BaseFormComponent,
@ -19,22 +19,49 @@ import { firstValueFrom } from 'rxjs';
import { UserProfileDialogService } from '../services/user-profile-dialog.service'; import { UserProfileDialogService } from '../services/user-profile-dialog.service';
import { NgForOf, NgIf } from '@angular/common'; import { NgForOf, NgIf } from '@angular/common';
import { MatFormField } from '@angular/material/form-field'; import { MatFormField } from '@angular/material/form-field';
import { MatOption, MatSelect } from '@angular/material/select'; import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
import { MatSlideToggle } from '@angular/material/slide-toggle'; import { MatSlideToggle } from '@angular/material/slide-toggle';
import { PdfViewer } from '../../../../pdf-viewer/services/pdf-viewer.service'; import { PdfViewer } from '../../../../pdf-viewer/services/pdf-viewer.service';
import { formControlToSignal } from '@utils/functions';
import { AsControl } from '@common-ui/utils';
interface UserProfileForm {
email: string;
firstName: string;
lastName: string;
language: string;
darkTheme: boolean;
}
@Component({ @Component({
templateUrl: './user-profile-screen.component.html', templateUrl: './user-profile-screen.component.html',
styleUrls: ['./user-profile-screen.component.scss'], styleUrls: ['./user-profile-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [ReactiveFormsModule, NgIf, MatFormField, MatSelect, MatOption, NgForOf, TranslateModule, MatSlideToggle, IconButtonComponent], imports: [
ReactiveFormsModule,
NgIf,
MatFormField,
MatSelect,
MatOption,
NgForOf,
TranslateModule,
MatSlideToggle,
IconButtonComponent,
MatSelectTrigger,
],
}) })
export class UserProfileScreenComponent extends BaseFormComponent implements OnInit { export class UserProfileScreenComponent extends BaseFormComponent {
#profileModel: IProfile; readonly form: FormGroup<AsControl<UserProfileForm>> = this.#getForm();
initialFormValue = this.form.getRawValue();
readonly translations = languagesTranslations; readonly translations = languagesTranslations;
readonly devMode = this._userPreferenceService.isIqserDevMode; readonly devMode = this._userPreferenceService.isIqserDevMode;
readonly profileKeys = ['email', 'firstName', 'lastName'];
readonly languages = this._translateService.langs;
readonly language = formControlToSignal<UserProfileForm['language']>(this.form.controls.language);
readonly languageSelectLabel = computed(() => this.translations[this.language()]);
constructor( constructor(
private readonly _userService: UserService, private readonly _userService: UserService,
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
@ -49,41 +76,26 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
private readonly _pdfViewer: PdfViewer, private readonly _pdfViewer: PdfViewer,
) { ) {
super(); super();
this._loadingService.start(); if (!this._permissionsService.has(Roles.updateMyProfile)) {
this.form.disable();
}
this._loadingService.stop();
} }
get languageChanged(): boolean { get languageChanged(): boolean {
return this.#profileModel['language'] !== this.form.get('language').value; return this.initialFormValue['language'] !== this.form.controls.language.value;
} }
get themeChanged(): boolean { get themeChanged(): boolean {
return this.#profileModel['darkTheme'] !== this.form.get('darkTheme').value; return this.initialFormValue['darkTheme'] !== this.form.controls.darkTheme.value;
} }
get emailChanged(): boolean { get emailChanged(): boolean {
return this.#profileModel['email'] !== this.form.get('email').value; return this.initialFormValue['email'] !== this.form.controls.email.value;
} }
get profileChanged(): boolean { get profileChanged(): boolean {
const keys = Object.keys(this.form.getRawValue()); return this.profileKeys.some(key => this.initialFormValue[key] !== this.form.get(key).value);
keys.splice(keys.indexOf('language'), 1);
keys.splice(keys.indexOf('darkTheme'), 1);
for (const key of keys) {
if (this.#profileModel[key] !== this.form.get(key).value) {
return true;
}
}
return false;
}
get languages(): string[] {
return this._translateService.langs;
}
ngOnInit() {
this._initializeForm();
} }
async save(): Promise<void> { async save(): Promise<void> {
@ -108,16 +120,17 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
} }
if (this.languageChanged) { if (this.languageChanged) {
await this._languageService.change(this.form.get('language').value); await this._languageService.change(this.form.controls.language.value);
await this._pdfViewer.instance?.UI.setLanguage(this._languageService.currentLanguage); await this._pdfViewer.instance?.UI.setLanguage(this._languageService.currentLanguage);
} }
if (this.themeChanged) { if (this.themeChanged) {
await this._userPreferenceService.saveTheme(this.form.get('darkTheme').value ? 'dark' : 'light'); await this._userPreferenceService.saveTheme(this.form.controls.darkTheme.value ? 'dark' : 'light');
} }
this._initializeForm(); this.initialFormValue = this.form.getRawValue();
this._changeRef.markForCheck();
this._loadingService.stop();
this._toaster.success(_('user-profile-screen.update.success')); this._toaster.success(_('user-profile-screen.update.success'));
} catch (e) { } catch (e) {
this._loadingService.stop(); this._loadingService.stop();
@ -128,35 +141,13 @@ export class UserProfileScreenComponent extends BaseFormComponent implements OnI
await this._userService.createResetPasswordAction(); await this._userService.createResetPasswordAction();
} }
private _getForm(): UntypedFormGroup { #getForm() {
return this._formBuilder.group({ return this._formBuilder.group({
email: ['', [Validators.required, Validators.email]], email: [this._userService.currentUser.email ?? '', [Validators.required, Validators.email]],
firstName: [''], firstName: [this._userService.currentUser.firstName ?? ''],
lastName: [''], lastName: [this._userService.currentUser.lastName ?? ''],
language: [''], language: [this._userPreferenceService.getLanguage()],
darkTheme: [false], darkTheme: [this._userPreferenceService.getTheme() === 'dark'],
}); });
} }
private _initializeForm(): void {
try {
this.form = this._getForm();
if (!this._permissionsService.has(Roles.updateMyProfile)) {
this.form.disable();
}
this.#profileModel = {
email: this._userService.currentUser.email ?? '',
firstName: this._userService.currentUser.firstName ?? '',
lastName: this._userService.currentUser.lastName ?? '',
language: this._languageService.currentLanguage ?? '',
darkTheme: this._userPreferenceService.getTheme() === 'dark',
};
this.form.patchValue(this.#profileModel, { emitEvent: false });
this.initialFormValue = this.form.getRawValue();
} catch (e) {
} finally {
this._loadingService.stop();
this._changeRef.markForCheck();
}
}
} }

View File

@ -2,6 +2,8 @@ import { ITrackable } from '@iqser/common-ui';
import type { List } from '@iqser/common-ui/lib/utils'; import type { List } from '@iqser/common-ui/lib/utils';
import type { AnnotationWrapper } from '@models/file/annotation.wrapper'; import type { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Dayjs } from 'dayjs'; import { Dayjs } from 'dayjs';
import { FormControl } from '@angular/forms';
import { toSignal } from '@angular/core/rxjs-interop';
export function hexToRgb(hex: string) { export function hexToRgb(hex: string) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
@ -143,3 +145,7 @@ export function urlFileId() {
const fileId = splitUrl[splitUrl.length - 1]; const fileId = splitUrl[splitUrl.length - 1];
return fileId.split('?')[0]; return fileId.split('?')[0];
} }
export function formControlToSignal<T>(control: FormControl<T>) {
return toSignal(control.valueChanges, { initialValue: control.value });
}

View File

@ -1,6 +1,6 @@
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { Router, RouterStateSnapshot } from '@angular/router'; import { Router, RouterStateSnapshot } from '@angular/router';
import { AsyncGuard, IqserPermissionsService, LoadingService } from '@iqser/common-ui'; import { AsyncGuard, IqserPermissionsService, LanguageService, LoadingService } from '@iqser/common-ui';
import { TenantsService } from '@iqser/common-ui/lib/tenants'; import { TenantsService } from '@iqser/common-ui/lib/tenants';
import { ConfigService } from '@services/config.service'; import { ConfigService } from '@services/config.service';
import { DossiersChangesService } from '@services/dossiers/dossier-changes.service'; import { DossiersChangesService } from '@services/dossiers/dossier-changes.service';
@ -38,6 +38,7 @@ export function mainGuard(): AsyncGuard {
const tenantsService = inject(TenantsService); const tenantsService = inject(TenantsService);
const loadingService = inject(LoadingService); const loadingService = inject(LoadingService);
const configService = inject(ConfigService); const configService = inject(ConfigService);
const languageService = inject(LanguageService);
const baseHref = inject(APP_BASE_HREF); const baseHref = inject(APP_BASE_HREF);
const generalConfig$ = inject(GeneralSettingsService).getGeneralConfigurations(); const generalConfig$ = inject(GeneralSettingsService).getGeneralConfigurations();
@ -51,6 +52,7 @@ export function mainGuard(): AsyncGuard {
firstValueFrom(updatedDisplayName$), firstValueFrom(updatedDisplayName$),
]); ]);
await languageService.setInitialLanguage();
const lastDossierTemplate = userPreferenceService.getLastDossierTemplate(); const lastDossierTemplate = userPreferenceService.getLastDossierTemplate();
if (lastDossierTemplate && !isUsersAdminOnly) { if (lastDossierTemplate && !isUsersAdminOnly) {

@ -1 +1 @@
Subproject commit 3c89b8f7e71eb9253aa40f40c1598eac2c400c71 Subproject commit 32de7758599d887c4b574d70a11b4e0382f30f0c