Pull request #338: VM/RED-2614

Merge in RED/ui from VM/RED-2614 to master

* commit '5db44d09c477a7d8e2c13c4f39833463aa01fc9d': (21 commits)
  fixed one more import in 'can-deactivate.guard.ts'
  fixed more imports
  extended base dialog for 'add edit justifications' component
  extended base dialog for 'add edit user dialog' component
  added PendingChangesGuard for Configurations
  updates for rules screen to can use PendingChangesGuard
  added PendingChangesGuard for dictionary overview component
  updated PendingChangesGuard and used it for user profile and notifications preferences screens
  added warn box for all needed dialogs
  extended base dialog for 'recategorize image dialog' component and 'resize annotation dialog' compoenent
  extended base dialog for 'document info dialog' component and 'force annotation dialog' compoenent
  extended base dialog for 'change legal basis dialog' component
  extended base dialog for 'manual annotation dialog' component
  extended base dialog for 'add dossier' component
  moved 'base-dialog component' and 'confirmation-dialog service' in common
  stored initial form as a simple object instead of a form
  changed 'click' to 'action'
  removed commented code, updated 'close' and 'changeTab' methods
  updated general logic for 'valid' and 'changed' methods to be used by more classes that inherit BaseDialog
  updated base dialog component to display the confirmation dialog whenever the user wants to leave the modal or change its tab with unsaved changes
  ...
This commit is contained in:
Valentin-Gabriel Mihai 2022-01-26 16:31:01 +01:00 committed by Timo Bejan
commit bb1235b700
31 changed files with 544 additions and 492 deletions

View File

@ -1,40 +1,34 @@
import { CanDeactivate } from '@angular/router';
import { Directive, HostListener, Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { Injectable } from '@angular/core';
import { map, Observable } from 'rxjs';
import { ConfirmationDialogService, ConfirmOptions } from '@iqser/common-ui';
export interface ComponentCanDeactivate {
hasChanges: boolean;
}
@Directive()
export abstract class ComponentHasChanges implements ComponentCanDeactivate {
abstract hasChanges: boolean;
protected constructor(protected _translateService: TranslateService) {}
@HostListener('window:beforeunload', ['$event'])
unloadNotification($event: any) {
if (this.hasChanges) {
// This message will be displayed in IE/Edge
$event.returnValue = this._translateService.instant('pending-changes-guard');
}
}
changed: boolean;
valid?: boolean;
isLeavingPage?: boolean;
save: () => Promise<void>;
}
@Injectable({ providedIn: 'root' })
export class PendingChangesGuard implements CanDeactivate<ComponentCanDeactivate> {
constructor(private readonly _translateService: TranslateService) {}
constructor(private _dialogService: ConfirmationDialogService) {}
canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
// if there are no pending changes, just allow deactivation; else confirm first
return !component.hasChanges
? true
: // NOTE: this warning message will only be shown when navigating elsewhere
// within your angular app;
// when navigating away from your angular app,
// the browser will show a generic warning message
// see http://stackoverflow.com/a/42207299/7307355
confirm(this._translateService.instant('pending-changes-guard'));
if (component.changed) {
component.isLeavingPage = true;
const dialogRef = this._dialogService.openDialog({ disableConfirm: component.valid === false });
return dialogRef.afterClosed().pipe(
map(result => {
if (result === ConfirmOptions.CONFIRM) {
component.save();
}
component.isLeavingPage = false;
return !!result;
}),
);
}
return true;
}
}

View File

@ -1,4 +1,4 @@
<form (submit)="save()" [formGroup]="formGroup">
<form (submit)="save()" [formGroup]="form">
<div class="dialog-content">
<div *ngFor="let category of notificationCategories">
<div class="iqser-input-group header w-full">
@ -36,7 +36,7 @@
</div>
<div class="dialog-actions">
<button [disabled]="formGroup.invalid" color="primary" mat-flat-button type="submit">
<button [disabled]="form.invalid" color="primary" mat-flat-button type="submit">
{{ 'user-profile-screen.actions.save' | translate }}
</button>
</div>

View File

@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { notificationsTranslations } from '../../../translations/notifications-translations';
import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
import { LoadingService, Toaster } from '@iqser/common-ui';
import { BaseFormComponent, LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import {
EmailNotificationScheduleTypesValues,
@ -18,57 +18,57 @@ import { firstValueFrom } from 'rxjs';
styleUrls: ['./notifications-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NotificationsScreenComponent implements OnInit {
export class NotificationsScreenComponent extends BaseFormComponent implements OnInit {
readonly emailNotificationScheduleTypes = EmailNotificationScheduleTypesValues;
readonly notificationCategories = NotificationCategoriesValues;
readonly notificationGroupsKeys = NotificationGroupsKeys;
readonly notificationGroupsValues = NotificationGroupsValues;
readonly translations = notificationsTranslations;
readonly formGroup: FormGroup = this._getForm();
constructor(
private readonly _toaster: Toaster,
private readonly _formBuilder: FormBuilder,
private readonly _loadingService: LoadingService,
private readonly _notificationPreferencesService: NotificationPreferencesService,
) {}
) {
super();
}
async ngOnInit(): Promise<void> {
await this._initializeForm();
}
isCategoryActive(category: string) {
return this.formGroup.get(`${category}Enabled`).value;
return this.form.get(`${category}Enabled`).value;
}
setEmailNotificationType(type: string) {
this.formGroup.get('emailNotificationType').setValue(type);
this.form.get('emailNotificationType').setValue(type);
}
getEmailNotificationType() {
return this.formGroup.get('emailNotificationType').value;
return this.form.get('emailNotificationType').value;
}
isPreferenceChecked(category: string, preference: string) {
return this.formGroup.get(category).value.includes(preference);
return this.form.get(category).value.includes(preference);
}
addRemovePreference(checked: boolean, category: string, preference: string) {
const preferences = this.formGroup.get(category).value;
const preferences = this.form.get(category).value;
if (checked) {
preferences.push(preference);
} else {
const indexOfPreference = preferences.indexOf(preference);
preferences.splice(indexOfPreference, 1);
}
this.formGroup.get(category).setValue(preferences);
this.form.get(category).setValue(preferences);
}
async save() {
this._loadingService.start();
try {
await firstValueFrom(this._notificationPreferencesService.update(this.formGroup.value));
await firstValueFrom(this._notificationPreferencesService.update(this.form.value));
} catch (e) {
this._toaster.error(_('notifications-screen.error.generic'));
}
@ -88,8 +88,10 @@ export class NotificationsScreenComponent implements OnInit {
private async _initializeForm() {
this._loadingService.start();
this.form = this._getForm();
const notificationPreferences = await firstValueFrom(this._notificationPreferencesService.get());
this.formGroup.patchValue(notificationPreferences);
this.form.patchValue(notificationPreferences);
this.initialFormValue = JSON.parse(JSON.stringify(this.form.getRawValue()));
this._loadingService.stop();
}

View File

@ -3,8 +3,9 @@ import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component';
import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard';
const routes = [{ path: '', component: NotificationsScreenComponent }];
const routes = [{ path: '', component: NotificationsScreenComponent, canDeactivate: [PendingChangesGuard] }];
@NgModule({
declarations: [NotificationsScreenComponent],

View File

@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { LoadingService } from '@iqser/common-ui';
import { BaseFormComponent, LoadingService } from '@iqser/common-ui';
import { IProfile } from '@red/domain';
import { languagesTranslations } from '../../../translations/languages-translations';
import { PermissionsService } from '@services/permissions.service';
@ -17,8 +17,7 @@ import { firstValueFrom } from 'rxjs';
styleUrls: ['./user-profile-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserProfileScreenComponent implements OnInit {
readonly form: FormGroup = this._getForm();
export class UserProfileScreenComponent extends BaseFormComponent implements OnInit {
changePasswordUrl: SafeResourceUrl;
translations = languagesTranslations;
@ -31,11 +30,11 @@ export class UserProfileScreenComponent implements OnInit {
private readonly _configService: ConfigService,
private readonly _languageService: LanguageService,
private readonly _domSanitizer: DomSanitizer,
private readonly _translateService: TranslateService,
private readonly _loadingService: LoadingService,
protected readonly _translateService: TranslateService,
) {
super();
this._loadingService.start();
this.changePasswordUrl = this._domSanitizer.bypassSecurityTrustResourceUrl(
`${this._configService.values.OAUTH_URL}/account/password`,
);
@ -101,6 +100,7 @@ export class UserProfileScreenComponent implements OnInit {
private _initializeForm(): void {
try {
this.form = this._getForm();
this._profileModel = {
email: this._userService.currentUser.email,
firstName: this._userService.currentUser.firstName,
@ -112,6 +112,7 @@ export class UserProfileScreenComponent implements OnInit {
this.form.get('email').disable();
}
this.form.patchValue(this._profileModel, { emitEvent: false });
this.initialFormValue = this.form.getRawValue();
} catch (e) {
} finally {
this._loadingService.stop();

View File

@ -3,8 +3,9 @@ import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
import { PendingChangesGuard } from '../../../../guards/can-deactivate.guard';
const routes = [{ path: '', component: UserProfileScreenComponent }];
const routes = [{ path: '', component: UserProfileScreenComponent, canDeactivate: [PendingChangesGuard] }];
@NgModule({
declarations: [UserProfileScreenComponent],

View File

@ -174,6 +174,7 @@ const routes: Routes = [
path: 'general-config',
component: GeneralConfigScreenComponent,
canActivate: [CompositeRouteGuard],
canDeactivate: [PendingChangesGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
requiredRoles: ['RED_ADMIN'],

View File

@ -1,17 +1,18 @@
<section class="dialog">
<redaction-user-details
(closeDialog)="dialogRef.close($event)"
(closeDialog)="closeDialog($event)"
(cancel)="close()"
(toggleResetPassword)="toggleResetPassword()"
*ngIf="!resettingPassword"
[hidden]="resettingPassword"
[user]="user"
></redaction-user-details>
<redaction-reset-password
(close)="dialogRef.close($event)"
(close)="closeDialog($event)"
(toggleResetPassword)="toggleResetPassword()"
*ngIf="resettingPassword"
[hidden]="!resettingPassword"
[user]="user"
></redaction-reset-password>
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
</section>

View File

@ -1,18 +1,44 @@
import { Component, Inject } from '@angular/core';
import { Component, Inject, Injector, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { User } from '@red/domain';
import { UserDetailsComponent } from './user-details/user-details.component';
import { BaseDialogComponent } from '@iqser/common-ui';
@Component({
selector: 'redaction-add-edit-user-dialog',
templateUrl: './add-edit-user-dialog.component.html',
styleUrls: ['./add-edit-user-dialog.component.scss'],
})
export class AddEditUserDialogComponent {
export class AddEditUserDialogComponent extends BaseDialogComponent {
@ViewChild(UserDetailsComponent) private readonly _userDetailsComponent: UserDetailsComponent;
resettingPassword = false;
constructor(readonly dialogRef: MatDialogRef<AddEditUserDialogComponent>, @Inject(MAT_DIALOG_DATA) readonly user: User) {}
constructor(
protected readonly _injector: Injector,
protected readonly _dialogRef: MatDialogRef<AddEditUserDialogComponent>,
@Inject(MAT_DIALOG_DATA) readonly user: User,
) {
super(_injector, _dialogRef);
}
toggleResetPassword() {
this.resettingPassword = !this.resettingPassword;
}
async save(): Promise<void> {
await this._userDetailsComponent.save();
}
get changed(): boolean {
return this._userDetailsComponent.changed;
}
get valid(): boolean {
return this._userDetailsComponent.valid;
}
closeDialog(event) {
this._dialogRef.close(event);
}
}

View File

@ -53,6 +53,6 @@
icon="iqser:trash"
></iqser-icon-button>
<div class="all-caps-label cancel" mat-dialog-close translate="add-edit-user.actions.cancel"></div>
<div class="all-caps-label cancel" translate="add-edit-user.actions.cancel" (click)="cancel.emit()"></div>
</div>
</form>

View File

@ -1,7 +1,7 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AdminDialogService } from '../../../services/admin-dialog.service';
import { AutoUnsubscribe, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { BaseFormComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { rolesTranslations } from '../../../../../translations/roles-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { User } from '@red/domain';
@ -14,16 +14,16 @@ import { firstValueFrom } from 'rxjs';
templateUrl: './user-details.component.html',
styleUrls: ['./user-details.component.scss'],
})
export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges, OnDestroy {
export class UserDetailsComponent extends BaseFormComponent implements OnChanges, OnDestroy {
readonly iconButtonTypes = IconButtonTypes;
@Input() user: User;
@Output() readonly toggleResetPassword = new EventEmitter();
@Output() readonly closeDialog = new EventEmitter();
@Output() readonly cancel = new EventEmitter();
readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
readonly translations = rolesTranslations;
form: FormGroup;
private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
constructor(
@ -36,29 +36,6 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges,
super();
}
get changed(): boolean {
if (!this.user) {
return true;
}
if (this.user.roles.length !== this.activeRoles.length) {
return true;
}
for (const key of Object.keys(this.form.getRawValue())) {
const keyValue = this.form.get(key).value;
if (key.startsWith('RED_')) {
if (this.user.roles.includes(key) !== keyValue) {
return true;
}
} else if (this.user[key] !== keyValue) {
return true;
}
}
return false;
}
get activeRoles(): string[] {
return this.ROLES.reduce((acc, role) => {
if (this.form.get(role).value) {
@ -86,6 +63,7 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges,
ngOnChanges() {
this.form = this._getForm();
this._setRolesRequirements();
this.initialFormValue = this.form.getRawValue();
}
shouldBeDisabled(role: string): boolean {
@ -133,7 +111,6 @@ export class UserDetailsComponent extends AutoUnsubscribe implements OnChanges,
}
private _getForm(): FormGroup {
console.log(this.user);
return this._formBuilder.group({
firstName: [this.user?.firstName, Validators.required],
lastName: [this.user?.lastName, Validators.required],

View File

@ -27,5 +27,5 @@
</div>
</form>
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
</section>

View File

@ -1,23 +1,27 @@
import { Component, Inject } from '@angular/core';
import { Component, Inject, Injector } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UserService } from '@services/user.service';
import { ISmtpConfiguration } from '@red/domain';
import { BaseDialogComponent } from '@iqser/common-ui';
@Component({
selector: 'redaction-smtp-auth-dialog',
templateUrl: './smtp-auth-dialog.component.html',
styleUrls: ['./smtp-auth-dialog.component.scss'],
})
export class SmtpAuthDialogComponent {
readonly form: FormGroup = this._getForm();
export class SmtpAuthDialogComponent extends BaseDialogComponent {
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _userService: UserService,
public dialogRef: MatDialogRef<SmtpAuthDialogComponent>,
protected readonly _injector: Injector,
protected readonly _dialogRef: MatDialogRef<SmtpAuthDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: ISmtpConfiguration,
) {}
) {
super(_injector, _dialogRef);
this.form = this._getForm();
this.initialFormValue = this.form.getRawValue();
}
private _getForm(): FormGroup {
return this._formBuilder.group({
@ -27,6 +31,6 @@ export class SmtpAuthDialogComponent {
}
save() {
this.dialogRef.close(this.form.getRawValue());
this._dialogRef.close(this.form.getRawValue());
}
}

View File

@ -55,8 +55,9 @@
<redaction-dictionary-manager
#dictionaryManager
(saveDictionary)="saveEntries($event)"
(saveDictionary)="save()"
[canEdit]="currentUser.isAdmin"
[isLeavingPage]="isLeavingPage"
[filterByDossierTemplate]="true"
[initialEntries]="initialEntries"
></redaction-dictionary-manager>

View File

@ -3,7 +3,6 @@ import { AppStateService } from '@state/app-state.service';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver';
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
import { DictionaryService } from '@shared/services/dictionary.service';
@ -17,12 +16,13 @@ import { firstValueFrom } from 'rxjs';
templateUrl: './dictionary-overview-screen.component.html',
styleUrls: ['./dictionary-overview-screen.component.scss'],
})
export class DictionaryOverviewScreenComponent extends ComponentHasChanges implements OnInit, OnDestroy {
export class DictionaryOverviewScreenComponent implements OnInit, OnDestroy {
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
initialEntries: string[] = [];
dictionary: Dictionary;
isLeavingPage = false;
@ViewChild('dictionaryManager', { static: false })
private readonly _dictionaryManager: DictionaryManagerComponent;
@ -38,11 +38,9 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
private readonly _dialogService: AdminDialogService,
protected readonly _translateService: TranslateService,
private readonly _dictionaryService: DictionaryService,
) {
super(_translateService);
}
) {}
get hasChanges() {
get changed() {
return this._dictionaryManager.editor.hasChanges;
}
@ -117,7 +115,9 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
}
}
saveEntries(entries: string[]) {
save() {
const entries = this._dictionaryManager.editor?.currentEntries;
this._loadingService.start();
this._dictionaryService
.saveEntries(entries, this.initialEntries, this.dictionary.dossierTemplateId, this.dictionary.type, null)

View File

@ -2,7 +2,7 @@
<div class="heading-l" translate="general-config-screen.general.title"></div>
<div translate="general-config-screen.general.subtitle"></div>
</div>
<form (submit)="saveGeneralConfig()" [formGroup]="form">
<form (submit)="save()" [formGroup]="form" *ngIf="form">
<div class="dialog-content">
<div class="dialog-content-left">
<div class="iqser-input-group">
@ -23,7 +23,7 @@
</div>
</div>
<div class="dialog-actions">
<button [disabled]="form.invalid || !generalConfigurationChanged" color="primary" mat-flat-button type="submit">
<button [disabled]="form?.invalid || !changed" color="primary" mat-flat-button type="submit">
{{ 'general-config-screen.actions.save' | translate }}
</button>
</div>

View File

@ -1,5 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { AutoUnsubscribe, LoadingService } from '@iqser/common-ui';
import { BaseFormComponent, LoadingService } from '@iqser/common-ui';
import { GeneralSettingsService } from '@services/general-settings.service';
import { IGeneralConfiguration } from '@red/domain';
import { ConfigService } from '@services/config.service';
@ -11,8 +11,7 @@ import { firstValueFrom } from 'rxjs';
templateUrl: './general-config-form.component.html',
styleUrls: ['./general-config-form.component.scss'],
})
export class GeneralConfigFormComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
readonly form: FormGroup = this._getForm();
export class GeneralConfigFormComponent extends BaseFormComponent implements OnInit, OnDestroy {
private _initialConfiguration: IGeneralConfiguration;
constructor(
@ -22,6 +21,7 @@ export class GeneralConfigFormComponent extends AutoUnsubscribe implements OnIni
private readonly _formBuilder: FormBuilder,
) {
super();
this.form = this._getForm();
}
get generalConfigurationChanged(): boolean {
@ -42,7 +42,7 @@ export class GeneralConfigFormComponent extends AutoUnsubscribe implements OnIni
await this._loadData();
}
async saveGeneralConfig() {
async save() {
this._loadingService.start();
const configFormValues = this.form.getRawValue();
@ -51,6 +51,7 @@ export class GeneralConfigFormComponent extends AutoUnsubscribe implements OnIni
this._initialConfiguration = await firstValueFrom(this._generalSettingsService.getGeneralConfigurations());
this._configService.updateDisplayName(this._initialConfiguration.displayName);
this._loadingService.stop();
await this._loadData();
}
private _getForm(): FormGroup {
@ -65,6 +66,7 @@ export class GeneralConfigFormComponent extends AutoUnsubscribe implements OnIni
try {
this._initialConfiguration = await firstValueFrom(this._generalSettingsService.getGeneralConfigurations());
this.form.patchValue(this._initialConfiguration, { emitEvent: false });
this.initialFormValue = this.form.getRawValue();
} catch (e) {}
this._loadingService.stop();

View File

@ -1,13 +1,52 @@
import { Component } from '@angular/core';
import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { UserService } from '@services/user.service';
import { GeneralConfigFormComponent } from './general-config-form/general-config-form.component';
import { SmtpFormComponent } from './smtp-form/smtp-form.component';
import { BaseFormComponent } from '@iqser/common-ui';
@Component({
selector: 'redaction-general-config-screen',
templateUrl: './general-config-screen.component.html',
styleUrls: ['./general-config-screen.component.scss'],
})
export class GeneralConfigScreenComponent {
export class GeneralConfigScreenComponent extends BaseFormComponent implements AfterViewInit {
readonly currentUser = this._userService.currentUser;
constructor(private readonly _userService: UserService) {}
@ViewChild(GeneralConfigFormComponent) generalConfigFormComponent: GeneralConfigFormComponent;
@ViewChild(SmtpFormComponent) smtpFormComponent: SmtpFormComponent;
children: BaseFormComponent[];
constructor(private readonly _userService: UserService) {
super();
}
ngAfterViewInit() {
this.children = [this.generalConfigFormComponent, this.smtpFormComponent];
}
get changed(): boolean {
for (const child of this.children) {
if (child.changed) {
return true;
}
}
return false;
}
get valid() {
for (const child of this.children) {
if (!child.valid) {
return false;
}
}
return true;
}
async save(): Promise<void> {
for (const child of this.children) {
if (child.changed) {
await child.save();
}
}
}
}

View File

@ -94,7 +94,7 @@
</div>
</div>
<div class="dialog-actions">
<button [disabled]="form.invalid || !smtpConfigurationChanged" color="primary" mat-flat-button type="submit">
<button [disabled]="form.invalid || !changed" color="primary" mat-flat-button type="submit">
{{ 'general-config-screen.actions.save' | translate }}
</button>

View File

@ -1,7 +1,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ISmtpConfiguration } from '@red/domain';
import { AutoUnsubscribe, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { BaseFormComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { AdminDialogService } from '../../../services/admin-dialog.service';
import { SmtpConfigService } from '../../../services/smtp-config.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -12,9 +12,8 @@ import { firstValueFrom } from 'rxjs';
templateUrl: './smtp-form.component.html',
styleUrls: ['./smtp-form.component.scss'],
})
export class SmtpFormComponent extends AutoUnsubscribe implements OnInit, OnDestroy {
export class SmtpFormComponent extends BaseFormComponent implements OnInit, OnDestroy {
readonly iconButtonTypes = IconButtonTypes;
readonly form: FormGroup = this._getForm();
private _initialConfiguration: ISmtpConfiguration;
constructor(
@ -25,6 +24,7 @@ export class SmtpFormComponent extends AutoUnsubscribe implements OnInit, OnDest
private readonly _toaster: Toaster,
) {
super();
this.form = this._getForm();
this.addSubscription = this.form.controls.auth.valueChanges.subscribe(auth => {
if (auth) {
this.openAuthConfigDialog();
@ -32,20 +32,6 @@ export class SmtpFormComponent extends AutoUnsubscribe implements OnInit, OnDest
});
}
get smtpConfigurationChanged(): boolean {
if (!this._initialConfiguration) {
return true;
}
for (const key of Object.keys(this.form.getRawValue())) {
if (this._initialConfiguration[key] !== this.form.get(key).value) {
return true;
}
}
return false;
}
async ngOnInit(): Promise<void> {
await this._loadData();
}
@ -65,6 +51,7 @@ export class SmtpFormComponent extends AutoUnsubscribe implements OnInit, OnDest
await firstValueFrom(this._smtpConfigService.updateSMTPConfiguration(this.form.getRawValue()));
this._initialConfiguration = this.form.getRawValue();
this._loadingService.stop();
this._loadData();
}
async testConnection() {
@ -104,6 +91,7 @@ export class SmtpFormComponent extends AutoUnsubscribe implements OnInit, OnDest
this.form.patchValue(this._initialConfiguration, { emitEvent: false });
} catch (e) {}
this.initialFormValue = this.form.getRawValue();
this._loadingService.stop();
}
}

View File

@ -43,13 +43,13 @@
</div>
</div>
<div class="dialog-actions">
<button [disabled]="form.invalid || !changed" color="primary" mat-flat-button type="submit">
<button [disabled]="disabled" color="primary" mat-flat-button type="submit">
{{ 'add-edit-justification.actions.save' | translate }}
</button>
<div class="all-caps-label cancel" mat-dialog-close translate="add-edit-justification.actions.cancel"></div>
<div class="all-caps-label cancel" translate="add-edit-justification.actions.cancel" (click)="close()"></div>
</div>
</form>
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
</section>

View File

@ -1,10 +1,10 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { ChangeDetectionStrategy, Component, Inject, Injector } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Justification } from '@red/domain';
import { JustificationsService } from '@services/entity-services/justifications.service';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { LoadingService } from '@iqser/common-ui';
import { BaseDialogComponent, LoadingService } from '@iqser/common-ui';
import { firstValueFrom } from 'rxjs';
@Component({
@ -13,23 +13,21 @@ import { firstValueFrom } from 'rxjs';
styleUrls: ['./add-edit-justification-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddEditJustificationDialogComponent {
readonly form: FormGroup = this._getForm();
export class AddEditJustificationDialogComponent extends BaseDialogComponent {
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _justificationService: JustificationsService,
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _loadingService: LoadingService,
public dialogRef: MatDialogRef<AddEditJustificationDialogComponent>,
protected readonly _injector: Injector,
protected readonly _dialogRef: MatDialogRef<AddEditJustificationDialogComponent>,
@Inject(MAT_DIALOG_DATA) public justification: Justification,
) {}
) {
super(_injector, _dialogRef);
get changed(): boolean {
return (
!this.justification ||
Object.keys(this.form.getRawValue()).reduce((prev, key) => prev || this.justification[key] !== this.form.get(key).value, false)
);
this.form = this._getForm();
this.initialFormValue = this.form.getRawValue();
}
async save() {
@ -39,7 +37,7 @@ export class AddEditJustificationDialogComponent {
await firstValueFrom(this._justificationService.createOrUpdate(this.form.getRawValue(), dossierTemplateId));
await firstValueFrom(this._justificationService.loadAll(dossierTemplateId));
this._loadingService.stop();
this.dialogRef.close(true);
this._dialogRef.close(true);
}
private _getForm(): FormGroup {

View File

@ -22,7 +22,7 @@
<div class="editor-container">
<ngx-monaco-editor (init)="onCodeEditorInit($event)" [(ngModel)]="codeEditorText" [options]="editorOptions"></ngx-monaco-editor>
</div>
<div *ngIf="hasChanges && permissionsService.isAdmin()" class="changes-box">
<div *ngIf="changed && permissionsService.isAdmin() && !isLeaving" class="changes-box">
<iqser-icon-button
(action)="save()"
[label]="'rules-screen.save-changes' | translate"

View File

@ -3,7 +3,6 @@ import { PermissionsService } from '@services/permissions.service';
import { Debounce, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver';
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { RulesService } from '../../services/rules.service';
@ -18,7 +17,7 @@ import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorCon
styleUrls: ['./rules-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RulesScreenComponent extends ComponentHasChanges implements OnInit {
export class RulesScreenComponent implements OnInit {
readonly iconButtonTypes = IconButtonTypes;
readonly editorOptions: IStandaloneEditorConstructionOptions = {
theme: 'vs',
@ -30,6 +29,8 @@ export class RulesScreenComponent extends ComponentHasChanges implements OnInit
initialLines: string[] = [];
currentLines: string[] = [];
isLeaving = false;
@ViewChild('fileInput')
private _fileInput: ElementRef;
@ -44,11 +45,14 @@ export class RulesScreenComponent extends ComponentHasChanges implements OnInit
private readonly _toaster: Toaster,
protected readonly _translateService: TranslateService,
private readonly _loadingService: LoadingService,
) {
super(_translateService);
) {}
set isLeavingPage(isLeaving: boolean) {
this.isLeaving = isLeaving;
this._changeDetectorRef.detectChanges();
}
get hasChanges(): boolean {
get changed(): boolean {
return this.currentLines.toString() !== this.initialLines.toString();
}

View File

@ -62,7 +62,7 @@ export class AdminDialogService extends DialogService<DialogType> {
},
addEditUser: {
component: AddEditUserDialogComponent,
dialogConfig: { autoFocus: true, disableClose: false },
dialogConfig: { autoFocus: true },
},
smtpAuthConfig: {
component: SmtpAuthDialogComponent,

View File

@ -100,7 +100,7 @@
</div>
</form>
<iqser-circle-button class="dialog-close" icon="iqser:close" mat-dialog-close></iqser-circle-button>
<iqser-circle-button class="dialog-close" icon="iqser:close" (action)="close()"></iqser-circle-button>
</section>
<ng-template #reportTemplateOptionTemplate let-option="option">

View File

@ -9,8 +9,8 @@ import { PermissionsService } from '@services/permissions.service';
import { JustificationsService } from '@services/entity-services/justifications.service';
import { Dictionary, Dossier, File, IAddRedactionRequest } from '@red/domain';
import { DossiersService } from '@services/entity-services/dossiers.service';
import { BaseDialogComponent } from '@iqser/common-ui';
import { DictionaryService } from '@shared/services/dictionary.service';
import { BaseDialogComponent } from '@iqser/common-ui';
import { firstValueFrom } from 'rxjs';
export interface LegalBasisOption {
@ -42,7 +42,6 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
private readonly _permissionsService: PermissionsService,
private readonly _dossiersService: DossiersService,
private readonly _dictionaryService: DictionaryService,
public dialogRef: MatDialogRef<ManualAnnotationDialogComponent>,
protected readonly _injector: Injector,
protected readonly _dialogRef: MatDialogRef<ManualAnnotationDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { manualRedactionEntryWrapper: ManualRedactionEntryWrapper; file: File },
@ -77,7 +76,6 @@ export class ManualAnnotationDialogComponent extends BaseDialogComponent impleme
async ngOnInit() {
super.ngOnInit();
this.possibleDictionaries = await this._getPossibleDictionaries();
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this._dossier.dossierTemplateId));
this.legalOptions = data.map(lbm => ({
legalBasis: lbm.reason,

View File

@ -76,9 +76,9 @@
</div>
</div>
<div *ngIf="withFloatingActions && !!editor?.hasChanges && canEdit" [class.offset]="compare" class="changes-box">
<div *ngIf="withFloatingActions && !!editor?.hasChanges && canEdit && !isLeavingPage" [class.offset]="compare" class="changes-box">
<iqser-icon-button
(action)="saveDictionary.emit(editor?.currentEntries)"
(action)="saveDictionary.emit()"
[label]="'dictionary-overview.save-changes' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { Debounce, IconButtonTypes, List } from '@iqser/common-ui';
import { firstValueFrom, Observable, of } from 'rxjs';
import { catchError, map, take, tap } from 'rxjs/operators';
@ -26,6 +26,7 @@ export class DictionaryManagerComponent implements OnChanges {
@Input() filterByDossierTemplate = false;
@Input() initialEntries: List;
@Input() canEdit = false;
@Input() isLeavingPage = false;
@Output() readonly saveDictionary = new EventEmitter<string[]>();
@ViewChild(EditorComponent) readonly editor: EditorComponent;
@ -175,8 +176,10 @@ export class DictionaryManagerComponent implements OnChanges {
this._scrollToCurrentMatch();
}
ngOnChanges(): void {
this.revert();
ngOnChanges(changes: SimpleChanges): void {
if (!changes.isLeavingPage) {
this.revert();
}
}
private _applySearchDecorations() {

@ -1 +1 @@
Subproject commit f4e0445212824788d10cd81adc0bce484a8dbb02
Subproject commit 47dc55206bdea193eec78b021e04ccbaaf5b6915

File diff suppressed because it is too large Load Diff