mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'accessibility-settings-7.6' into accessibility-settings-8_x
# Conflicts: # src/app/accessibility/accessibility-settings.service.spec.ts # src/app/accessibility/accessibility-settings.service.ts # src/app/info/accessibility-settings/accessibility-settings.component.spec.ts # src/app/info/accessibility-settings/accessibility-settings.component.ts # src/app/shared/cookies/klaro-configuration.ts
This commit is contained in:
@@ -4,10 +4,12 @@ import {
|
||||
} from '@angular/core/testing';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { AppConfig } from '../../config/app-config.interface';
|
||||
import { AuthService } from '../core/auth/auth.service';
|
||||
import { EPersonDataService } from '../core/eperson/eperson-data.service';
|
||||
import { EPerson } from '../core/eperson/models/eperson.model';
|
||||
import { CookieService } from '../core/services/cookie.service';
|
||||
import { KlaroServiceStub } from '../shared/cookies/klaro.service.stub';
|
||||
import { CookieServiceMock } from '../shared/mocks/cookie.service.mock';
|
||||
import {
|
||||
createFailedRemoteDataObject$,
|
||||
@@ -29,10 +31,16 @@ describe('accessibilitySettingsService', () => {
|
||||
let cookieService: CookieServiceMock;
|
||||
let authService: AuthServiceStub;
|
||||
let ePersonService: EPersonDataService;
|
||||
let klaroService: KlaroServiceStub;
|
||||
let appConfig: AppConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
cookieService = new CookieServiceMock();
|
||||
authService = new AuthServiceStub();
|
||||
klaroService = new KlaroServiceStub();
|
||||
appConfig = { accessibility: { cookieExpirationDuration: 10 } } as AppConfig;
|
||||
|
||||
klaroService.getSavedPreferences.and.returnValue(of({ accessibility: true }));
|
||||
|
||||
ePersonService = jasmine.createSpyObj('ePersonService', {
|
||||
createPatchFromCache: of([{
|
||||
@@ -46,6 +54,8 @@ describe('accessibilitySettingsService', () => {
|
||||
cookieService as unknown as CookieService,
|
||||
authService as unknown as AuthService,
|
||||
ePersonService,
|
||||
klaroService,
|
||||
appConfig,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -180,12 +190,12 @@ describe('accessibilitySettingsService', () => {
|
||||
|
||||
describe('setSettings', () => {
|
||||
beforeEach(() => {
|
||||
service.setSettingsInCookie = jasmine.createSpy('setSettingsInCookie');
|
||||
service.setSettingsInCookie = jasmine.createSpy('setSettingsInCookie').and.returnValue(of('cookie'));
|
||||
});
|
||||
|
||||
it('should attempt to set settings in metadata', () => {
|
||||
service.setSettingsInAuthenticatedUserMetadata =
|
||||
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(false));
|
||||
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of('failed'));
|
||||
|
||||
const settings: AccessibilitySettings = {
|
||||
notificationTimeOut: '1000',
|
||||
@@ -209,7 +219,7 @@ describe('accessibilitySettingsService', () => {
|
||||
|
||||
it('should not set settings in cookie if metadata succeeded', () => {
|
||||
service.setSettingsInAuthenticatedUserMetadata =
|
||||
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(true));
|
||||
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of('metadata'));
|
||||
|
||||
const settings: AccessibilitySettings = {
|
||||
notificationTimeOut: '1000',
|
||||
@@ -221,7 +231,7 @@ describe('accessibilitySettingsService', () => {
|
||||
|
||||
it('should return \'metadata\' if settings are stored in metadata', () => {
|
||||
service.setSettingsInAuthenticatedUserMetadata =
|
||||
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(true));
|
||||
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of('metadata'));
|
||||
|
||||
const settings: AccessibilitySettings = {
|
||||
notificationTimeOut: '1000',
|
||||
@@ -284,11 +294,11 @@ describe('accessibilitySettingsService', () => {
|
||||
expect(service.setSettingsInMetadata).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should emit false when the user is not authenticated', fakeAsync(() => {
|
||||
it('should emit "failed" when the user is not authenticated', fakeAsync(() => {
|
||||
authService.getAuthenticatedUserFromStoreIfAuthenticated = jasmine.createSpy().and.returnValue(of(null));
|
||||
|
||||
service.setSettingsInAuthenticatedUserMetadata({})
|
||||
.subscribe(value => expect(value).toBeFalse());
|
||||
.subscribe(value => expect(value).toEqual('failed'));
|
||||
flush();
|
||||
|
||||
expect(service.setSettingsInMetadata).not.toHaveBeenCalled();
|
||||
@@ -324,23 +334,23 @@ describe('accessibilitySettingsService', () => {
|
||||
expect(ePersonService.patch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit true when the update succeeded', fakeAsync(() => {
|
||||
it('should emit "metadata" when the update succeeded', fakeAsync(() => {
|
||||
ePersonService.patch = jasmine.createSpy().and.returnValue(createSuccessfulRemoteDataObject$({}));
|
||||
|
||||
service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' })
|
||||
.subscribe(value => {
|
||||
expect(value).toBeTrue();
|
||||
expect(value).toEqual('metadata');
|
||||
});
|
||||
|
||||
flush();
|
||||
}));
|
||||
|
||||
it('should emit false when the update failed', fakeAsync(() => {
|
||||
it('should emit "failed" when the update failed', fakeAsync(() => {
|
||||
ePersonService.patch = jasmine.createSpy().and.returnValue(createFailedRemoteDataObject$());
|
||||
|
||||
service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' })
|
||||
.subscribe(value => {
|
||||
expect(value).toBeFalse();
|
||||
expect(value).toEqual('failed');
|
||||
});
|
||||
|
||||
flush();
|
||||
@@ -353,16 +363,34 @@ describe('accessibilitySettingsService', () => {
|
||||
cookieService.remove = jasmine.createSpy('remove');
|
||||
});
|
||||
|
||||
it('should store the settings in a cookie', () => {
|
||||
service.setSettingsInCookie({ ['liveRegionTimeOut']: '500' });
|
||||
expect(cookieService.set).toHaveBeenCalled();
|
||||
});
|
||||
it('should fail to store settings in the cookie when the user has not accepted the cookie', fakeAsync(() => {
|
||||
klaroService.getSavedPreferences.and.returnValue(of({ accessibility: false }));
|
||||
|
||||
service.setSettingsInCookie({ ['liveRegionTimeOut']: '500' }).subscribe(value => {
|
||||
expect(value).toEqual('failed');
|
||||
});
|
||||
flush();
|
||||
expect(cookieService.set).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should store the settings in a cookie', fakeAsync(() => {
|
||||
service.setSettingsInCookie({ ['liveRegionTimeOut']: '500' }).subscribe(value => {
|
||||
expect(value).toEqual('cookie');
|
||||
});
|
||||
flush();
|
||||
expect(cookieService.set).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should remove the cookie when the settings are empty', fakeAsync(() => {
|
||||
service.setSettingsInCookie({}).subscribe(value => {
|
||||
expect(value).toEqual('cookie');
|
||||
});
|
||||
|
||||
flush();
|
||||
|
||||
it('should remove the cookie when the settings are empty', () => {
|
||||
service.setSettingsInCookie({});
|
||||
expect(cookieService.set).not.toHaveBeenCalled();
|
||||
expect(cookieService.remove).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('convertFormValuesToStoredValues', () => {
|
||||
|
@@ -37,5 +37,9 @@ export class AccessibilitySettingsServiceStub {
|
||||
|
||||
convertStoredValuesToFormValues = jasmine.createSpy('convertStoredValuesToFormValues').and.returnValue({});
|
||||
|
||||
getPlaceholder = jasmine.createSpy('getPlaceholder').and.returnValue('placeholder');
|
||||
getDefaultValue = jasmine.createSpy('getPlaceholder').and.returnValue('placeholder');
|
||||
|
||||
isValid = jasmine.createSpy('isValid').and.returnValue(true);
|
||||
|
||||
formValuesValid = jasmine.createSpy('allValid').and.returnValue(true);
|
||||
}
|
||||
|
@@ -1,6 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
Inject,
|
||||
Injectable,
|
||||
} from '@angular/core';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import {
|
||||
combineLatest,
|
||||
Observable,
|
||||
of,
|
||||
switchMap,
|
||||
@@ -10,13 +14,19 @@ import {
|
||||
take,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
APP_CONFIG,
|
||||
AppConfig,
|
||||
} from '../../config/app-config.interface';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { AuthService } from '../core/auth/auth.service';
|
||||
import { EPersonDataService } from '../core/eperson/eperson-data.service';
|
||||
import { EPerson } from '../core/eperson/models/eperson.model';
|
||||
import { CookieService } from '../core/services/cookie.service';
|
||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { KlaroService } from '../shared/cookies/klaro.service';
|
||||
import {
|
||||
hasNoValue,
|
||||
hasValue,
|
||||
isNotEmpty,
|
||||
} from '../shared/empty.util';
|
||||
@@ -33,11 +43,16 @@ export const ACCESSIBILITY_COOKIE = 'dsAccessibilityCookie';
|
||||
export const ACCESSIBILITY_SETTINGS_METADATA_KEY = 'dspace.accessibility.settings';
|
||||
|
||||
/**
|
||||
* Type containing all possible accessibility settings.
|
||||
* Array containing all possible accessibility settings.
|
||||
* When adding new settings, make sure to add the new setting to the accessibility-settings component form.
|
||||
* The converter methods to convert from stored format to form format (and vice-versa) need to be updated as well.
|
||||
*/
|
||||
export type AccessibilitySetting = 'notificationTimeOut' | 'liveRegionTimeOut';
|
||||
export const accessibilitySettingKeys = ['notificationTimeOut', 'liveRegionTimeOut'] as const;
|
||||
|
||||
/**
|
||||
* Type representing the possible accessibility settings
|
||||
*/
|
||||
export type AccessibilitySetting = typeof accessibilitySettingKeys[number];
|
||||
|
||||
/**
|
||||
* Type representing an object that contains accessibility settings values for all accessibility settings.
|
||||
@@ -73,6 +88,8 @@ export class AccessibilitySettingsService {
|
||||
protected cookieService: CookieService,
|
||||
protected authService: AuthService,
|
||||
protected ePersonService: EPersonDataService,
|
||||
protected klaroService: KlaroService,
|
||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -136,8 +153,9 @@ export class AccessibilitySettingsService {
|
||||
*
|
||||
* Returns 'cookie' when the changes were stored in the cookie.
|
||||
* Returns 'metadata' when the changes were stored in metadata.
|
||||
* Returns 'failed' when both options failed.
|
||||
*/
|
||||
set(setting: AccessibilitySetting, value: string): Observable<'cookie' | 'metadata'> {
|
||||
set(setting: AccessibilitySetting, value: string): Observable<'metadata' | 'cookie' | 'failed'> {
|
||||
return this.updateSettings({ [setting]: value });
|
||||
}
|
||||
|
||||
@@ -148,18 +166,15 @@ export class AccessibilitySettingsService {
|
||||
*
|
||||
* Returns 'cookie' when the changes were stored in the cookie.
|
||||
* Returns 'metadata' when the changes were stored in metadata.
|
||||
* Returns 'failed' when both options failed.
|
||||
*/
|
||||
setSettings(settings: AccessibilitySettings): Observable<'cookie' | 'metadata'> {
|
||||
setSettings(settings: AccessibilitySettings): Observable<'metadata' | 'cookie' | 'failed'> {
|
||||
return this.setSettingsInAuthenticatedUserMetadata(settings).pipe(
|
||||
take(1),
|
||||
map((succeeded) => {
|
||||
if (!succeeded) {
|
||||
this.setSettingsInCookie(settings);
|
||||
return 'cookie';
|
||||
} else {
|
||||
return 'metadata';
|
||||
}
|
||||
}),
|
||||
map(saveLocation => saveLocation === 'metadata'),
|
||||
switchMap((savedInMetadata) =>
|
||||
savedInMetadata ? ofMetadata() : this.setSettingsInCookie(settings),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -169,8 +184,9 @@ export class AccessibilitySettingsService {
|
||||
*
|
||||
* Returns 'cookie' when the changes were stored in the cookie.
|
||||
* Returns 'metadata' when the changes were stored in metadata.
|
||||
* Returns 'failed' when both options failed.
|
||||
*/
|
||||
updateSettings(settings: AccessibilitySettings): Observable<'cookie' | 'metadata'> {
|
||||
updateSettings(settings: AccessibilitySettings): Observable<'metadata' | 'cookie' | 'failed'> {
|
||||
return this.getAll().pipe(
|
||||
take(1),
|
||||
map(currentSettings => Object.assign({}, currentSettings, settings)),
|
||||
@@ -181,9 +197,9 @@ export class AccessibilitySettingsService {
|
||||
/**
|
||||
* Attempts to set the provided settings on the currently authorized user's metadata.
|
||||
* Emits false when no user is authenticated or when the metadata update failed.
|
||||
* Emits true when the metadata update succeeded.
|
||||
* Emits 'metadata' when the metadata update succeeded, and 'failed' otherwise.
|
||||
*/
|
||||
setSettingsInAuthenticatedUserMetadata(settings: AccessibilitySettings): Observable<boolean> {
|
||||
setSettingsInAuthenticatedUserMetadata(settings: AccessibilitySettings): Observable<'metadata' | 'failed'> {
|
||||
return this.authService.getAuthenticatedUserFromStoreIfAuthenticated().pipe(
|
||||
take(1),
|
||||
switchMap(user => {
|
||||
@@ -192,7 +208,7 @@ export class AccessibilitySettingsService {
|
||||
const clonedUser = cloneDeep(user);
|
||||
return this.setSettingsInMetadata(clonedUser, settings);
|
||||
} else {
|
||||
return of(false);
|
||||
return ofFailed();
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -205,7 +221,7 @@ export class AccessibilitySettingsService {
|
||||
setSettingsInMetadata(
|
||||
user: EPerson,
|
||||
settings: AccessibilitySettings,
|
||||
): Observable<boolean> {
|
||||
): Observable<'metadata' | 'failed'> {
|
||||
if (isNotEmpty(settings)) {
|
||||
user.setMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY, null, JSON.stringify(settings));
|
||||
} else {
|
||||
@@ -217,35 +233,49 @@ export class AccessibilitySettingsService {
|
||||
switchMap(operations =>
|
||||
isNotEmpty(operations) ? this.ePersonService.patch(user, operations) : createSuccessfulRemoteDataObject$({})),
|
||||
getFirstCompletedRemoteData(),
|
||||
map(rd => rd.hasSucceeded),
|
||||
switchMap(rd => rd.hasSucceeded ? ofMetadata() : ofFailed()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provided settings in a cookie
|
||||
* Attempts to set the provided settings in a cookie.
|
||||
* Emits 'failed' when setting in a cookie failed due to the cookie not being accepted, 'cookie' when it succeeded.
|
||||
*/
|
||||
setSettingsInCookie(settings: AccessibilitySettings) {
|
||||
if (isNotEmpty(settings)) {
|
||||
this.cookieService.set(ACCESSIBILITY_COOKIE, settings, { expires: environment.accessibility.cookieExpirationDuration });
|
||||
} else {
|
||||
this.cookieService.remove(ACCESSIBILITY_COOKIE);
|
||||
}
|
||||
setSettingsInCookie(settings: AccessibilitySettings): Observable<'cookie' | 'failed'> {
|
||||
return this.klaroService.getSavedPreferences().pipe(
|
||||
map(preferences => preferences.accessibility),
|
||||
map((accessibilityCookieAccepted: boolean) => {
|
||||
if (accessibilityCookieAccepted) {
|
||||
if (isNotEmpty(settings)) {
|
||||
this.cookieService.set(ACCESSIBILITY_COOKIE, settings, { expires: this.appConfig.accessibility.cookieExpirationDuration });
|
||||
} else {
|
||||
this.cookieService.remove(ACCESSIBILITY_COOKIE);
|
||||
}
|
||||
|
||||
return 'cookie';
|
||||
} else {
|
||||
return 'failed';
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all settings in the cookie and attempts to clear settings in metadata.
|
||||
* Emits true if settings in metadata were cleared and false otherwise.
|
||||
* Emits an array mentioning which settings succeeded or failed.
|
||||
*/
|
||||
clearSettings(): Observable<boolean> {
|
||||
this.setSettingsInCookie({});
|
||||
return this.setSettingsInAuthenticatedUserMetadata({});
|
||||
clearSettings(): Observable<['cookie' | 'failed', 'metadata' | 'failed']> {
|
||||
return combineLatest([
|
||||
this.setSettingsInCookie({}),
|
||||
this.setSettingsInAuthenticatedUserMetadata({}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the placeholder to be used for the provided AccessibilitySetting.
|
||||
* Returns an empty string when no placeholder is specified for the provided setting.
|
||||
* Retrieve the default value to be used for the provided AccessibilitySetting.
|
||||
* Returns an empty string when no default value is specified for the provided setting.
|
||||
*/
|
||||
getPlaceholder(setting: AccessibilitySetting): string {
|
||||
getDefaultValue(setting: AccessibilitySetting): string {
|
||||
switch (setting) {
|
||||
case 'notificationTimeOut':
|
||||
return millisecondsToSeconds(environment.notifications.timeOut.toString());
|
||||
@@ -287,6 +317,28 @@ export class AccessibilitySettingsService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the provided AccessibilitySetting is valid in regard to the provided formValues.
|
||||
*/
|
||||
isValid(setting: AccessibilitySetting, formValues: AccessibilitySettingsFormValues): boolean {
|
||||
switch (setting) {
|
||||
case 'notificationTimeOut':
|
||||
return formValues.notificationTimeOutEnabled ?
|
||||
hasNoValue(formValues.notificationTimeOut) || parseFloat(formValues.notificationTimeOut) > 0 :
|
||||
true;
|
||||
case 'liveRegionTimeOut':
|
||||
return hasNoValue(formValues.liveRegionTimeOut) || parseFloat(formValues.liveRegionTimeOut) > 0;
|
||||
default:
|
||||
throw new Error(`Unhandled accessibility setting during validity check: ${setting}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all settings in the provided AccessibilitySettingsFormValues object are valid
|
||||
*/
|
||||
formValuesValid(formValues: AccessibilitySettingsFormValues) {
|
||||
return accessibilitySettingKeys.every(setting => this.isValid(setting, formValues));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -314,3 +366,11 @@ function millisecondsToSeconds(millisecondsStr: string): string {
|
||||
return (milliseconds / 1000).toString();
|
||||
}
|
||||
}
|
||||
|
||||
function ofMetadata(): Observable<'metadata'> {
|
||||
return of('metadata');
|
||||
}
|
||||
|
||||
function ofFailed(): Observable<'failed'> {
|
||||
return of('failed');
|
||||
}
|
||||
|
@@ -3,11 +3,11 @@
|
||||
|
||||
<form>
|
||||
<div class="form-group row">
|
||||
<label [for]="'disableNotificationTimeOutInput'" class="col-sm-2 col-form-label">
|
||||
<label [for]="'disableNotificationTimeOutInput'" class="col-sm-4 col-form-label">
|
||||
{{ 'info.accessibility-settings.disableNotificationTimeOut.label' | translate }}
|
||||
</label>
|
||||
|
||||
<div class="col-sm-5">
|
||||
<div class="col-sm-4">
|
||||
<ui-switch [id]="'disableNotificationTimeOutInput'"
|
||||
[(ngModel)]="formValues.notificationTimeOutEnabled"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
@@ -24,21 +24,22 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label [for]="'notificationTimeOutInput'" class="col-sm-2 col-form-label">
|
||||
<label [for]="'notificationTimeOutInput'" class="col-sm-4 col-form-label">
|
||||
{{ 'info.accessibility-settings.notificationTimeOut.label' | translate }}
|
||||
</label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<input [type]="'number'" [id]="'notificationTimeOutInput'" class="form-control"
|
||||
[placeholder]="getPlaceholder('notificationTimeOut')"
|
||||
[ngClass]="{'is-invalid': !settingsService.isValid('notificationTimeOut', formValues)}"
|
||||
[min]="1"
|
||||
[readOnly]="!formValues.notificationTimeOutEnabled"
|
||||
[(ngModel)]="formValues.notificationTimeOut" [ngModelOptions]="{ standalone: true }"
|
||||
[attr.aria-describedby]="'notificationTimeOutHint'">
|
||||
<div class="invalid-feedback" [ngClass]="{ 'd-block': !settingsService.isValid('notificationTimeOut', formValues) }">
|
||||
{{ 'info.accessibility-settings.notificationTimeOut.invalid' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-1 col-form-label">
|
||||
{{ 'info.accessibility-settings.notificationTimeOut.unit' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-1" *dsContextHelp="{
|
||||
content: 'info.accessibility-settings.notificationTimeOut.hint',
|
||||
@@ -50,20 +51,21 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label [for]="'liveRegionTimeOutInput'" class="col-sm-2 col-form-label">
|
||||
<label [for]="'liveRegionTimeOutInput'" class="col-sm-4 col-form-label">
|
||||
{{ 'info.accessibility-settings.liveRegionTimeOut.label' | translate }}
|
||||
</label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<input [type]="'number'" [id]="'liveRegionTimeOutInput'" class="form-control"
|
||||
[placeholder]="getPlaceholder('liveRegionTimeOut')"
|
||||
[ngClass]="{'is-invalid': !settingsService.isValid('liveRegionTimeOut', formValues)}"
|
||||
[min]="1"
|
||||
[(ngModel)]="formValues.liveRegionTimeOut" [ngModelOptions]="{ standalone: true }"
|
||||
[attr.aria-describedby]="'liveRegionTimeOutHint'">
|
||||
<div class="invalid-feedback" [ngClass]="{ 'd-block': !settingsService.isValid('liveRegionTimeOut', formValues) }">
|
||||
{{ 'info.accessibility-settings.liveRegionTimeOut.invalid' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-1 col-form-label">
|
||||
{{ 'info.accessibility-settings.liveRegionTimeOut.unit' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="col-sm-1" *dsContextHelp="{
|
||||
content: 'info.accessibility-settings.liveRegionTimeOut.hint',
|
||||
@@ -78,11 +80,15 @@
|
||||
<button type="submit" (click)="saveSettings()" class="btn btn-primary mr-2">
|
||||
{{ 'info.accessibility-settings.submit' | translate }}
|
||||
</button>
|
||||
<button type="reset" (click)="resetSettings()" class="btn btn-warning">
|
||||
<button (click)="resetSettings()" class="btn btn-warning">
|
||||
{{ 'info.accessibility-settings.reset' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<div *ngIf="(isAuthenticated | async) === false && (cookieIsAccepted | async) === false" class="mt-2">
|
||||
<ds-alert [type]="AlertType.Warning">{{ 'info.accessibility-settings.cookie-warning' | translate }}</ds-alert>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@@ -11,6 +11,8 @@ import { ContextHelpDirective } from 'src/app/shared/context-help.directive';
|
||||
import { AccessibilitySettingsService } from '../../accessibility/accessibility-settings.service';
|
||||
import { getAccessibilitySettingsServiceStub } from '../../accessibility/accessibility-settings.service.stub';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { KlaroService } from '../../shared/cookies/klaro.service';
|
||||
import { KlaroServiceStub } from '../../shared/cookies/klaro.service.stub';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { AuthServiceStub } from '../../shared/testing/auth-service.stub';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||
@@ -24,11 +26,13 @@ describe('AccessibilitySettingsComponent', () => {
|
||||
let authService: AuthServiceStub;
|
||||
let settingsService: AccessibilitySettingsService;
|
||||
let notificationsService: NotificationsServiceStub;
|
||||
let klaroService: KlaroServiceStub;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
authService = new AuthServiceStub();
|
||||
settingsService = getAccessibilitySettingsServiceStub();
|
||||
notificationsService = new NotificationsServiceStub();
|
||||
klaroService = new KlaroServiceStub();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot()],
|
||||
@@ -36,6 +40,7 @@ describe('AccessibilitySettingsComponent', () => {
|
||||
{ provide: AuthService, useValue: authService },
|
||||
{ provide: AccessibilitySettingsService, useValue: settingsService },
|
||||
{ provide: NotificationsService, useValue: notificationsService },
|
||||
{ provide: KlaroService, useValue: klaroService },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).overrideComponent(AccessibilitySettingsComponent, {
|
||||
@@ -83,5 +88,11 @@ describe('AccessibilitySettingsComponent', () => {
|
||||
component.saveSettings();
|
||||
expect(notificationsService.success).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should give the user a notification mentioning why saving failed, if it failed', () => {
|
||||
settingsService.setSettings = jasmine.createSpy('setSettings').and.returnValue(of('failed'));
|
||||
component.saveSettings();
|
||||
expect(notificationsService.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
@@ -9,7 +10,16 @@ import {
|
||||
TranslateService,
|
||||
} from '@ngx-translate/core';
|
||||
import { UiSwitchModule } from 'ngx-ui-switch';
|
||||
import { take } from 'rxjs';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
Subscription,
|
||||
take,
|
||||
} from 'rxjs';
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
} from 'rxjs/operators';
|
||||
import { AlertType } from 'src/app/shared/alert/alert-type';
|
||||
|
||||
import {
|
||||
AccessibilitySetting,
|
||||
@@ -17,7 +27,10 @@ import {
|
||||
AccessibilitySettingsService,
|
||||
} from '../../accessibility/accessibility-settings.service';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { AlertComponent } from '../../shared/alert/alert.component';
|
||||
import { ContextHelpDirective } from '../../shared/context-help.directive';
|
||||
import { KlaroService } from '../../shared/cookies/klaro.service';
|
||||
import { isEmpty } from '../../shared/empty.util';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
|
||||
/**
|
||||
@@ -32,27 +45,45 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
||||
FormsModule,
|
||||
UiSwitchModule,
|
||||
ContextHelpDirective,
|
||||
AlertComponent,
|
||||
],
|
||||
standalone: true,
|
||||
})
|
||||
export class AccessibilitySettingsComponent implements OnInit {
|
||||
export class AccessibilitySettingsComponent implements OnInit, OnDestroy {
|
||||
// Redeclared for use in template
|
||||
protected readonly AlertType = AlertType;
|
||||
|
||||
protected formValues: AccessibilitySettingsFormValues;
|
||||
|
||||
isAuthenticated: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
cookieIsAccepted: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
|
||||
private subscriptions: Subscription[] = [];
|
||||
|
||||
constructor(
|
||||
protected authService: AuthService,
|
||||
protected settingsService: AccessibilitySettingsService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translateService: TranslateService,
|
||||
protected klaroService: KlaroService,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.updateFormValues();
|
||||
|
||||
this.subscriptions.push(
|
||||
this.authService.isAuthenticated().pipe(distinctUntilChanged())
|
||||
.subscribe(val => this.isAuthenticated.next(val)),
|
||||
this.klaroService.getSavedPreferences().pipe(
|
||||
map(preferences => preferences?.accessibility === true),
|
||||
distinctUntilChanged(),
|
||||
).subscribe(val => this.cookieIsAccepted.next(val)),
|
||||
);
|
||||
}
|
||||
|
||||
getPlaceholder(setting: AccessibilitySetting): string {
|
||||
return this.settingsService.getPlaceholder(setting);
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,19 +91,43 @@ export class AccessibilitySettingsComponent implements OnInit {
|
||||
*/
|
||||
saveSettings() {
|
||||
const formValues = this.formValues;
|
||||
const convertedValues = this.settingsService.convertFormValuesToStoredValues(formValues);
|
||||
this.settingsService.setSettings(convertedValues).pipe(take(1)).subscribe(location => {
|
||||
this.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.save-notification.' + location));
|
||||
this.updateFormValues();
|
||||
});
|
||||
|
||||
if (this.settingsService.formValuesValid(formValues)) {
|
||||
const convertedValues = this.settingsService.convertFormValuesToStoredValues(formValues);
|
||||
this.settingsService.setSettings(convertedValues).pipe(take(1)).subscribe(location => {
|
||||
if (location !== 'failed') {
|
||||
this.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.save-notification.' + location));
|
||||
this.updateFormValues();
|
||||
} else {
|
||||
this.notificationsService.error(null, this.translateService.instant('info.accessibility-settings.failed-notification'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.notificationsService.error(
|
||||
null,
|
||||
this.translateService.instant('info.accessibility-settings.invalid-form-notification'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the form values with the currently stored accessibility settings
|
||||
* Updates the form values with the currently stored accessibility settings and sets the default values for settings
|
||||
* that have no stored value.
|
||||
*/
|
||||
updateFormValues() {
|
||||
this.settingsService.getAll().pipe(take(1)).subscribe(storedSettings => {
|
||||
this.formValues = this.settingsService.convertStoredValuesToFormValues(storedSettings);
|
||||
const formValues = this.settingsService.convertStoredValuesToFormValues(storedSettings);
|
||||
|
||||
const settingsRequiringDefaultValue: AccessibilitySetting[] = ['notificationTimeOut', 'liveRegionTimeOut'];
|
||||
|
||||
for (const setting of settingsRequiringDefaultValue) {
|
||||
if (isEmpty(formValues[setting])) {
|
||||
const defaultValue = this.settingsService.getDefaultValue(setting);
|
||||
formValues[setting] = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
this.formValues = formValues;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,9 +135,13 @@ export class AccessibilitySettingsComponent implements OnInit {
|
||||
* Resets accessibility settings
|
||||
*/
|
||||
resetSettings() {
|
||||
this.settingsService.clearSettings().pipe(take(1)).subscribe(() => {
|
||||
this.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.reset-notification'));
|
||||
this.updateFormValues();
|
||||
this.settingsService.clearSettings().pipe(take(1)).subscribe(([cookieReset, metadataReset]) => {
|
||||
if (cookieReset === 'failed' && metadataReset === 'failed') {
|
||||
this.notificationsService.warning(null, this.translateService.instant('info.accessibility-settings.reset-failed'));
|
||||
} else {
|
||||
this.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.reset-notification'));
|
||||
this.updateFormValues();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { ACCESSIBILITY_COOKIE } from '../../accessibility/accessibility-settings.service';
|
||||
import {
|
||||
IMPERSONATING_COOKIE,
|
||||
REDIRECT_COOKIE,
|
||||
@@ -204,5 +205,12 @@ export const klaroConfiguration: any = {
|
||||
onDecline: `window.refreshCaptchaScript?.call()`,
|
||||
onlyOnce: true,
|
||||
},
|
||||
{
|
||||
name: 'accessibility',
|
||||
purposes: ['functional'],
|
||||
required: false,
|
||||
cookies: [ACCESSIBILITY_COOKIE],
|
||||
onlyOnce: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
9
src/app/shared/cookies/klaro.service.stub.ts
Normal file
9
src/app/shared/cookies/klaro.service.stub.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { of } from 'rxjs';
|
||||
|
||||
export class KlaroServiceStub {
|
||||
initialize = jasmine.createSpy('initialize');
|
||||
|
||||
showSettings = jasmine.createSpy('showSettings');
|
||||
|
||||
getSavedPreferences = jasmine.createSpy('getSavedPreferences').and.returnValue(of({}));
|
||||
}
|
@@ -1618,6 +1618,10 @@
|
||||
|
||||
"cookies.consent.content-modal.service": "service",
|
||||
|
||||
"cookies.consent.app.title.accessibility": "Accessibility Settings",
|
||||
|
||||
"cookies.consent.app.description.accessibility": "Required for saving your accessibility settings locally",
|
||||
|
||||
"cookies.consent.app.title.authentication": "Authentication",
|
||||
|
||||
"cookies.consent.app.description.authentication": "Required for signing you in",
|
||||
@@ -2134,26 +2138,34 @@
|
||||
|
||||
"info.accessibility-settings.breadcrumbs": "Accessibility settings",
|
||||
|
||||
"info.accessibility-settings.disableNotificationTimeOut.label": "Hide notifications automatically",
|
||||
"info.accessibility-settings.cookie-warning": "Saving the accessibility settings is currently not possible. Either log in to save the settings in user data, or accept the 'Accessibility Settings' cookie using the 'Cookie Settings' menu at the bottom of the page. Once the cookie has been accepted, you can reload the page to remove this message.",
|
||||
|
||||
"info.accessibility-settings.disableNotificationTimeOut.hint": "When this toggle is activated, notifications will remain until manually closed.",
|
||||
"info.accessibility-settings.disableNotificationTimeOut.label": "Automatically close notifications after time out",
|
||||
|
||||
"info.accessibility-settings.liveRegionTimeOut.label": "Live region time-out",
|
||||
"info.accessibility-settings.disableNotificationTimeOut.hint": "When this toggle is activated, notifications will close automatically after the time out passes. When deactivated, notifications will remain open untill manually closed.",
|
||||
|
||||
"info.accessibility-settings.liveRegionTimeOut.hint": "The duration after which a message in the live region disappears.",
|
||||
"info.accessibility-settings.failed-notification": "Failed to save accessibility settings",
|
||||
|
||||
"info.accessibility-settings.liveRegionTimeOut.unit": "Seconds",
|
||||
"info.accessibility-settings.invalid-form-notification": "Did not save. The form contains invalid values.",
|
||||
|
||||
"info.accessibility-settings.notificationTimeOut.label": "Notification time-out",
|
||||
"info.accessibility-settings.liveRegionTimeOut.label": "ARIA Live region time out (in seconds)",
|
||||
|
||||
"info.accessibility-settings.liveRegionTimeOut.hint": "The duration after which a message in the ARIA live region disappears. ARIA live regions are not visible on the page, but proivde announcements of notifications (or other actions) to screen readers.",
|
||||
|
||||
"info.accessibility-settings.liveRegionTimeOut.invalid": "Live region time out must be greater than 0",
|
||||
|
||||
"info.accessibility-settings.notificationTimeOut.label": "Notification time out (in seconds)",
|
||||
|
||||
"info.accessibility-settings.notificationTimeOut.hint": "The duration after which a notification disappears.",
|
||||
|
||||
"info.accessibility-settings.notificationTimeOut.unit": "Seconds",
|
||||
"info.accessibility-settings.notificationTimeOut.invalid": "Notification time out must be greater than 0",
|
||||
|
||||
"info.accessibility-settings.save-notification.cookie": "Successfully saved settings locally.",
|
||||
|
||||
"info.accessibility-settings.save-notification.metadata": "Successfully saved settings on the user profile.",
|
||||
|
||||
"info.accessibility-settings.reset-failed": "Failed to reset. Either log in or accept the 'Accessibility Settings' cookie.",
|
||||
|
||||
"info.accessibility-settings.reset-notification": "Successfully reset settings.",
|
||||
|
||||
"info.accessibility-settings.reset": "Reset accessibility settings",
|
||||
|
Reference in New Issue
Block a user