mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-15 14:03:06 +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 { Injectable } from '@angular/core';
|
||||||
import { Observable, of, switchMap } from 'rxjs';
|
import { Observable, of, switchMap, combineLatest } from 'rxjs';
|
||||||
import { map, take } from 'rxjs/operators';
|
import { map, take } from 'rxjs/operators';
|
||||||
import { CookieService } from '../core/services/cookie.service';
|
import { CookieService } from '../core/services/cookie.service';
|
||||||
import { hasValue, isNotEmpty, hasNoValue } from '../shared/empty.util';
|
import { hasValue, isNotEmpty, hasNoValue } from '../shared/empty.util';
|
||||||
@@ -10,6 +10,7 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
|||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
|
import { KlaroService } from '../shared/cookies/klaro.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the cookie used to store the settings locally
|
* Name of the cookie used to store the settings locally
|
||||||
@@ -62,6 +63,7 @@ export class AccessibilitySettingsService {
|
|||||||
protected cookieService: CookieService,
|
protected cookieService: CookieService,
|
||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
protected ePersonService: EPersonDataService,
|
protected ePersonService: EPersonDataService,
|
||||||
|
protected klaroService: KlaroService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,8 +127,9 @@ export class AccessibilitySettingsService {
|
|||||||
*
|
*
|
||||||
* Returns 'cookie' when the changes were stored in the cookie.
|
* Returns 'cookie' when the changes were stored in the cookie.
|
||||||
* Returns 'metadata' when the changes were stored in metadata.
|
* 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 });
|
return this.updateSettings({ [setting]: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,18 +140,15 @@ export class AccessibilitySettingsService {
|
|||||||
*
|
*
|
||||||
* Returns 'cookie' when the changes were stored in the cookie.
|
* Returns 'cookie' when the changes were stored in the cookie.
|
||||||
* Returns 'metadata' when the changes were stored in metadata.
|
* 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(
|
return this.setSettingsInAuthenticatedUserMetadata(settings).pipe(
|
||||||
take(1),
|
take(1),
|
||||||
map((succeeded) => {
|
map(saveLocation => saveLocation === 'metadata'),
|
||||||
if (!succeeded) {
|
switchMap((savedInMetadata) =>
|
||||||
this.setSettingsInCookie(settings);
|
savedInMetadata ? ofMetadata() : this.setSettingsInCookie(settings)
|
||||||
return 'cookie';
|
),
|
||||||
} else {
|
|
||||||
return 'metadata';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,8 +158,9 @@ export class AccessibilitySettingsService {
|
|||||||
*
|
*
|
||||||
* Returns 'cookie' when the changes were stored in the cookie.
|
* Returns 'cookie' when the changes were stored in the cookie.
|
||||||
* Returns 'metadata' when the changes were stored in metadata.
|
* 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(
|
return this.getAll().pipe(
|
||||||
take(1),
|
take(1),
|
||||||
map(currentSettings => Object.assign({}, currentSettings, settings)),
|
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.
|
* 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 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(
|
return this.authService.getAuthenticatedUserFromStoreIfAuthenticated().pipe(
|
||||||
take(1),
|
take(1),
|
||||||
switchMap(user => {
|
switchMap(user => {
|
||||||
@@ -181,7 +182,7 @@ export class AccessibilitySettingsService {
|
|||||||
const clonedUser = cloneDeep(user);
|
const clonedUser = cloneDeep(user);
|
||||||
return this.setSettingsInMetadata(clonedUser, settings);
|
return this.setSettingsInMetadata(clonedUser, settings);
|
||||||
} else {
|
} else {
|
||||||
return of(false);
|
return ofFailed();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -194,7 +195,7 @@ export class AccessibilitySettingsService {
|
|||||||
setSettingsInMetadata(
|
setSettingsInMetadata(
|
||||||
user: EPerson,
|
user: EPerson,
|
||||||
settings: AccessibilitySettings,
|
settings: AccessibilitySettings,
|
||||||
): Observable<boolean> {
|
): Observable<'metadata' | 'failed'> {
|
||||||
if (isNotEmpty(settings)) {
|
if (isNotEmpty(settings)) {
|
||||||
user.setMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY, null, JSON.stringify(settings));
|
user.setMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY, null, JSON.stringify(settings));
|
||||||
} else {
|
} else {
|
||||||
@@ -206,28 +207,42 @@ export class AccessibilitySettingsService {
|
|||||||
switchMap(operations =>
|
switchMap(operations =>
|
||||||
isNotEmpty(operations) ? this.ePersonService.patch(user, operations) : createSuccessfulRemoteDataObject$({})),
|
isNotEmpty(operations) ? this.ePersonService.patch(user, operations) : createSuccessfulRemoteDataObject$({})),
|
||||||
getFirstCompletedRemoteData(),
|
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) {
|
setSettingsInCookie(settings: AccessibilitySettings): Observable<'cookie' | 'failed'> {
|
||||||
|
return this.klaroService.getSavedPreferences().pipe(
|
||||||
|
map(preferences => preferences.accessibility),
|
||||||
|
map((accessibilityCookieAccepted: boolean) => {
|
||||||
|
if (accessibilityCookieAccepted) {
|
||||||
if (isNotEmpty(settings)) {
|
if (isNotEmpty(settings)) {
|
||||||
this.cookieService.set(ACCESSIBILITY_COOKIE, settings, { expires: environment.accessibility.cookieExpirationDuration });
|
this.cookieService.set(ACCESSIBILITY_COOKIE, settings, { expires: environment.accessibility.cookieExpirationDuration });
|
||||||
} else {
|
} else {
|
||||||
this.cookieService.remove(ACCESSIBILITY_COOKIE);
|
this.cookieService.remove(ACCESSIBILITY_COOKIE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 'cookie';
|
||||||
|
} else {
|
||||||
|
return 'failed';
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all settings in the cookie and attempts to clear settings in metadata.
|
* 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> {
|
clearSettings(): Observable<['cookie' | 'failed', 'metadata' | 'failed']> {
|
||||||
this.setSettingsInCookie({});
|
return combineLatest([
|
||||||
return this.setSettingsInAuthenticatedUserMetadata({});
|
this.setSettingsInCookie({}),
|
||||||
|
this.setSettingsInAuthenticatedUserMetadata({}),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -323,3 +338,11 @@ function millisecondsToSeconds(millisecondsStr: string): string {
|
|||||||
return (milliseconds / 1000).toString();
|
return (milliseconds / 1000).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ofMetadata(): Observable<'metadata'> {
|
||||||
|
return of('metadata');
|
||||||
|
}
|
||||||
|
|
||||||
|
function ofFailed(): Observable<'failed'> {
|
||||||
|
return of('failed');
|
||||||
|
}
|
||||||
|
@@ -87,4 +87,8 @@
|
|||||||
|
|
||||||
</form>
|
</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>
|
</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 { AuthService } from '../../core/auth/auth.service';
|
||||||
import {
|
import {
|
||||||
AccessibilitySetting,
|
AccessibilitySetting,
|
||||||
AccessibilitySettingsService,
|
AccessibilitySettingsService,
|
||||||
AccessibilitySettingsFormValues,
|
AccessibilitySettingsFormValues,
|
||||||
} from '../../accessibility/accessibility-settings.service';
|
} 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 { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { isEmpty } from 'src/app/shared/empty.util';
|
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.
|
* 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',
|
selector: 'ds-accessibility-settings',
|
||||||
templateUrl: './accessibility-settings.component.html'
|
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;
|
protected formValues: AccessibilitySettingsFormValues;
|
||||||
|
|
||||||
|
isAuthenticated: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
cookieIsAccepted: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
|
||||||
|
private subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
protected settingsService: AccessibilitySettingsService,
|
protected settingsService: AccessibilitySettingsService,
|
||||||
protected notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
protected translateService: TranslateService,
|
protected translateService: TranslateService,
|
||||||
|
protected klaroService: KlaroService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.updateFormValues();
|
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)) {
|
if (this.settingsService.allValid(convertedValues)) {
|
||||||
this.settingsService.setSettings(convertedValues).pipe(take(1)).subscribe(location => {
|
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.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.save-notification.' + location));
|
||||||
this.updateFormValues();
|
this.updateFormValues();
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(null, this.translateService.instant('info.accessibility-settings.failed-notification'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.error(
|
this.notificationsService.error(
|
||||||
@@ -78,9 +105,13 @@ export class AccessibilitySettingsComponent implements OnInit {
|
|||||||
* Resets accessibility settings
|
* Resets accessibility settings
|
||||||
*/
|
*/
|
||||||
resetSettings() {
|
resetSettings() {
|
||||||
this.settingsService.clearSettings().pipe(take(1)).subscribe(() => {
|
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.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.reset-notification'));
|
||||||
this.updateFormValues();
|
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 { IMPERSONATING_COOKIE, REDIRECT_COOKIE } from '../../core/auth/auth.service';
|
||||||
import { LANG_COOKIE } from '../../core/locale/locale.service';
|
import { LANG_COOKIE } from '../../core/locale/locale.service';
|
||||||
import { CAPTCHA_COOKIE, CAPTCHA_NAME } from '../../core/google-recaptcha/google-recaptcha.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
|
* Cookie for has_agreed_end_user
|
||||||
@@ -189,6 +190,13 @@ export const klaroConfiguration: any = {
|
|||||||
onAccept: `window.refreshCaptchaScript?.call()`,
|
onAccept: `window.refreshCaptchaScript?.call()`,
|
||||||
onDecline: `window.refreshCaptchaScript?.call()`,
|
onDecline: `window.refreshCaptchaScript?.call()`,
|
||||||
onlyOnce: true,
|
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.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.title.authentication": "Authentication",
|
||||||
|
|
||||||
"cookies.consent.app.description.authentication": "Required for signing you in",
|
"cookies.consent.app.description.authentication": "Required for signing you in",
|
||||||
@@ -1844,10 +1848,14 @@
|
|||||||
|
|
||||||
"info.accessibility-settings.breadcrumbs": "Accessibility settings",
|
"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.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.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.invalid-form-notification": "Did not save. The form contains invalid values.",
|
||||||
|
|
||||||
"info.accessibility-settings.liveRegionTimeOut.label": "ARIA Live region time out (in seconds)",
|
"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.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-notification": "Successfully reset settings.",
|
||||||
|
|
||||||
"info.accessibility-settings.reset": "Reset accessibility settings",
|
"info.accessibility-settings.reset": "Reset accessibility settings",
|
||||||
|
Reference in New Issue
Block a user