Merge branch 'accessibility-settings-7.6' into accessibility-settings-main

# 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/info/info.module.ts
#	src/app/shared/live-region/live-region.service.ts
#	src/app/shared/notifications/notifications-board/notifications-board.component.ts
This commit is contained in:
Andreas Awouters
2024-12-11 10:45:46 +01:00
13 changed files with 268 additions and 109 deletions

View File

@@ -17,7 +17,6 @@ import { AuthServiceStub } from '../shared/testing/auth-service.stub';
import { import {
ACCESSIBILITY_COOKIE, ACCESSIBILITY_COOKIE,
ACCESSIBILITY_SETTINGS_METADATA_KEY, ACCESSIBILITY_SETTINGS_METADATA_KEY,
AccessibilitySetting,
AccessibilitySettings, AccessibilitySettings,
AccessibilitySettingsService, AccessibilitySettingsService,
} from './accessibility-settings.service'; } from './accessibility-settings.service';
@@ -48,17 +47,6 @@ describe('accessibilitySettingsService', () => {
); );
}); });
describe('getALlAccessibilitySettingsKeys', () => {
it('should return an array containing all accessibility setting names', () => {
const settingNames: AccessibilitySetting[] = [
AccessibilitySetting.NotificationTimeOut,
AccessibilitySetting.LiveRegionTimeOut,
];
expect(service.getAllAccessibilitySettingKeys()).toEqual(settingNames);
});
});
describe('get', () => { describe('get', () => {
it('should return the setting if it is set', () => { it('should return the setting if it is set', () => {
const settings: AccessibilitySettings = { const settings: AccessibilitySettings = {
@@ -67,7 +55,7 @@ describe('accessibilitySettingsService', () => {
service.getAll = jasmine.createSpy('getAll').and.returnValue(of(settings)); service.getAll = jasmine.createSpy('getAll').and.returnValue(of(settings));
service.get(AccessibilitySetting.NotificationTimeOut, 'default').subscribe(value => service.get('notificationTimeOut', 'default').subscribe(value =>
expect(value).toEqual('1000'), expect(value).toEqual('1000'),
); );
}); });
@@ -79,7 +67,7 @@ describe('accessibilitySettingsService', () => {
service.getAll = jasmine.createSpy('getAll').and.returnValue(of(settings)); service.getAll = jasmine.createSpy('getAll').and.returnValue(of(settings));
service.get(AccessibilitySetting.LiveRegionTimeOut, 'default').subscribe(value => service.get('liveRegionTimeOut', 'default').subscribe(value =>
expect(value).toEqual('default'), expect(value).toEqual('default'),
); );
}); });
@@ -89,7 +77,7 @@ describe('accessibilitySettingsService', () => {
it('should return the setting as number if the value for the setting can be parsed to a number', () => { it('should return the setting as number if the value for the setting can be parsed to a number', () => {
service.get = jasmine.createSpy('get').and.returnValue(of('1000')); service.get = jasmine.createSpy('get').and.returnValue(of('1000'));
service.getAsNumber(AccessibilitySetting.NotificationTimeOut).subscribe(value => service.getAsNumber('notificationTimeOut').subscribe(value =>
expect(value).toEqual(1000), expect(value).toEqual(1000),
); );
}); });
@@ -97,7 +85,7 @@ describe('accessibilitySettingsService', () => {
it('should return the default value if no value is set for the setting', () => { it('should return the default value if no value is set for the setting', () => {
service.get = jasmine.createSpy('get').and.returnValue(of(null)); service.get = jasmine.createSpy('get').and.returnValue(of(null));
service.getAsNumber(AccessibilitySetting.NotificationTimeOut, 123).subscribe(value => service.getAsNumber('notificationTimeOut', 123).subscribe(value =>
expect(value).toEqual(123), expect(value).toEqual(123),
); );
}); });
@@ -105,7 +93,7 @@ describe('accessibilitySettingsService', () => {
it('should return the default value if the value for the setting can not be parsed to a number', () => { it('should return the default value if the value for the setting can not be parsed to a number', () => {
service.get = jasmine.createSpy('get').and.returnValue(of('text')); service.get = jasmine.createSpy('get').and.returnValue(of('text'));
service.getAsNumber(AccessibilitySetting.NotificationTimeOut, 123).subscribe(value => service.getAsNumber('notificationTimeOut', 123).subscribe(value =>
expect(value).toEqual(123), expect(value).toEqual(123),
); );
}); });
@@ -183,7 +171,7 @@ describe('accessibilitySettingsService', () => {
it('should correctly update the chosen setting', () => { it('should correctly update the chosen setting', () => {
service.updateSettings = jasmine.createSpy('updateSettings'); service.updateSettings = jasmine.createSpy('updateSettings');
service.set(AccessibilitySetting.LiveRegionTimeOut, '500'); service.set('liveRegionTimeOut', '500');
expect(service.updateSettings).toHaveBeenCalledWith({ liveRegionTimeOut: '500' }); expect(service.updateSettings).toHaveBeenCalledWith({ liveRegionTimeOut: '500' });
}); });
}); });
@@ -314,7 +302,7 @@ describe('accessibilitySettingsService', () => {
}); });
it('should set the settings in metadata', () => { it('should set the settings in metadata', () => {
service.setSettingsInMetadata(ePerson, { [AccessibilitySetting.LiveRegionTimeOut]: '500' }).subscribe(); service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' }).subscribe();
expect(ePerson.setMetadata).toHaveBeenCalled(); expect(ePerson.setMetadata).toHaveBeenCalled();
}); });
@@ -325,19 +313,19 @@ describe('accessibilitySettingsService', () => {
}); });
it('should create a patch with the metadata changes', () => { it('should create a patch with the metadata changes', () => {
service.setSettingsInMetadata(ePerson, { [AccessibilitySetting.LiveRegionTimeOut]: '500' }).subscribe(); service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' }).subscribe();
expect(ePersonService.createPatchFromCache).toHaveBeenCalled(); expect(ePersonService.createPatchFromCache).toHaveBeenCalled();
}); });
it('should send the patch request', () => { it('should send the patch request', () => {
service.setSettingsInMetadata(ePerson, { [AccessibilitySetting.LiveRegionTimeOut]: '500' }).subscribe(); service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' }).subscribe();
expect(ePersonService.patch).toHaveBeenCalled(); expect(ePersonService.patch).toHaveBeenCalled();
}); });
it('should emit true when the update succeeded', fakeAsync(() => { it('should emit true when the update succeeded', fakeAsync(() => {
ePersonService.patch = jasmine.createSpy().and.returnValue(createSuccessfulRemoteDataObject$({})); ePersonService.patch = jasmine.createSpy().and.returnValue(createSuccessfulRemoteDataObject$({}));
service.setSettingsInMetadata(ePerson, { [AccessibilitySetting.LiveRegionTimeOut]: '500' }) service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' })
.subscribe(value => { .subscribe(value => {
expect(value).toBeTrue(); expect(value).toBeTrue();
}); });
@@ -348,7 +336,7 @@ describe('accessibilitySettingsService', () => {
it('should emit false when the update failed', fakeAsync(() => { it('should emit false when the update failed', fakeAsync(() => {
ePersonService.patch = jasmine.createSpy().and.returnValue(createFailedRemoteDataObject$()); ePersonService.patch = jasmine.createSpy().and.returnValue(createFailedRemoteDataObject$());
service.setSettingsInMetadata(ePerson, { [AccessibilitySetting.LiveRegionTimeOut]: '500' }) service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' })
.subscribe(value => { .subscribe(value => {
expect(value).toBeFalse(); expect(value).toBeFalse();
}); });
@@ -364,7 +352,7 @@ describe('accessibilitySettingsService', () => {
}); });
it('should store the settings in a cookie', () => { it('should store the settings in a cookie', () => {
service.setSettingsInCookie({ [AccessibilitySetting.LiveRegionTimeOut]: '500' }); service.setSettingsInCookie({ ['liveRegionTimeOut']: '500' });
expect(cookieService.set).toHaveBeenCalled(); expect(cookieService.set).toHaveBeenCalled();
}); });
@@ -375,12 +363,4 @@ describe('accessibilitySettingsService', () => {
}); });
}); });
describe('getInputType', () => {
it('should correctly return the input type', () => {
expect(service.getInputType(AccessibilitySetting.NotificationTimeOut)).toEqual('number');
expect(service.getInputType(AccessibilitySetting.LiveRegionTimeOut)).toEqual('number');
expect(service.getInputType('unknownValue' as AccessibilitySetting)).toEqual('text');
});
});
}); });

View File

@@ -32,4 +32,10 @@ export class AccessibilitySettingsServiceStub {
setSettingsInCookie = jasmine.createSpy('setSettingsInCookie'); setSettingsInCookie = jasmine.createSpy('setSettingsInCookie');
getInputType = jasmine.createSpy('getInputType').and.returnValue('text'); getInputType = jasmine.createSpy('getInputType').and.returnValue('text');
convertFormValuesToStoredValues = jasmine.createSpy('convertFormValuesToStoredValues').and.returnValue({});
convertStoredValuesToFormValues = jasmine.createSpy('convertStoredValuesToFormValues').and.returnValue({});
getPlaceholder = jasmine.createSpy('getPlaceholder').and.returnValue('placeholder');
} }

View File

@@ -19,8 +19,8 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators';
import { import {
hasValue, hasValue,
isNotEmpty, isNotEmpty,
isNotEmptyOperator,
} from '../shared/empty.util'; } from '../shared/empty.util';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
/** /**
* Name of the cookie used to store the settings locally * Name of the cookie used to store the settings locally
@@ -33,16 +33,30 @@ export const ACCESSIBILITY_COOKIE = 'dsAccessibilityCookie';
export const ACCESSIBILITY_SETTINGS_METADATA_KEY = 'dspace.accessibility.settings'; export const ACCESSIBILITY_SETTINGS_METADATA_KEY = 'dspace.accessibility.settings';
/** /**
* Enum containing all possible accessibility settings. * Type containing all possible accessibility settings.
* When adding new settings, the {@link AccessibilitySettingsService#getInputType} method and the i18n keys for the * When adding new settings, make sure to add the new setting to the accessibility-settings component form.
* accessibility settings page should be updated. * The converter methods to convert from stored format to form format (and vice-versa) need to be updated as well.
*/ */
export enum AccessibilitySetting { export type AccessibilitySetting = 'notificationTimeOut' | 'liveRegionTimeOut';
NotificationTimeOut = 'notificationTimeOut',
LiveRegionTimeOut = 'liveRegionTimeOut',
}
export type AccessibilitySettings = { [key in AccessibilitySetting]?: any }; /**
* Type representing an object that contains accessibility settings values for all accessibility settings.
*/
export type FullAccessibilitySettings = { [key in AccessibilitySetting]: string };
/**
* Type representing an object that contains accessibility settings values for some accessibility settings.
*/
export type AccessibilitySettings = Partial<FullAccessibilitySettings>;
/**
* The accessibility settings object format used by the accessibility-settings component form.
*/
export interface AccessibilitySettingsFormValues {
notificationTimeOutEnabled: boolean,
notificationTimeOut: string,
liveRegionTimeOut: string,
}
/** /**
* Service handling the retrieval and configuration of accessibility settings. * Service handling the retrieval and configuration of accessibility settings.
@@ -62,10 +76,6 @@ export class AccessibilitySettingsService {
) { ) {
} }
getAllAccessibilitySettingKeys(): AccessibilitySetting[] {
return Object.entries(AccessibilitySetting).map(([_, val]) => val);
}
/** /**
* Get the stored value for the provided {@link AccessibilitySetting}. If the value does not exist or if it is empty, * Get the stored value for the provided {@link AccessibilitySetting}. If the value does not exist or if it is empty,
* the provided defaultValue is emitted instead. * the provided defaultValue is emitted instead.
@@ -204,8 +214,8 @@ export class AccessibilitySettingsService {
return this.ePersonService.createPatchFromCache(user).pipe( return this.ePersonService.createPatchFromCache(user).pipe(
take(1), take(1),
isNotEmptyOperator(), switchMap(operations =>
switchMap(operations => this.ePersonService.patch(user, operations)), isNotEmpty(operations) ? this.ePersonService.patch(user, operations) : createSuccessfulRemoteDataObject$({})),
getFirstCompletedRemoteData(), getFirstCompletedRemoteData(),
map(rd => rd.hasSucceeded), map(rd => rd.hasSucceeded),
); );
@@ -223,17 +233,75 @@ export class AccessibilitySettingsService {
} }
/** /**
* Returns the input type that a form should use for the provided {@link AccessibilitySetting} * Clears all settings in the cookie and attempts to clear settings in metadata.
* Emits true if settings in metadata were cleared and false otherwise.
*/ */
getInputType(setting: AccessibilitySetting): string { clearSettings(): Observable<boolean> {
this.setSettingsInCookie({});
return 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.
*/
getPlaceholder(setting: AccessibilitySetting): string {
switch (setting) { switch (setting) {
case AccessibilitySetting.NotificationTimeOut: case 'notificationTimeOut':
return 'number'; return millisecondsToSeconds(environment.notifications.timeOut.toString());
case AccessibilitySetting.LiveRegionTimeOut: case 'liveRegionTimeOut':
return 'number'; return millisecondsToSeconds(environment.liveRegion.messageTimeOutDurationMs.toString());
default: default:
return 'text'; return '';
} }
} }
/**
* Convert values in the provided accessibility settings object to values ready to be stored.
*/
convertFormValuesToStoredValues(settings: AccessibilitySettingsFormValues): FullAccessibilitySettings {
return {
notificationTimeOut: settings.notificationTimeOutEnabled ?
secondsToMilliseconds(settings.notificationTimeOut) : '0',
liveRegionTimeOut: secondsToMilliseconds(settings.liveRegionTimeOut),
};
}
/**
* Convert values in the provided accessibility settings object to values ready to show in the form.
*/
convertStoredValuesToFormValues(settings: AccessibilitySettings): AccessibilitySettingsFormValues {
return {
notificationTimeOutEnabled: parseFloat(settings.notificationTimeOut) !== 0,
notificationTimeOut: millisecondsToSeconds(settings.notificationTimeOut),
liveRegionTimeOut: millisecondsToSeconds(settings.liveRegionTimeOut),
};
}
}
/**
* Converts a string representing seconds to a string representing milliseconds
* Returns null if the input could not be parsed to a float
*/
function secondsToMilliseconds(secondsStr: string): string {
const seconds = parseFloat(secondsStr);
if (isNaN(seconds)) {
return null;
} else {
return (seconds * 1000).toString();
}
}
/**
* Converts a string representing milliseconds to a string representing seconds
* Returns null if the input could not be parsed to a float
*/
function millisecondsToSeconds(millisecondsStr: string): string {
const milliseconds = parseFloat(millisecondsStr);
if (isNaN(milliseconds)) {
return null;
} else {
return (milliseconds / 1000).toString();
}
} }

View File

@@ -2,25 +2,87 @@
<h2>{{ 'info.accessibility-settings.title' | translate }}</h2> <h2>{{ 'info.accessibility-settings.title' | translate }}</h2>
<form> <form>
<div *ngFor="let setting of accessibilitySettingsOptions" class="form-group row"> <div class="form-group row">
<label [for]="setting + 'Input'" class="col-sm-2 col-form-label"> <label [for]="'disableNotificationTimeOutInput'" class="col-sm-2 col-form-label">
{{ 'info.accessibility-settings.' + setting + '.label' | translate }} {{ 'info.accessibility-settings.disableNotificationTimeOut.label' | translate }}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-5">
<input [type]="getInputType(setting)" [id]="setting + 'Input'" class="form-control" <ui-switch [id]="'disableNotificationTimeOutInput'"
[(ngModel)]="formValues[setting]" [ngModelOptions]="{ standalone: true }" [(ngModel)]="formValues.notificationTimeOutEnabled"
[attr.aria-describedby]="setting + 'Hint'"> [ngModelOptions]="{ standalone: true }"
></ui-switch>
</div>
<small [id]="setting + 'Hint'" class="form-text text-muted"> <div class="col-sm-1" *dsContextHelp="{
{{ 'info.accessibility-settings.' + setting + '.hint' | translate }} content: 'info.accessibility-settings.disableNotificationTimeOut.hint',
</small> id: 'disableNotificationTimeOutHelp',
iconPlacement: 'right',
tooltipPlacement: 'right'
}">
</div> </div>
</div> </div>
<button type="submit" (click)="saveSettings()" class="btn btn-primary"> <div class="form-group row">
<label [for]="'notificationTimeOutInput'" class="col-sm-2 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')"
[readOnly]="!formValues.notificationTimeOutEnabled"
[(ngModel)]="formValues.notificationTimeOut" [ngModelOptions]="{ standalone: true }"
[attr.aria-describedby]="'notificationTimeOutHint'">
</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',
id: 'notificationTimeOutHelp',
iconPlacement: 'right',
tooltipPlacement: 'right'
}">
</div>
</div>
<div class="form-group row">
<label [for]="'liveRegionTimeOutInput'" class="col-sm-2 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')"
[(ngModel)]="formValues.liveRegionTimeOut" [ngModelOptions]="{ standalone: true }"
[attr.aria-describedby]="'liveRegionTimeOutHint'">
</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',
id: 'liveRegionTimeOutHelp',
iconPlacement: 'right',
tooltipPlacement: 'right'
}">
</div>
</div>
<div role="group">
<button type="submit" (click)="saveSettings()" class="btn btn-primary mr-2">
{{ 'info.accessibility-settings.submit' | translate }} {{ 'info.accessibility-settings.submit' | translate }}
</button> </button>
<button type="reset" (click)="resetSettings()" class="btn btn-warning">
{{ 'info.accessibility-settings.reset' | translate }}
</button>
</div>
</form> </form>
</div> </div>

View File

@@ -6,11 +6,9 @@ import {
} from '@angular/core/testing'; } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { ContextHelpDirective } from 'src/app/shared/context-help.directive';
import { import { AccessibilitySettingsService } from '../../accessibility/accessibility-settings.service';
AccessibilitySetting,
AccessibilitySettingsService,
} from '../../accessibility/accessibility-settings.service';
import { getAccessibilitySettingsServiceStub } from '../../accessibility/accessibility-settings.service.stub'; import { getAccessibilitySettingsServiceStub } from '../../accessibility/accessibility-settings.service.stub';
import { AuthService } from '../../core/auth/auth.service'; import { AuthService } from '../../core/auth/auth.service';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
@@ -40,6 +38,10 @@ describe('AccessibilitySettingsComponent', () => {
{ provide: NotificationsService, useValue: notificationsService }, { provide: NotificationsService, useValue: notificationsService },
], ],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
}).overrideComponent(AccessibilitySettingsComponent, {
remove: {
imports: [ContextHelpDirective],
},
}).compileComponents(); }).compileComponents();
})); }));
@@ -54,19 +56,12 @@ describe('AccessibilitySettingsComponent', () => {
}); });
describe('On Init', () => { describe('On Init', () => {
it('should retrieve all accessibility settings options', () => {
expect(settingsService.getAllAccessibilitySettingKeys).toHaveBeenCalled();
});
it('should retrieve the current settings', () => { it('should retrieve the current settings', () => {
expect(settingsService.getAll).toHaveBeenCalled(); expect(settingsService.getAll).toHaveBeenCalled();
}); });
});
describe('getInputType', () => { it('should convert retrieved settings to form format', () => {
it('should retrieve the input type for the setting from the service', () => { expect(settingsService.convertStoredValuesToFormValues).toHaveBeenCalled();
component.getInputType(AccessibilitySetting.LiveRegionTimeOut);
expect(settingsService.getInputType).toHaveBeenCalledWith(AccessibilitySetting.LiveRegionTimeOut);
}); });
}); });
@@ -77,6 +72,12 @@ describe('AccessibilitySettingsComponent', () => {
expect(settingsService.setSettings).toHaveBeenCalled(); expect(settingsService.setSettings).toHaveBeenCalled();
}); });
it('should convert form settings to stored format', () => {
settingsService.setSettings = jasmine.createSpy('setSettings').and.returnValue(of('cookie'));
component.saveSettings();
expect(settingsService.convertFormValuesToStoredValues).toHaveBeenCalled();
});
it('should give the user a notification mentioning where the settings were saved', () => { it('should give the user a notification mentioning where the settings were saved', () => {
settingsService.setSettings = jasmine.createSpy('setSettings').and.returnValue(of('cookie')); settingsService.setSettings = jasmine.createSpy('setSettings').and.returnValue(of('cookie'));
component.saveSettings(); component.saveSettings();

View File

@@ -8,16 +8,21 @@ import {
TranslateModule, TranslateModule,
TranslateService, TranslateService,
} from '@ngx-translate/core'; } from '@ngx-translate/core';
import { UiSwitchModule } from 'ngx-ui-switch';
import { take } from 'rxjs'; import { take } from 'rxjs';
import { import {
AccessibilitySetting, AccessibilitySetting,
AccessibilitySettings, AccessibilitySettingsFormValues,
AccessibilitySettingsService, AccessibilitySettingsService,
} from '../../accessibility/accessibility-settings.service'; } from '../../accessibility/accessibility-settings.service';
import { AuthService } from '../../core/auth/auth.service'; import { AuthService } from '../../core/auth/auth.service';
import { ContextHelpDirective } from '../../shared/context-help.directive';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
/**
* Component providing the form where users can update accessibility settings.
*/
@Component({ @Component({
selector: 'ds-accessibility-settings', selector: 'ds-accessibility-settings',
templateUrl: './accessibility-settings.component.html', templateUrl: './accessibility-settings.component.html',
@@ -25,14 +30,14 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
CommonModule, CommonModule,
TranslateModule, TranslateModule,
FormsModule, FormsModule,
UiSwitchModule,
ContextHelpDirective,
], ],
standalone: true, standalone: true,
}) })
export class AccessibilitySettingsComponent implements OnInit { export class AccessibilitySettingsComponent implements OnInit {
protected accessibilitySettingsOptions: AccessibilitySetting[]; protected formValues: AccessibilitySettingsFormValues;
protected formValues: AccessibilitySettings = { };
constructor( constructor(
protected authService: AuthService, protected authService: AuthService,
@@ -43,19 +48,41 @@ export class AccessibilitySettingsComponent implements OnInit {
} }
ngOnInit() { ngOnInit() {
this.accessibilitySettingsOptions = this.settingsService.getAllAccessibilitySettingKeys(); this.updateFormValues();
this.settingsService.getAll().pipe(take(1)).subscribe(currentSettings => {
this.formValues = currentSettings;
});
} }
getInputType(setting: AccessibilitySetting): string { getPlaceholder(setting: AccessibilitySetting): string {
return this.settingsService.getInputType(setting); return this.settingsService.getPlaceholder(setting);
} }
/**
* Saves the user-configured settings
*/
saveSettings() { saveSettings() {
this.settingsService.setSettings(this.formValues).pipe(take(1)).subscribe(location => { 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.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.save-notification.' + location));
this.updateFormValues();
});
}
/**
* Updates the form values with the currently stored accessibility settings
*/
updateFormValues() {
this.settingsService.getAll().pipe(take(1)).subscribe(storedSettings => {
this.formValues = this.settingsService.convertStoredValuesToFormValues(storedSettings);
});
}
/**
* 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();
}); });
} }

View File

@@ -36,7 +36,7 @@
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header">{{'profile.card.accessibility.header' | translate}}</div> <div class="card-header">{{'profile.card.accessibility.header' | translate}}</div>
<div class="card-body"> <div class="card-body">
<div class="mb-1">{{'profile.card.accessibility.content' | translate}}</div> <ds-alert class="mb-4" [type]="'alert-info'">{{'profile.card.accessibility.content' | translate}}</ds-alert>
<a [routerLink]="'/info/accessibility'">{{'profile.card.accessibility.link' | translate}}</a> <a [routerLink]="'/info/accessibility'">{{'profile.card.accessibility.link' | translate}}</a>
</div> </div>
</div> </div>

View File

@@ -5,6 +5,7 @@ import {
waitForAsync, waitForAsync,
} from '@angular/core/testing'; } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { StoreModule } from '@ngrx/store'; import { StoreModule } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing'; import { provideMockStore } from '@ngrx/store/testing';
@@ -116,6 +117,7 @@ describe('ProfilePageComponent', () => {
RouterModule.forRoot([]), RouterModule.forRoot([]),
ProfilePageComponent, ProfilePageComponent,
VarDirective, VarDirective,
NoopAnimationsModule,
], ],
providers: [ providers: [
{ provide: EPersonDataService, useValue: epersonService }, { provide: EPersonDataService, useValue: epersonService },

View File

@@ -41,6 +41,7 @@ import {
getRemoteDataPayload, getRemoteDataPayload,
} from '../core/shared/operators'; } from '../core/shared/operators';
import { SuggestionsNotificationComponent } from '../notifications/suggestions-notification/suggestions-notification.component'; import { SuggestionsNotificationComponent } from '../notifications/suggestions-notification/suggestions-notification.component';
import { AlertComponent } from '../shared/alert/alert.component';
import { import {
hasValue, hasValue,
isNotEmpty, isNotEmpty,
@@ -67,6 +68,7 @@ import { ProfilePageSecurityFormComponent } from './profile-page-security-form/p
NgForOf, NgForOf,
SuggestionsNotificationComponent, SuggestionsNotificationComponent,
RouterModule, RouterModule,
AlertComponent,
], ],
standalone: true, standalone: true,
}) })

View File

@@ -9,10 +9,7 @@ import {
} from 'rxjs'; } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { import { AccessibilitySettingsService } from '../../accessibility/accessibility-settings.service';
AccessibilitySetting,
AccessibilitySettingsService,
} from '../../accessibility/accessibility-settings.service';
import { UUIDService } from '../../core/shared/uuid.service'; import { UUIDService } from '../../core/shared/uuid.service';
export const MIN_MESSAGE_DURATION = 200; export const MIN_MESSAGE_DURATION = 200;
@@ -141,7 +138,7 @@ export class LiveRegionService {
*/ */
getConfiguredMessageTimeOutMs(): Observable<number> { getConfiguredMessageTimeOutMs(): Observable<number> {
return this.accessibilitySettingsService.getAsNumber( return this.accessibilitySettingsService.getAsNumber(
AccessibilitySetting.LiveRegionTimeOut, 'liveRegionTimeOut',
this.getMessageTimeOutMs(), this.getMessageTimeOutMs(),
).pipe(map(timeOut => Math.max(timeOut, MIN_MESSAGE_DURATION))); ).pipe(map(timeOut => Math.max(timeOut, MIN_MESSAGE_DURATION)));
} }

View File

@@ -16,7 +16,7 @@ import {
Store, Store,
} from '@ngrx/store'; } from '@ngrx/store';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import difference from 'lodash/difference'; import differenceWith from 'lodash/differenceWith';
import { import {
BehaviorSubject, BehaviorSubject,
Subscription, Subscription,
@@ -24,10 +24,7 @@ import {
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { INotificationBoardOptions } from '../../../../config/notifications-config.interfaces'; import { INotificationBoardOptions } from '../../../../config/notifications-config.interfaces';
import { import { AccessibilitySettingsService } from '../../../accessibility/accessibility-settings.service';
AccessibilitySetting,
AccessibilitySettingsService,
} from '../../../accessibility/accessibility-settings.service';
import { AppState } from '../../../app.reducer'; import { AppState } from '../../../app.reducer';
import { INotification } from '../models/notification.model'; import { INotification } from '../models/notification.model';
import { NotificationComponent } from '../notification/notification.component'; import { NotificationComponent } from '../notification/notification.component';
@@ -82,13 +79,13 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy {
this.notifications = []; this.notifications = [];
} else if (state.length > this.notifications.length) { } else if (state.length > this.notifications.length) {
// Add // Add
const newElem = difference(state, this.notifications); const newElem = differenceWith(state, this.notifications, this.byId);
newElem.forEach((notification) => { newElem.forEach((notification) => {
this.add(notification); this.add(notification);
}); });
} else { } else {
// Remove // Remove
const delElem = difference(this.notifications, state); const delElem = differenceWith(this.notifications, state, this.byId);
delElem.forEach((notification) => { delElem.forEach((notification) => {
this.notifications = this.notifications.filter((item: INotification) => item.id !== notification.id); this.notifications = this.notifications.filter((item: INotification) => item.id !== notification.id);
@@ -98,6 +95,9 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy {
}); });
} }
private byId = (notificationA: INotification, notificationB: INotification) =>
notificationA.id === notificationB.id;
// Add the new notification to the notification array // Add the new notification to the notification array
add(item: INotification): void { add(item: INotification): void {
const toBlock: boolean = this.block(item); const toBlock: boolean = this.block(item);
@@ -108,7 +108,7 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy {
// It would be a bit better to handle the retrieval of configured settings in the NotificationsService. // It would be a bit better to handle the retrieval of configured settings in the NotificationsService.
// Due to circular dependencies this is difficult to implement. // Due to circular dependencies this is difficult to implement.
this.accessibilitySettingsService.getAsNumber(AccessibilitySetting.NotificationTimeOut, item.options.timeOut) this.accessibilitySettingsService.getAsNumber('notificationTimeOut', item.options.timeOut)
.pipe(take(1)).subscribe(timeOut => { .pipe(take(1)).subscribe(timeOut => {
if (timeOut < 0) { if (timeOut < 0) {
timeOut = 0; timeOut = 0;

View File

@@ -2134,18 +2134,30 @@
"info.accessibility-settings.breadcrumbs": "Accessibility settings", "info.accessibility-settings.breadcrumbs": "Accessibility settings",
"info.accessibility-settings.disableNotificationTimeOut.label": "Hide notifications automatically",
"info.accessibility-settings.disableNotificationTimeOut.hint": "When this toggle is activated, notifications will remain until manually closed.",
"info.accessibility-settings.liveRegionTimeOut.label": "Live region time-out", "info.accessibility-settings.liveRegionTimeOut.label": "Live region time-out",
"info.accessibility-settings.liveRegionTimeOut.hint": "The duration in milliseconds after which a message in the live region disappears.", "info.accessibility-settings.liveRegionTimeOut.hint": "The duration after which a message in the live region disappears.",
"info.accessibility-settings.liveRegionTimeOut.unit": "Seconds",
"info.accessibility-settings.notificationTimeOut.label": "Notification time-out", "info.accessibility-settings.notificationTimeOut.label": "Notification time-out",
"info.accessibility-settings.notificationTimeOut.hint": "The duration in milliseconds after which a notification disappears. Set to 0 for notifications to remain indefinitely.", "info.accessibility-settings.notificationTimeOut.hint": "The duration after which a notification disappears.",
"info.accessibility-settings.notificationTimeOut.unit": "Seconds",
"info.accessibility-settings.save-notification.cookie": "Successfully saved settings locally.", "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.save-notification.metadata": "Successfully saved settings on the user profile.",
"info.accessibility-settings.reset-notification": "Successfully reset settings.",
"info.accessibility-settings.reset": "Reset accessibility settings",
"info.accessibility-settings.submit": "Save accessibility settings", "info.accessibility-settings.submit": "Save accessibility settings",
"info.accessibility-settings.title": "Accessibility settings", "info.accessibility-settings.title": "Accessibility settings",
@@ -3896,7 +3908,7 @@
"profile.card.accessibility.header": "Accessibility", "profile.card.accessibility.header": "Accessibility",
"profile.card.accessibility.link": "Accessibility Settings Page", "profile.card.accessibility.link": "Go to Accessibility Settings Page",
"profile.card.identify": "Identify", "profile.card.identify": "Identify",

View File

@@ -12,6 +12,7 @@ import { ProfilePageComponent as BaseComponent } from '../../../../app/profile-p
import { ThemedProfilePageMetadataFormComponent } from '../../../../app/profile-page/profile-page-metadata-form/themed-profile-page-metadata-form.component'; import { ThemedProfilePageMetadataFormComponent } from '../../../../app/profile-page/profile-page-metadata-form/themed-profile-page-metadata-form.component';
import { ProfilePageResearcherFormComponent } from '../../../../app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component'; import { ProfilePageResearcherFormComponent } from '../../../../app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component';
import { ProfilePageSecurityFormComponent } from '../../../../app/profile-page/profile-page-security-form/profile-page-security-form.component'; import { ProfilePageSecurityFormComponent } from '../../../../app/profile-page/profile-page-security-form/profile-page-security-form.component';
import { AlertComponent } from '../../../../app/shared/alert/alert.component';
import { VarDirective } from '../../../../app/shared/utils/var.directive'; import { VarDirective } from '../../../../app/shared/utils/var.directive';
@Component({ @Component({
@@ -32,6 +33,7 @@ import { VarDirective } from '../../../../app/shared/utils/var.directive';
NgForOf, NgForOf,
SuggestionsNotificationComponent, SuggestionsNotificationComponent,
RouterModule, RouterModule,
AlertComponent,
], ],
}) })
/** /**