mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-15 22:13:02 +00:00
119602: Add 'Accessibility Settings' Klaro option & respect user choice
This commit is contained in:
@@ -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');
|
||||
}
|
||||
|
@@ -87,4 +87,8 @@
|
||||
|
||||
</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>
|
||||
|
@@ -1,14 +1,16 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import {
|
||||
AccessibilitySetting,
|
||||
AccessibilitySettingsService,
|
||||
AccessibilitySettingsFormValues,
|
||||
} from '../../accessibility/accessibility-settings.service';
|
||||
import { take } from 'rxjs';
|
||||
import { BehaviorSubject, distinctUntilChanged, map, Subscription, take } from 'rxjs';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { isEmpty } from 'src/app/shared/empty.util';
|
||||
import { AlertType } from '../../shared/alert/aletr-type';
|
||||
import { KlaroService } from '../../shared/cookies/klaro.service';
|
||||
|
||||
/**
|
||||
* Component providing the form where users can update accessibility settings.
|
||||
@@ -17,20 +19,41 @@ import { isEmpty } from 'src/app/shared/empty.util';
|
||||
selector: 'ds-accessibility-settings',
|
||||
templateUrl: './accessibility-settings.component.html'
|
||||
})
|
||||
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)),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,8 +65,12 @@ export class AccessibilitySettingsComponent implements OnInit {
|
||||
|
||||
if (this.settingsService.allValid(convertedValues)) {
|
||||
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 (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(
|
||||
@@ -78,9 +105,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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,7 @@ import { TOKENITEM } from '../../core/auth/models/auth-token-info.model';
|
||||
import { IMPERSONATING_COOKIE, REDIRECT_COOKIE } from '../../core/auth/auth.service';
|
||||
import { LANG_COOKIE } from '../../core/locale/locale.service';
|
||||
import { CAPTCHA_COOKIE, CAPTCHA_NAME } from '../../core/google-recaptcha/google-recaptcha.service';
|
||||
import { ACCESSIBILITY_COOKIE } from '../../accessibility/accessibility-settings.service';
|
||||
|
||||
/**
|
||||
* Cookie for has_agreed_end_user
|
||||
@@ -189,6 +190,13 @@ export const klaroConfiguration: any = {
|
||||
onAccept: `window.refreshCaptchaScript?.call()`,
|
||||
onDecline: `window.refreshCaptchaScript?.call()`,
|
||||
onlyOnce: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'accessibility',
|
||||
purposes: ['functional'],
|
||||
required: false,
|
||||
cookies: [ACCESSIBILITY_COOKIE],
|
||||
onlyOnce: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@@ -1350,6 +1350,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",
|
||||
@@ -1844,10 +1848,14 @@
|
||||
|
||||
"info.accessibility-settings.breadcrumbs": "Accessibility settings",
|
||||
|
||||
"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.label": "Automatically close notifications after 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.failed-notification": "Failed to save accessibility settings",
|
||||
|
||||
"info.accessibility-settings.invalid-form-notification": "Did not save. The form contains invalid values.",
|
||||
|
||||
"info.accessibility-settings.liveRegionTimeOut.label": "ARIA Live region time out (in seconds)",
|
||||
@@ -1866,6 +1874,8 @@
|
||||
|
||||
"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