119602: Add 'Accessibility Settings' Klaro option & respect user choice

This commit is contained in:
Andreas Awouters
2025-02-18 10:22:59 +01:00
parent e838873235
commit b837f63b7c
5 changed files with 113 additions and 37 deletions

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { Observable, of, switchMap } from 'rxjs';
import { Observable, of, switchMap, combineLatest } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { CookieService } from '../core/services/cookie.service';
import { hasValue, isNotEmpty, hasNoValue } from '../shared/empty.util';
@@ -10,6 +10,7 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators';
import cloneDeep from 'lodash/cloneDeep';
import { environment } from '../../environments/environment';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
import { KlaroService } from '../shared/cookies/klaro.service';
/**
* Name of the cookie used to store the settings locally
@@ -62,6 +63,7 @@ export class AccessibilitySettingsService {
protected cookieService: CookieService,
protected authService: AuthService,
protected ePersonService: EPersonDataService,
protected klaroService: KlaroService,
) {
}
@@ -125,8 +127,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 });
}
@@ -137,18 +140,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)
),
);
}
@@ -158,8 +158,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)),
@@ -170,9 +171,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 => {
@@ -181,7 +182,7 @@ export class AccessibilitySettingsService {
const clonedUser = cloneDeep(user);
return this.setSettingsInMetadata(clonedUser, settings);
} else {
return of(false);
return ofFailed();
}
})
);
@@ -194,7 +195,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 {
@@ -206,28 +207,42 @@ 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: environment.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({}),
]);
}
/**
@@ -323,3 +338,11 @@ function millisecondsToSeconds(millisecondsStr: string): string {
return (milliseconds / 1000).toString();
}
}
function ofMetadata(): Observable<'metadata'> {
return of('metadata');
}
function ofFailed(): Observable<'failed'> {
return of('failed');
}