mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'accessibility-settings-7.6' into accessibility-settings-8_x
# Conflicts: # src/app/core/auth/auth.service.ts # src/app/info/info-routing-paths.ts # src/app/info/info-routing.module.ts # src/app/info/info.module.ts # src/app/shared/live-region/live-region.service.spec.ts # src/app/shared/live-region/live-region.service.ts # src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts # src/app/shared/notifications/notifications-board/notifications-board.component.ts # src/config/app-config.interface.ts # src/config/default-app-config.ts
This commit is contained in:
@@ -516,3 +516,8 @@ liveRegion:
|
|||||||
messageTimeOutDurationMs: 30000
|
messageTimeOutDurationMs: 30000
|
||||||
# The visibility of the live region. Setting this to true is only useful for debugging purposes.
|
# The visibility of the live region. Setting this to true is only useful for debugging purposes.
|
||||||
isVisible: false
|
isVisible: false
|
||||||
|
|
||||||
|
# Configuration for storing accessibility settings, used by the AccessibilitySettingsService
|
||||||
|
accessibility:
|
||||||
|
# The duration in days after which the accessibility settings cookie expires
|
||||||
|
cookieExpirationDuration: 7
|
||||||
|
11
src/app/accessibility/accessibility-settings.config.ts
Normal file
11
src/app/accessibility/accessibility-settings.config.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Config } from '../../config/config.interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration interface used by the AccessibilitySettingsService
|
||||||
|
*/
|
||||||
|
export class AccessibilitySettingsConfig implements Config {
|
||||||
|
/**
|
||||||
|
* The duration in days after which the accessibility settings cookie expires
|
||||||
|
*/
|
||||||
|
cookieExpirationDuration: number;
|
||||||
|
}
|
386
src/app/accessibility/accessibility-settings.service.spec.ts
Normal file
386
src/app/accessibility/accessibility-settings.service.spec.ts
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
import {
|
||||||
|
fakeAsync,
|
||||||
|
flush,
|
||||||
|
} from '@angular/core/testing';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
|
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 { CookieServiceMock } from '../shared/mocks/cookie.service.mock';
|
||||||
|
import {
|
||||||
|
createFailedRemoteDataObject$,
|
||||||
|
createSuccessfulRemoteDataObject$,
|
||||||
|
} from '../shared/remote-data.utils';
|
||||||
|
import { AuthServiceStub } from '../shared/testing/auth-service.stub';
|
||||||
|
import {
|
||||||
|
ACCESSIBILITY_COOKIE,
|
||||||
|
ACCESSIBILITY_SETTINGS_METADATA_KEY,
|
||||||
|
AccessibilitySetting,
|
||||||
|
AccessibilitySettings,
|
||||||
|
AccessibilitySettingsService,
|
||||||
|
} from './accessibility-settings.service';
|
||||||
|
|
||||||
|
|
||||||
|
describe('accessibilitySettingsService', () => {
|
||||||
|
let service: AccessibilitySettingsService;
|
||||||
|
let cookieService: CookieServiceMock;
|
||||||
|
let authService: AuthServiceStub;
|
||||||
|
let ePersonService: EPersonDataService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cookieService = new CookieServiceMock();
|
||||||
|
authService = new AuthServiceStub();
|
||||||
|
|
||||||
|
ePersonService = jasmine.createSpyObj('ePersonService', {
|
||||||
|
createPatchFromCache: of([{
|
||||||
|
op: 'add',
|
||||||
|
value: null,
|
||||||
|
}]),
|
||||||
|
patch: of({}),
|
||||||
|
});
|
||||||
|
|
||||||
|
service = new AccessibilitySettingsService(
|
||||||
|
cookieService as unknown as CookieService,
|
||||||
|
authService as unknown as AuthService,
|
||||||
|
ePersonService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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', () => {
|
||||||
|
it('should return the setting if it is set', () => {
|
||||||
|
const settings: AccessibilitySettings = {
|
||||||
|
notificationTimeOut: '1000',
|
||||||
|
};
|
||||||
|
|
||||||
|
service.getAll = jasmine.createSpy('getAll').and.returnValue(of(settings));
|
||||||
|
|
||||||
|
service.get(AccessibilitySetting.NotificationTimeOut, 'default').subscribe(value =>
|
||||||
|
expect(value).toEqual('1000'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the default value if the setting is not set', () => {
|
||||||
|
const settings: AccessibilitySettings = {
|
||||||
|
notificationTimeOut: '1000',
|
||||||
|
};
|
||||||
|
|
||||||
|
service.getAll = jasmine.createSpy('getAll').and.returnValue(of(settings));
|
||||||
|
|
||||||
|
service.get(AccessibilitySetting.LiveRegionTimeOut, 'default').subscribe(value =>
|
||||||
|
expect(value).toEqual('default'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAsNumber', () => {
|
||||||
|
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.getAsNumber(AccessibilitySetting.NotificationTimeOut).subscribe(value =>
|
||||||
|
expect(value).toEqual(1000),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the default value if no value is set for the setting', () => {
|
||||||
|
service.get = jasmine.createSpy('get').and.returnValue(of(null));
|
||||||
|
|
||||||
|
service.getAsNumber(AccessibilitySetting.NotificationTimeOut, 123).subscribe(value =>
|
||||||
|
expect(value).toEqual(123),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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.getAsNumber(AccessibilitySetting.NotificationTimeOut, 123).subscribe(value =>
|
||||||
|
expect(value).toEqual(123),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAll', () => {
|
||||||
|
it('should attempt to get the settings from metadata first', () => {
|
||||||
|
service.getAllSettingsFromAuthenticatedUserMetadata =
|
||||||
|
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of({ }));
|
||||||
|
|
||||||
|
service.getAll().subscribe();
|
||||||
|
expect(service.getAllSettingsFromAuthenticatedUserMetadata).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attempt to get the settings from the cookie if the settings from metadata are empty', () => {
|
||||||
|
service.getAllSettingsFromAuthenticatedUserMetadata =
|
||||||
|
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of({ }));
|
||||||
|
|
||||||
|
service.getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({ });
|
||||||
|
|
||||||
|
service.getAll().subscribe();
|
||||||
|
expect(service.getAllSettingsFromCookie).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not attempt to get the settings from the cookie if the settings from metadata are not empty', () => {
|
||||||
|
const settings: AccessibilitySettings = {
|
||||||
|
notificationTimeOut: '1000',
|
||||||
|
};
|
||||||
|
|
||||||
|
service.getAllSettingsFromAuthenticatedUserMetadata =
|
||||||
|
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of(settings));
|
||||||
|
|
||||||
|
service.getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({ });
|
||||||
|
|
||||||
|
service.getAll().subscribe();
|
||||||
|
expect(service.getAllSettingsFromCookie).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty object if both are empty', () => {
|
||||||
|
service.getAllSettingsFromAuthenticatedUserMetadata =
|
||||||
|
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of({ }));
|
||||||
|
|
||||||
|
service.getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({ });
|
||||||
|
|
||||||
|
service.getAll().subscribe(value => expect(value).toEqual({}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAllSettingsFromCookie', () => {
|
||||||
|
it('should retrieve the settings from the cookie', () => {
|
||||||
|
cookieService.get = jasmine.createSpy();
|
||||||
|
|
||||||
|
service.getAllSettingsFromCookie();
|
||||||
|
expect(cookieService.get).toHaveBeenCalledWith(ACCESSIBILITY_COOKIE);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAllSettingsFromAuthenticatedUserMetadata', () => {
|
||||||
|
it('should retrieve all settings from the user\'s metadata', () => {
|
||||||
|
const settings = { 'liveRegionTimeOut': '1000' };
|
||||||
|
|
||||||
|
const user = new EPerson();
|
||||||
|
user.setMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY, null, JSON.stringify(settings));
|
||||||
|
|
||||||
|
authService.getAuthenticatedUserFromStoreIfAuthenticated =
|
||||||
|
jasmine.createSpy('getAuthenticatedUserFromStoreIfAuthenticated').and.returnValue(of(user));
|
||||||
|
|
||||||
|
service.getAllSettingsFromAuthenticatedUserMetadata().subscribe(value =>
|
||||||
|
expect(value).toEqual(settings),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('set', () => {
|
||||||
|
it('should correctly update the chosen setting', () => {
|
||||||
|
service.updateSettings = jasmine.createSpy('updateSettings');
|
||||||
|
|
||||||
|
service.set(AccessibilitySetting.LiveRegionTimeOut, '500');
|
||||||
|
expect(service.updateSettings).toHaveBeenCalledWith({ liveRegionTimeOut: '500' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setSettings', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.setSettingsInCookie = jasmine.createSpy('setSettingsInCookie');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attempt to set settings in metadata', () => {
|
||||||
|
service.setSettingsInAuthenticatedUserMetadata =
|
||||||
|
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(false));
|
||||||
|
|
||||||
|
const settings: AccessibilitySettings = {
|
||||||
|
notificationTimeOut: '1000',
|
||||||
|
};
|
||||||
|
|
||||||
|
service.setSettings(settings).subscribe();
|
||||||
|
expect(service.setSettingsInAuthenticatedUserMetadata).toHaveBeenCalledWith(settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set settings in cookie if metadata failed', () => {
|
||||||
|
service.setSettingsInAuthenticatedUserMetadata =
|
||||||
|
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(false));
|
||||||
|
|
||||||
|
const settings: AccessibilitySettings = {
|
||||||
|
notificationTimeOut: '1000',
|
||||||
|
};
|
||||||
|
|
||||||
|
service.setSettings(settings).subscribe();
|
||||||
|
expect(service.setSettingsInCookie).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not set settings in cookie if metadata succeeded', () => {
|
||||||
|
service.setSettingsInAuthenticatedUserMetadata =
|
||||||
|
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(true));
|
||||||
|
|
||||||
|
const settings: AccessibilitySettings = {
|
||||||
|
notificationTimeOut: '1000',
|
||||||
|
};
|
||||||
|
|
||||||
|
service.setSettings(settings).subscribe();
|
||||||
|
expect(service.setSettingsInCookie).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return \'metadata\' if settings are stored in metadata', () => {
|
||||||
|
service.setSettingsInAuthenticatedUserMetadata =
|
||||||
|
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(true));
|
||||||
|
|
||||||
|
const settings: AccessibilitySettings = {
|
||||||
|
notificationTimeOut: '1000',
|
||||||
|
};
|
||||||
|
|
||||||
|
service.setSettings(settings).subscribe(value =>
|
||||||
|
expect(value).toEqual('metadata'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return \'cookie\' if settings are stored in cookie', () => {
|
||||||
|
service.setSettingsInAuthenticatedUserMetadata =
|
||||||
|
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(false));
|
||||||
|
|
||||||
|
const settings: AccessibilitySettings = {
|
||||||
|
notificationTimeOut: '1000',
|
||||||
|
};
|
||||||
|
|
||||||
|
service.setSettings(settings).subscribe(value =>
|
||||||
|
expect(value).toEqual('cookie'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateSettings', () => {
|
||||||
|
it('should call setSettings with the updated settings', () => {
|
||||||
|
const beforeSettings: AccessibilitySettings = {
|
||||||
|
notificationTimeOut: '1000',
|
||||||
|
};
|
||||||
|
|
||||||
|
service.getAll = jasmine.createSpy('getAll').and.returnValue(of(beforeSettings));
|
||||||
|
service.setSettings = jasmine.createSpy('setSettings').and.returnValue(of('cookie'));
|
||||||
|
|
||||||
|
const newSettings: AccessibilitySettings = {
|
||||||
|
liveRegionTimeOut: '2000',
|
||||||
|
};
|
||||||
|
|
||||||
|
const combinedSettings: AccessibilitySettings = {
|
||||||
|
notificationTimeOut: '1000',
|
||||||
|
liveRegionTimeOut: '2000',
|
||||||
|
};
|
||||||
|
|
||||||
|
service.updateSettings(newSettings).subscribe();
|
||||||
|
expect(service.setSettings).toHaveBeenCalledWith(combinedSettings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setSettingsInAuthenticatedUserMetadata', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.setSettingsInMetadata = jasmine.createSpy('setSettingsInMetadata').and.returnValue(of(null));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should store settings in metadata when the user is authenticated', fakeAsync(() => {
|
||||||
|
const user = new EPerson();
|
||||||
|
authService.getAuthenticatedUserFromStoreIfAuthenticated = jasmine.createSpy().and.returnValue(of(user));
|
||||||
|
|
||||||
|
service.setSettingsInAuthenticatedUserMetadata({}).subscribe();
|
||||||
|
flush();
|
||||||
|
|
||||||
|
expect(service.setSettingsInMetadata).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should emit false when the user is not authenticated', fakeAsync(() => {
|
||||||
|
authService.getAuthenticatedUserFromStoreIfAuthenticated = jasmine.createSpy().and.returnValue(of(null));
|
||||||
|
|
||||||
|
service.setSettingsInAuthenticatedUserMetadata({})
|
||||||
|
.subscribe(value => expect(value).toBeFalse());
|
||||||
|
flush();
|
||||||
|
|
||||||
|
expect(service.setSettingsInMetadata).not.toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setSettingsInMetadata', () => {
|
||||||
|
const ePerson = new EPerson();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
ePerson.setMetadata = jasmine.createSpy('setMetadata');
|
||||||
|
ePerson.removeMetadata = jasmine.createSpy('removeMetadata');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the settings in metadata', () => {
|
||||||
|
service.setSettingsInMetadata(ePerson, { [AccessibilitySetting.LiveRegionTimeOut]: '500' }).subscribe();
|
||||||
|
expect(ePerson.setMetadata).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the metadata when the settings are emtpy', () => {
|
||||||
|
service.setSettingsInMetadata(ePerson, {}).subscribe();
|
||||||
|
expect(ePerson.setMetadata).not.toHaveBeenCalled();
|
||||||
|
expect(ePerson.removeMetadata).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a patch with the metadata changes', () => {
|
||||||
|
service.setSettingsInMetadata(ePerson, { [AccessibilitySetting.LiveRegionTimeOut]: '500' }).subscribe();
|
||||||
|
expect(ePersonService.createPatchFromCache).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send the patch request', () => {
|
||||||
|
service.setSettingsInMetadata(ePerson, { [AccessibilitySetting.LiveRegionTimeOut]: '500' }).subscribe();
|
||||||
|
expect(ePersonService.patch).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit true when the update succeeded', fakeAsync(() => {
|
||||||
|
ePersonService.patch = jasmine.createSpy().and.returnValue(createSuccessfulRemoteDataObject$({}));
|
||||||
|
|
||||||
|
service.setSettingsInMetadata(ePerson, { [AccessibilitySetting.LiveRegionTimeOut]: '500' })
|
||||||
|
.subscribe(value => {
|
||||||
|
expect(value).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
flush();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should emit false when the update failed', fakeAsync(() => {
|
||||||
|
ePersonService.patch = jasmine.createSpy().and.returnValue(createFailedRemoteDataObject$());
|
||||||
|
|
||||||
|
service.setSettingsInMetadata(ePerson, { [AccessibilitySetting.LiveRegionTimeOut]: '500' })
|
||||||
|
.subscribe(value => {
|
||||||
|
expect(value).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
flush();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setSettingsInCookie', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cookieService.set = jasmine.createSpy('set');
|
||||||
|
cookieService.remove = jasmine.createSpy('remove');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should store the settings in a cookie', () => {
|
||||||
|
service.setSettingsInCookie({ [AccessibilitySetting.LiveRegionTimeOut]: '500' });
|
||||||
|
expect(cookieService.set).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the cookie when the settings are empty', () => {
|
||||||
|
service.setSettingsInCookie({});
|
||||||
|
expect(cookieService.set).not.toHaveBeenCalled();
|
||||||
|
expect(cookieService.remove).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
35
src/app/accessibility/accessibility-settings.service.stub.ts
Normal file
35
src/app/accessibility/accessibility-settings.service.stub.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
|
import { AccessibilitySettingsService } from './accessibility-settings.service';
|
||||||
|
|
||||||
|
export function getAccessibilitySettingsServiceStub(): AccessibilitySettingsService {
|
||||||
|
return new AccessibilitySettingsServiceStub() as unknown as AccessibilitySettingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AccessibilitySettingsServiceStub {
|
||||||
|
getAllAccessibilitySettingKeys = jasmine.createSpy('getAllAccessibilitySettingKeys').and.returnValue([]);
|
||||||
|
|
||||||
|
get = jasmine.createSpy('get').and.returnValue(of(null));
|
||||||
|
|
||||||
|
getAsNumber = jasmine.createSpy('getAsNumber').and.returnValue(of(0));
|
||||||
|
|
||||||
|
getAll = jasmine.createSpy('getAll').and.returnValue(of({}));
|
||||||
|
|
||||||
|
getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({});
|
||||||
|
|
||||||
|
getAllSettingsFromAuthenticatedUserMetadata = jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata')
|
||||||
|
.and.returnValue(of({}));
|
||||||
|
|
||||||
|
set = jasmine.createSpy('setSettings').and.returnValue(of('cookie'));
|
||||||
|
|
||||||
|
updateSettings = jasmine.createSpy('updateSettings').and.returnValue(of('cookie'));
|
||||||
|
|
||||||
|
setSettingsInAuthenticatedUserMetadata = jasmine.createSpy('setSettingsInAuthenticatedUserMetadata')
|
||||||
|
.and.returnValue(of(false));
|
||||||
|
|
||||||
|
setSettingsInMetadata = jasmine.createSpy('setSettingsInMetadata').and.returnValue(of(false));
|
||||||
|
|
||||||
|
setSettingsInCookie = jasmine.createSpy('setSettingsInCookie');
|
||||||
|
|
||||||
|
getInputType = jasmine.createSpy('getInputType').and.returnValue('text');
|
||||||
|
}
|
239
src/app/accessibility/accessibility-settings.service.ts
Normal file
239
src/app/accessibility/accessibility-settings.service.ts
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import {
|
||||||
|
Observable,
|
||||||
|
of,
|
||||||
|
switchMap,
|
||||||
|
} from 'rxjs';
|
||||||
|
import {
|
||||||
|
map,
|
||||||
|
take,
|
||||||
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
|
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 {
|
||||||
|
hasValue,
|
||||||
|
isNotEmpty,
|
||||||
|
isNotEmptyOperator,
|
||||||
|
} from '../shared/empty.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the cookie used to store the settings locally
|
||||||
|
*/
|
||||||
|
export const ACCESSIBILITY_COOKIE = 'dsAccessibilityCookie';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the metadata field to store settings on the ePerson
|
||||||
|
*/
|
||||||
|
export const ACCESSIBILITY_SETTINGS_METADATA_KEY = 'dspace.accessibility.settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum containing all possible accessibility settings.
|
||||||
|
* When adding new settings, the {@link AccessibilitySettingsService#getInputType} method and the i18n keys for the
|
||||||
|
* accessibility settings page should be updated.
|
||||||
|
*/
|
||||||
|
export enum AccessibilitySetting {
|
||||||
|
NotificationTimeOut = 'notificationTimeOut',
|
||||||
|
LiveRegionTimeOut = 'liveRegionTimeOut',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AccessibilitySettings = { [key in AccessibilitySetting]?: any };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service handling the retrieval and configuration of accessibility settings.
|
||||||
|
*
|
||||||
|
* This service stores the configured settings in either a cookie or on the user's metadata depending on whether
|
||||||
|
* the user is authenticated.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class AccessibilitySettingsService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected cookieService: CookieService,
|
||||||
|
protected authService: AuthService,
|
||||||
|
protected ePersonService: EPersonDataService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
* the provided defaultValue is emitted instead.
|
||||||
|
*/
|
||||||
|
get(setting: AccessibilitySetting, defaultValue: string = null): Observable<string> {
|
||||||
|
return this.getAll().pipe(
|
||||||
|
map(settings => settings[setting]),
|
||||||
|
map(value => isNotEmpty(value) ? value : defaultValue),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the stored value for the provided {@link AccessibilitySetting} as a number. If the stored value
|
||||||
|
* could not be converted to a number, the value of the defaultValue parameter is emitted instead.
|
||||||
|
*/
|
||||||
|
getAsNumber(setting: AccessibilitySetting, defaultValue: number = null): Observable<number> {
|
||||||
|
return this.get(setting).pipe(
|
||||||
|
map(value => hasValue(value) ? parseInt(value, 10) : NaN),
|
||||||
|
map(number => !isNaN(number) ? number : defaultValue),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all currently stored accessibility settings
|
||||||
|
*/
|
||||||
|
getAll(): Observable<AccessibilitySettings> {
|
||||||
|
return this.getAllSettingsFromAuthenticatedUserMetadata().pipe(
|
||||||
|
map(value => isNotEmpty(value) ? value : this.getAllSettingsFromCookie()),
|
||||||
|
map(value => isNotEmpty(value) ? value : {}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all settings from the accessibility settings cookie
|
||||||
|
*/
|
||||||
|
getAllSettingsFromCookie(): AccessibilitySettings {
|
||||||
|
return this.cookieService.get(ACCESSIBILITY_COOKIE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to retrieve all settings from the authenticated user's metadata.
|
||||||
|
* Returns an empty object when no user is authenticated.
|
||||||
|
*/
|
||||||
|
getAllSettingsFromAuthenticatedUserMetadata(): Observable<AccessibilitySettings> {
|
||||||
|
return this.authService.getAuthenticatedUserFromStoreIfAuthenticated().pipe(
|
||||||
|
take(1),
|
||||||
|
map(user => hasValue(user) && hasValue(user.firstMetadataValue(ACCESSIBILITY_SETTINGS_METADATA_KEY)) ?
|
||||||
|
JSON.parse(user.firstMetadataValue(ACCESSIBILITY_SETTINGS_METADATA_KEY)) :
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a single accessibility setting value, leaving all other settings unchanged.
|
||||||
|
* When setting all values, {@link AccessibilitySettingsService#setSettings} should be used.
|
||||||
|
* When updating multiple values, {@link AccessibilitySettingsService#updateSettings} should be used.
|
||||||
|
*
|
||||||
|
* Returns 'cookie' when the changes were stored in the cookie.
|
||||||
|
* Returns 'metadata' when the changes were stored in metadata.
|
||||||
|
*/
|
||||||
|
set(setting: AccessibilitySetting, value: string): Observable<'cookie' | 'metadata'> {
|
||||||
|
return this.updateSettings({ [setting]: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all accessibility settings simultaneously.
|
||||||
|
* This method removes existing settings if they are missing from the provided {@link AccessibilitySettings} object.
|
||||||
|
* Removes all settings if the provided object is empty.
|
||||||
|
*
|
||||||
|
* Returns 'cookie' when the changes were stored in the cookie.
|
||||||
|
* Returns 'metadata' when the changes were stored in metadata.
|
||||||
|
*/
|
||||||
|
setSettings(settings: AccessibilitySettings): Observable<'cookie' | 'metadata'> {
|
||||||
|
return this.setSettingsInAuthenticatedUserMetadata(settings).pipe(
|
||||||
|
take(1),
|
||||||
|
map((succeeded) => {
|
||||||
|
if (!succeeded) {
|
||||||
|
this.setSettingsInCookie(settings);
|
||||||
|
return 'cookie';
|
||||||
|
} else {
|
||||||
|
return 'metadata';
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update multiple accessibility settings simultaneously.
|
||||||
|
* This method does not change the settings that are missing from the provided {@link AccessibilitySettings} object.
|
||||||
|
*
|
||||||
|
* Returns 'cookie' when the changes were stored in the cookie.
|
||||||
|
* Returns 'metadata' when the changes were stored in metadata.
|
||||||
|
*/
|
||||||
|
updateSettings(settings: AccessibilitySettings): Observable<'cookie' | 'metadata'> {
|
||||||
|
return this.getAll().pipe(
|
||||||
|
take(1),
|
||||||
|
map(currentSettings => Object.assign({}, currentSettings, settings)),
|
||||||
|
switchMap(newSettings => this.setSettings(newSettings)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
setSettingsInAuthenticatedUserMetadata(settings: AccessibilitySettings): Observable<boolean> {
|
||||||
|
return this.authService.getAuthenticatedUserFromStoreIfAuthenticated().pipe(
|
||||||
|
take(1),
|
||||||
|
switchMap(user => {
|
||||||
|
if (hasValue(user)) {
|
||||||
|
// EPerson has to be cloned, otherwise the EPerson's metadata can't be modified
|
||||||
|
const clonedUser = cloneDeep(user);
|
||||||
|
return this.setSettingsInMetadata(clonedUser, settings);
|
||||||
|
} else {
|
||||||
|
return of(false);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to set the provided settings on the user's metadata.
|
||||||
|
* Emits false when the update failed, true when the update succeeded.
|
||||||
|
*/
|
||||||
|
setSettingsInMetadata(
|
||||||
|
user: EPerson,
|
||||||
|
settings: AccessibilitySettings,
|
||||||
|
): Observable<boolean> {
|
||||||
|
if (isNotEmpty(settings)) {
|
||||||
|
user.setMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY, null, JSON.stringify(settings));
|
||||||
|
} else {
|
||||||
|
user.removeMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ePersonService.createPatchFromCache(user).pipe(
|
||||||
|
take(1),
|
||||||
|
isNotEmptyOperator(),
|
||||||
|
switchMap(operations => this.ePersonService.patch(user, operations)),
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
map(rd => rd.hasSucceeded),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the provided settings in a cookie
|
||||||
|
*/
|
||||||
|
setSettingsInCookie(settings: AccessibilitySettings) {
|
||||||
|
if (isNotEmpty(settings)) {
|
||||||
|
this.cookieService.set(ACCESSIBILITY_COOKIE, settings, { expires: environment.accessibility.cookieExpirationDuration });
|
||||||
|
} else {
|
||||||
|
this.cookieService.remove(ACCESSIBILITY_COOKIE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the input type that a form should use for the provided {@link AccessibilitySetting}
|
||||||
|
*/
|
||||||
|
getInputType(setting: AccessibilitySetting): string {
|
||||||
|
switch (setting) {
|
||||||
|
case AccessibilitySetting.NotificationTimeOut:
|
||||||
|
return 'number';
|
||||||
|
case AccessibilitySetting.LiveRegionTimeOut:
|
||||||
|
return 'number';
|
||||||
|
default:
|
||||||
|
return 'text';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -60,6 +60,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
getAllSucceededRemoteDataPayload,
|
getAllSucceededRemoteDataPayload,
|
||||||
getFirstCompletedRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
|
getFirstSucceededRemoteDataPayload,
|
||||||
} from '../shared/operators';
|
} from '../shared/operators';
|
||||||
import { PageInfo } from '../shared/page-info.model';
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
import {
|
import {
|
||||||
@@ -261,6 +262,23 @@ export class AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable which emits the currently authenticated user from the store,
|
||||||
|
* or null if the user is not authenticated.
|
||||||
|
*/
|
||||||
|
public getAuthenticatedUserFromStoreIfAuthenticated(): Observable<EPerson> {
|
||||||
|
return this.store.pipe(
|
||||||
|
select(getAuthenticatedUserId),
|
||||||
|
switchMap((id: string) => {
|
||||||
|
if (hasValue(id)) {
|
||||||
|
return this.epersonService.findById(id).pipe(getFirstSucceededRemoteDataPayload());
|
||||||
|
} else {
|
||||||
|
return observableOf(null);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if token is present into browser storage and is valid.
|
* Checks if token is present into browser storage and is valid.
|
||||||
*/
|
*/
|
||||||
|
@@ -80,6 +80,10 @@
|
|||||||
<a class="btn text-white"
|
<a class="btn text-white"
|
||||||
routerLink="info/feedback">{{ 'footer.link.feedback' | translate}}</a>
|
routerLink="info/feedback">{{ 'footer.link.feedback' | translate}}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="text-white"
|
||||||
|
routerLink="info/accessibility">{{ 'footer.link.accessibility' | translate }}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="coarLdnEnabled$ | async" class="notify-enabled text-white">
|
<div *ngIf="coarLdnEnabled$ | async" class="notify-enabled text-white">
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h2>{{ 'info.accessibility-settings.title' | translate }}</h2>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<div *ngFor="let setting of accessibilitySettingsOptions" class="form-group row">
|
||||||
|
<label [for]="setting + 'Input'" class="col-sm-2 col-form-label">
|
||||||
|
{{ 'info.accessibility-settings.' + setting + '.label' | translate }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input [type]="getInputType(setting)" [id]="setting + 'Input'" class="form-control"
|
||||||
|
[(ngModel)]="formValues[setting]" [ngModelOptions]="{ standalone: true }"
|
||||||
|
[attr.aria-describedby]="setting + 'Hint'">
|
||||||
|
|
||||||
|
<small [id]="setting + 'Hint'" class="form-text text-muted">
|
||||||
|
{{ 'info.accessibility-settings.' + setting + '.hint' | translate }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" (click)="saveSettings()" class="btn btn-primary">
|
||||||
|
{{ 'info.accessibility-settings.submit' | translate }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
@@ -0,0 +1,86 @@
|
|||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import {
|
||||||
|
ComponentFixture,
|
||||||
|
TestBed,
|
||||||
|
waitForAsync,
|
||||||
|
} from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AccessibilitySetting,
|
||||||
|
AccessibilitySettingsService,
|
||||||
|
} from '../../accessibility/accessibility-settings.service';
|
||||||
|
import { getAccessibilitySettingsServiceStub } from '../../accessibility/accessibility-settings.service.stub';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { AuthServiceStub } from '../../shared/testing/auth-service.stub';
|
||||||
|
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||||
|
import { AccessibilitySettingsComponent } from './accessibility-settings.component';
|
||||||
|
|
||||||
|
|
||||||
|
describe('AccessibilitySettingsComponent', () => {
|
||||||
|
let component: AccessibilitySettingsComponent;
|
||||||
|
let fixture: ComponentFixture<AccessibilitySettingsComponent>;
|
||||||
|
|
||||||
|
let authService: AuthServiceStub;
|
||||||
|
let settingsService: AccessibilitySettingsService;
|
||||||
|
let notificationsService: NotificationsServiceStub;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
authService = new AuthServiceStub();
|
||||||
|
settingsService = getAccessibilitySettingsServiceStub();
|
||||||
|
notificationsService = new NotificationsServiceStub();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
{ provide: AuthService, useValue: authService },
|
||||||
|
{ provide: AccessibilitySettingsService, useValue: settingsService },
|
||||||
|
{ provide: NotificationsService, useValue: notificationsService },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AccessibilitySettingsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('On Init', () => {
|
||||||
|
it('should retrieve all accessibility settings options', () => {
|
||||||
|
expect(settingsService.getAllAccessibilitySettingKeys).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve the current settings', () => {
|
||||||
|
expect(settingsService.getAll).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getInputType', () => {
|
||||||
|
it('should retrieve the input type for the setting from the service', () => {
|
||||||
|
component.getInputType(AccessibilitySetting.LiveRegionTimeOut);
|
||||||
|
expect(settingsService.getInputType).toHaveBeenCalledWith(AccessibilitySetting.LiveRegionTimeOut);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('saveSettings', () => {
|
||||||
|
it('should save the settings in the service', () => {
|
||||||
|
settingsService.setSettings = jasmine.createSpy('setSettings').and.returnValue(of('cookie'));
|
||||||
|
component.saveSettings();
|
||||||
|
expect(settingsService.setSettings).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should give the user a notification mentioning where the settings were saved', () => {
|
||||||
|
settingsService.setSettings = jasmine.createSpy('setSettings').and.returnValue(of('cookie'));
|
||||||
|
component.saveSettings();
|
||||||
|
expect(notificationsService.success).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,62 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import {
|
||||||
|
TranslateModule,
|
||||||
|
TranslateService,
|
||||||
|
} from '@ngx-translate/core';
|
||||||
|
import { take } from 'rxjs';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AccessibilitySetting,
|
||||||
|
AccessibilitySettings,
|
||||||
|
AccessibilitySettingsService,
|
||||||
|
} from '../../accessibility/accessibility-settings.service';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-accessibility-settings',
|
||||||
|
templateUrl: './accessibility-settings.component.html',
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TranslateModule,
|
||||||
|
FormsModule,
|
||||||
|
],
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
export class AccessibilitySettingsComponent implements OnInit {
|
||||||
|
|
||||||
|
protected accessibilitySettingsOptions: AccessibilitySetting[];
|
||||||
|
|
||||||
|
protected formValues: AccessibilitySettings = { };
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected authService: AuthService,
|
||||||
|
protected settingsService: AccessibilitySettingsService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected translateService: TranslateService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.accessibilitySettingsOptions = this.settingsService.getAllAccessibilitySettingKeys();
|
||||||
|
this.settingsService.getAll().pipe(take(1)).subscribe(currentSettings => {
|
||||||
|
this.formValues = currentSettings;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputType(setting: AccessibilitySetting): string {
|
||||||
|
return this.settingsService.getInputType(setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSettings() {
|
||||||
|
this.settingsService.setSettings(this.formValues).pipe(take(1)).subscribe(location => {
|
||||||
|
this.notificationsService.success(null, this.translateService.instant('info.accessibility-settings.save-notification.' + location));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -8,9 +8,11 @@ import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
|
|||||||
import { notifyInfoGuard } from '../core/coar-notify/notify-info/notify-info.guard';
|
import { notifyInfoGuard } from '../core/coar-notify/notify-info/notify-info.guard';
|
||||||
import { feedbackGuard } from '../core/feedback/feedback.guard';
|
import { feedbackGuard } from '../core/feedback/feedback.guard';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
|
import { AccessibilitySettingsComponent } from './accessibility-settings/accessibility-settings.component';
|
||||||
import { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end-user-agreement.component';
|
import { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end-user-agreement.component';
|
||||||
import { ThemedFeedbackComponent } from './feedback/themed-feedback.component';
|
import { ThemedFeedbackComponent } from './feedback/themed-feedback.component';
|
||||||
import {
|
import {
|
||||||
|
ACCESSIBILITY_SETTINGS_PATH,
|
||||||
COAR_NOTIFY_SUPPORT,
|
COAR_NOTIFY_SUPPORT,
|
||||||
END_USER_AGREEMENT_PATH,
|
END_USER_AGREEMENT_PATH,
|
||||||
FEEDBACK_PATH,
|
FEEDBACK_PATH,
|
||||||
@@ -28,6 +30,12 @@ export const ROUTES: Routes = [
|
|||||||
data: { title: 'info.feedback.title', breadcrumbKey: 'info.feedback' },
|
data: { title: 'info.feedback.title', breadcrumbKey: 'info.feedback' },
|
||||||
canActivate: [feedbackGuard],
|
canActivate: [feedbackGuard],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ACCESSIBILITY_SETTINGS_PATH,
|
||||||
|
component: AccessibilitySettingsComponent,
|
||||||
|
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||||
|
data: { title: 'info.accessibility-settings.title', breadcrumbKey: 'info.accessibility-settings' },
|
||||||
|
},
|
||||||
environment.info.enableEndUserAgreement ? {
|
environment.info.enableEndUserAgreement ? {
|
||||||
path: END_USER_AGREEMENT_PATH,
|
path: END_USER_AGREEMENT_PATH,
|
||||||
component: ThemedEndUserAgreementComponent,
|
component: ThemedEndUserAgreementComponent,
|
||||||
|
@@ -4,6 +4,7 @@ export const END_USER_AGREEMENT_PATH = 'end-user-agreement';
|
|||||||
export const PRIVACY_PATH = 'privacy';
|
export const PRIVACY_PATH = 'privacy';
|
||||||
export const FEEDBACK_PATH = 'feedback';
|
export const FEEDBACK_PATH = 'feedback';
|
||||||
export const COAR_NOTIFY_SUPPORT = 'coar-notify-support';
|
export const COAR_NOTIFY_SUPPORT = 'coar-notify-support';
|
||||||
|
export const ACCESSIBILITY_SETTINGS_PATH = 'accessibility';
|
||||||
|
|
||||||
export function getEndUserAgreementPath() {
|
export function getEndUserAgreementPath() {
|
||||||
return getSubPath(END_USER_AGREEMENT_PATH);
|
return getSubPath(END_USER_AGREEMENT_PATH);
|
||||||
@@ -21,6 +22,10 @@ export function getCOARNotifySupportPath(): string {
|
|||||||
return getSubPath(COAR_NOTIFY_SUPPORT);
|
return getSubPath(COAR_NOTIFY_SUPPORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAccessibilitySettingsPath() {
|
||||||
|
return getSubPath(ACCESSIBILITY_SETTINGS_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
function getSubPath(path: string) {
|
function getSubPath(path: string) {
|
||||||
return `${getInfoModulePath()}/${path}`;
|
return `${getInfoModulePath()}/${path}`;
|
||||||
}
|
}
|
||||||
|
@@ -29,10 +29,18 @@
|
|||||||
></ds-profile-page-security-form>
|
></ds-profile-page-security-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 text-right pr-0">
|
<div class="col-12 text-right pr-0 mb-4">
|
||||||
<button class="btn btn-primary" (click)="updateProfile()"><i class="fas fa-edit"></i> {{'profile.form.submit' | translate}}</button>
|
<button class="btn btn-primary" (click)="updateProfile()"><i class="fas fa-edit"></i> {{'profile.form.submit' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">{{'profile.card.accessibility.header' | translate}}</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-1">{{'profile.card.accessibility.content' | translate}}</div>
|
||||||
|
<a [routerLink]="'/info/accessibility'">{{'profile.card.accessibility.link' | translate}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ng-container *ngVar="(groupsRD$ | async)?.payload?.page as groups">
|
<ng-container *ngVar="(groupsRD$ | async)?.payload?.page as groups">
|
||||||
<div *ngIf="groups?.length > 0">
|
<div *ngIf="groups?.length > 0">
|
||||||
<h2 class="mt-4">{{'profile.groups.head' | translate}}</h2>
|
<h2 class="mt-4">{{'profile.groups.head' | translate}}</h2>
|
||||||
|
@@ -8,6 +8,7 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
import {
|
import {
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
TranslateService,
|
TranslateService,
|
||||||
@@ -65,6 +66,7 @@ import { ProfilePageSecurityFormComponent } from './profile-page-security-form/p
|
|||||||
NgIf,
|
NgIf,
|
||||||
NgForOf,
|
NgForOf,
|
||||||
SuggestionsNotificationComponent,
|
SuggestionsNotificationComponent,
|
||||||
|
RouterModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -8,6 +8,8 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { AccessibilitySettingsService } from '../accessibility/accessibility-settings.service';
|
||||||
|
import { AccessibilitySettingsServiceStub } from '../accessibility/accessibility-settings.service.stub';
|
||||||
import { ThemedAdminSidebarComponent } from '../admin/admin-sidebar/themed-admin-sidebar.component';
|
import { ThemedAdminSidebarComponent } from '../admin/admin-sidebar/themed-admin-sidebar.component';
|
||||||
import { ThemedBreadcrumbsComponent } from '../breadcrumbs/themed-breadcrumbs.component';
|
import { ThemedBreadcrumbsComponent } from '../breadcrumbs/themed-breadcrumbs.component';
|
||||||
import { ThemedFooterComponent } from '../footer/themed-footer.component';
|
import { ThemedFooterComponent } from '../footer/themed-footer.component';
|
||||||
@@ -41,6 +43,7 @@ describe('RootComponent', () => {
|
|||||||
{ provide: MenuService, useValue: new MenuServiceStub() },
|
{ provide: MenuService, useValue: new MenuServiceStub() },
|
||||||
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
||||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||||
|
{ provide: AccessibilitySettingsService, useValue: new AccessibilitySettingsServiceStub() },
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
})
|
})
|
||||||
|
@@ -1,18 +1,26 @@
|
|||||||
import {
|
import {
|
||||||
fakeAsync,
|
fakeAsync,
|
||||||
flush,
|
|
||||||
tick,
|
tick,
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { AccessibilitySettingsService } from 'src/app/accessibility/accessibility-settings.service';
|
||||||
|
import { getAccessibilitySettingsServiceStub } from 'src/app/accessibility/accessibility-settings.service.stub';
|
||||||
|
|
||||||
import { UUIDService } from '../../core/shared/uuid.service';
|
import { UUIDService } from '../../core/shared/uuid.service';
|
||||||
import { LiveRegionService } from './live-region.service';
|
import { LiveRegionService } from './live-region.service';
|
||||||
|
|
||||||
describe('liveRegionService', () => {
|
describe('liveRegionService', () => {
|
||||||
let service: LiveRegionService;
|
let service: LiveRegionService;
|
||||||
|
let accessibilitySettingsService: AccessibilitySettingsService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
accessibilitySettingsService = getAccessibilitySettingsServiceStub();
|
||||||
|
|
||||||
|
accessibilitySettingsService.getAsNumber = jasmine.createSpy('getAsNumber').and.returnValue(of(100));
|
||||||
|
|
||||||
service = new LiveRegionService(
|
service = new LiveRegionService(
|
||||||
new UUIDService(),
|
new UUIDService(),
|
||||||
|
accessibilitySettingsService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,13 +94,16 @@ describe('liveRegionService', () => {
|
|||||||
expect(results[2]).toEqual(['Message One', 'Message Two']);
|
expect(results[2]).toEqual(['Message One', 'Message Two']);
|
||||||
|
|
||||||
service.clear();
|
service.clear();
|
||||||
flush();
|
tick(200);
|
||||||
|
|
||||||
expect(results.length).toEqual(4);
|
expect(results.length).toEqual(4);
|
||||||
expect(results[3]).toEqual([]);
|
expect(results[3]).toEqual([]);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should not pop messages added after clearing within timeOut period', fakeAsync(() => {
|
it('should not pop messages added after clearing within timeOut period', fakeAsync(() => {
|
||||||
|
// test expects a clear rate of 30 seconds
|
||||||
|
accessibilitySettingsService.getAsNumber = jasmine.createSpy('getAsNumber').and.returnValue(of(30000));
|
||||||
|
|
||||||
const results: string[][] = [];
|
const results: string[][] = [];
|
||||||
|
|
||||||
service.getMessages$().subscribe((messages) => {
|
service.getMessages$().subscribe((messages) => {
|
||||||
@@ -119,45 +130,6 @@ describe('liveRegionService', () => {
|
|||||||
expect(results.length).toEqual(5);
|
expect(results.length).toEqual(5);
|
||||||
expect(results[4]).toEqual([]);
|
expect(results[4]).toEqual([]);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should respect configured timeOut', fakeAsync(() => {
|
|
||||||
const results: string[][] = [];
|
|
||||||
|
|
||||||
service.getMessages$().subscribe((messages) => {
|
|
||||||
results.push(messages);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(results.length).toEqual(1);
|
|
||||||
expect(results[0]).toEqual([]);
|
|
||||||
|
|
||||||
const timeOutMs = 500;
|
|
||||||
service.setMessageTimeOutMs(timeOutMs);
|
|
||||||
|
|
||||||
service.addMessage('Message One');
|
|
||||||
tick(timeOutMs - 1);
|
|
||||||
|
|
||||||
expect(results.length).toEqual(2);
|
|
||||||
expect(results[1]).toEqual(['Message One']);
|
|
||||||
|
|
||||||
tick(1);
|
|
||||||
|
|
||||||
expect(results.length).toEqual(3);
|
|
||||||
expect(results[2]).toEqual([]);
|
|
||||||
|
|
||||||
const timeOutMsTwo = 50000;
|
|
||||||
service.setMessageTimeOutMs(timeOutMsTwo);
|
|
||||||
|
|
||||||
service.addMessage('Message Two');
|
|
||||||
tick(timeOutMsTwo - 1);
|
|
||||||
|
|
||||||
expect(results.length).toEqual(4);
|
|
||||||
expect(results[3]).toEqual(['Message Two']);
|
|
||||||
|
|
||||||
tick(1);
|
|
||||||
|
|
||||||
expect(results.length).toEqual(5);
|
|
||||||
expect(results[4]).toEqual([]);
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('liveRegionVisibility', () => {
|
describe('liveRegionVisibility', () => {
|
||||||
|
@@ -1,9 +1,22 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
map,
|
||||||
|
Observable,
|
||||||
|
switchMap,
|
||||||
|
take,
|
||||||
|
timer,
|
||||||
|
} from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
|
import {
|
||||||
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The LiveRegionService is responsible for handling the messages that are shown by the {@link LiveRegionComponent}.
|
* The LiveRegionService is responsible for handling the messages that are shown by the {@link LiveRegionComponent}.
|
||||||
* Use this service to add or remove messages to the Live Region.
|
* Use this service to add or remove messages to the Live Region.
|
||||||
@@ -15,6 +28,7 @@ export class LiveRegionService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected uuidService: UUIDService,
|
protected uuidService: UUIDService,
|
||||||
|
protected accessibilitySettingsService: AccessibilitySettingsService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +79,12 @@ export class LiveRegionService {
|
|||||||
addMessage(message: string): string {
|
addMessage(message: string): string {
|
||||||
const uuid = this.uuidService.generate();
|
const uuid = this.uuidService.generate();
|
||||||
this.messages.push({ message, uuid });
|
this.messages.push({ message, uuid });
|
||||||
setTimeout(() => this.clearMessageByUUID(uuid), this.messageTimeOutDurationMs);
|
|
||||||
|
this.getConfiguredMessageTimeOutMs().pipe(
|
||||||
|
take(1),
|
||||||
|
switchMap(timeOut => timer(timeOut)),
|
||||||
|
).subscribe(() => this.clearMessageByUUID(uuid));
|
||||||
|
|
||||||
this.emitCurrentMessages();
|
this.emitCurrentMessages();
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
@@ -116,6 +135,17 @@ export class LiveRegionService {
|
|||||||
this.liveRegionIsVisible = isVisible;
|
this.liveRegionIsVisible = isVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user-configured timeOut, or the stored timeOut if the user has not configured a timeOut duration.
|
||||||
|
* Emits {@link MIN_MESSAGE_DURATION} if the configured value is smaller.
|
||||||
|
*/
|
||||||
|
getConfiguredMessageTimeOutMs(): Observable<number> {
|
||||||
|
return this.accessibilitySettingsService.getAsNumber(
|
||||||
|
AccessibilitySetting.LiveRegionTimeOut,
|
||||||
|
this.getMessageTimeOutMs(),
|
||||||
|
).pipe(map(timeOut => Math.max(timeOut, MIN_MESSAGE_DURATION)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current message timeOut duration in milliseconds
|
* Gets the current message timeOut duration in milliseconds
|
||||||
*/
|
*/
|
||||||
|
@@ -18,6 +18,8 @@ import { cold } from 'jasmine-marbles';
|
|||||||
import uniqueId from 'lodash/uniqueId';
|
import uniqueId from 'lodash/uniqueId';
|
||||||
|
|
||||||
import { INotificationBoardOptions } from '../../../../config/notifications-config.interfaces';
|
import { INotificationBoardOptions } from '../../../../config/notifications-config.interfaces';
|
||||||
|
import { AccessibilitySettingsService } from '../../../accessibility/accessibility-settings.service';
|
||||||
|
import { getAccessibilitySettingsServiceStub } from '../../../accessibility/accessibility-settings.service.stub';
|
||||||
import { AppState } from '../../../app.reducer';
|
import { AppState } from '../../../app.reducer';
|
||||||
import { NotificationsServiceStub } from '../../testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../testing/notifications-service.stub';
|
||||||
import { Notification } from '../models/notification.model';
|
import { Notification } from '../models/notification.model';
|
||||||
@@ -48,6 +50,7 @@ describe('NotificationsBoardComponent', () => {
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: NotificationsService, useClass: NotificationsServiceStub },
|
{ provide: NotificationsService, useClass: NotificationsServiceStub },
|
||||||
|
{ provide: AccessibilitySettingsService, useValue: getAccessibilitySettingsServiceStub() },
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
],
|
],
|
||||||
}).compileComponents(); // compile template and css
|
}).compileComponents(); // compile template and css
|
||||||
|
@@ -15,13 +15,19 @@ import {
|
|||||||
select,
|
select,
|
||||||
Store,
|
Store,
|
||||||
} from '@ngrx/store';
|
} from '@ngrx/store';
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import difference from 'lodash/difference';
|
import difference from 'lodash/difference';
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
Subscription,
|
Subscription,
|
||||||
|
take,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { INotificationBoardOptions } from '../../../../config/notifications-config.interfaces';
|
import { INotificationBoardOptions } from '../../../../config/notifications-config.interfaces';
|
||||||
|
import {
|
||||||
|
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';
|
||||||
@@ -61,9 +67,12 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
public isPaused$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
public isPaused$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
constructor(private service: NotificationsService,
|
constructor(
|
||||||
private store: Store<AppState>,
|
protected service: NotificationsService,
|
||||||
private cdr: ChangeDetectorRef) {
|
protected store: Store<AppState>,
|
||||||
|
protected cdr: ChangeDetectorRef,
|
||||||
|
protected accessibilitySettingsService: AccessibilitySettingsService,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -96,7 +105,22 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy {
|
|||||||
if (this.notifications.length >= this.maxStack) {
|
if (this.notifications.length >= this.maxStack) {
|
||||||
this.notifications.splice(this.notifications.length - 1, 1);
|
this.notifications.splice(this.notifications.length - 1, 1);
|
||||||
}
|
}
|
||||||
this.notifications.splice(0, 0, item);
|
|
||||||
|
// 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.
|
||||||
|
this.accessibilitySettingsService.getAsNumber(AccessibilitySetting.NotificationTimeOut, item.options.timeOut)
|
||||||
|
.pipe(take(1)).subscribe(timeOut => {
|
||||||
|
if (timeOut < 0) {
|
||||||
|
timeOut = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep clone because the unaltered item is read-only
|
||||||
|
const modifiedNotification = cloneDeep(item);
|
||||||
|
modifiedNotification.options.timeOut = timeOut;
|
||||||
|
this.notifications.splice(0, 0, modifiedNotification);
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Remove the notification from the store
|
// Remove the notification from the store
|
||||||
// This notification was in the store, but not in this.notifications
|
// This notification was in the store, but not in this.notifications
|
||||||
|
@@ -59,6 +59,10 @@ export class AuthServiceStub {
|
|||||||
return observableOf(EPersonMock);
|
return observableOf(EPersonMock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAuthenticatedUserFromStoreIfAuthenticated(): Observable<EPerson> {
|
||||||
|
return observableOf(EPersonMock);
|
||||||
|
}
|
||||||
|
|
||||||
public buildAuthHeader(token?: AuthTokenInfo): string {
|
public buildAuthHeader(token?: AuthTokenInfo): string {
|
||||||
return `Bearer ${token ? token.accessToken : ''}`;
|
return `Bearer ${token ? token.accessToken : ''}`;
|
||||||
}
|
}
|
||||||
|
@@ -1900,6 +1900,8 @@
|
|||||||
|
|
||||||
"footer.copyright": "copyright © 2002-{{ year }}",
|
"footer.copyright": "copyright © 2002-{{ year }}",
|
||||||
|
|
||||||
|
"footer.link.accessibility": "Accessibility settings",
|
||||||
|
|
||||||
"footer.link.dspace": "DSpace software",
|
"footer.link.dspace": "DSpace software",
|
||||||
|
|
||||||
"footer.link.lyrasis": "LYRASIS",
|
"footer.link.lyrasis": "LYRASIS",
|
||||||
@@ -2128,6 +2130,24 @@
|
|||||||
|
|
||||||
"home.top-level-communities.help": "Select a community to browse its collections.",
|
"home.top-level-communities.help": "Select a community to browse its collections.",
|
||||||
|
|
||||||
|
"info.accessibility-settings.breadcrumbs": "Accessibility settings",
|
||||||
|
|
||||||
|
"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.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.save-notification.cookie": "Successfully saved settings locally.",
|
||||||
|
|
||||||
|
"info.accessibility-settings.save-notification.metadata": "Successfully saved settings on the user profile.",
|
||||||
|
|
||||||
|
"info.accessibility-settings.submit": "Save accessibility settings",
|
||||||
|
|
||||||
|
"info.accessibility-settings.title": "Accessibility settings",
|
||||||
|
|
||||||
"info.end-user-agreement.accept": "I have read and I agree to the End User Agreement",
|
"info.end-user-agreement.accept": "I have read and I agree to the End User Agreement",
|
||||||
|
|
||||||
"info.end-user-agreement.accept.error": "An error occurred accepting the End User Agreement",
|
"info.end-user-agreement.accept.error": "An error occurred accepting the End User Agreement",
|
||||||
@@ -3870,6 +3890,12 @@
|
|||||||
|
|
||||||
"profile.breadcrumbs": "Update Profile",
|
"profile.breadcrumbs": "Update Profile",
|
||||||
|
|
||||||
|
"profile.card.accessibility.content": "Accessibility settings can be configured on the accessibility settings page.",
|
||||||
|
|
||||||
|
"profile.card.accessibility.header": "Accessibility",
|
||||||
|
|
||||||
|
"profile.card.accessibility.link": "Accessibility Settings Page",
|
||||||
|
|
||||||
"profile.card.identify": "Identify",
|
"profile.card.identify": "Identify",
|
||||||
|
|
||||||
"profile.card.security": "Security",
|
"profile.card.security": "Security",
|
||||||
|
@@ -4,6 +4,7 @@ import {
|
|||||||
Type,
|
Type,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import { AccessibilitySettingsConfig } from '../app/accessibility/accessibility-settings.config';
|
||||||
import { AdminNotifyMetricsRow } from '../app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.model';
|
import { AdminNotifyMetricsRow } from '../app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.model';
|
||||||
import { HALDataService } from '../app/core/data/base/hal-data-service.interface';
|
import { HALDataService } from '../app/core/data/base/hal-data-service.interface';
|
||||||
import { LiveRegionConfig } from '../app/shared/live-region/live-region.config';
|
import { LiveRegionConfig } from '../app/shared/live-region/live-region.config';
|
||||||
@@ -66,6 +67,7 @@ interface AppConfig extends Config {
|
|||||||
search: SearchConfig;
|
search: SearchConfig;
|
||||||
notifyMetrics: AdminNotifyMetricsRow[];
|
notifyMetrics: AdminNotifyMetricsRow[];
|
||||||
liveRegion: LiveRegionConfig;
|
liveRegion: LiveRegionConfig;
|
||||||
|
accessibility: AccessibilitySettingsConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { AccessibilitySettingsConfig } from '../app/accessibility/accessibility-settings.config';
|
||||||
import { AdminNotifyMetricsRow } from '../app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.model';
|
import { AdminNotifyMetricsRow } from '../app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.model';
|
||||||
import { RestRequestMethod } from '../app/core/data/rest-request-method';
|
import { RestRequestMethod } from '../app/core/data/rest-request-method';
|
||||||
import { LiveRegionConfig } from '../app/shared/live-region/live-region.config';
|
import { LiveRegionConfig } from '../app/shared/live-region/live-region.config';
|
||||||
@@ -598,4 +599,9 @@ export class DefaultAppConfig implements AppConfig {
|
|||||||
messageTimeOutDurationMs: 30000,
|
messageTimeOutDurationMs: 30000,
|
||||||
isVisible: false,
|
isVisible: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Accessibility settings configuration, used by the AccessibilitySettingsService
|
||||||
|
accessibility: AccessibilitySettingsConfig = {
|
||||||
|
cookieExpirationDuration: 7,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@@ -427,4 +427,8 @@ export const environment: BuildConfig = {
|
|||||||
messageTimeOutDurationMs: 30000,
|
messageTimeOutDurationMs: 30000,
|
||||||
isVisible: false,
|
isVisible: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
accessibility: {
|
||||||
|
cookieExpirationDuration: 7,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@@ -4,6 +4,7 @@ import {
|
|||||||
NgIf,
|
NgIf,
|
||||||
} from '@angular/common';
|
} from '@angular/common';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { SuggestionsNotificationComponent } from '../../../../app/notifications/suggestions-notification/suggestions-notification.component';
|
import { SuggestionsNotificationComponent } from '../../../../app/notifications/suggestions-notification/suggestions-notification.component';
|
||||||
@@ -30,6 +31,7 @@ import { VarDirective } from '../../../../app/shared/utils/var.directive';
|
|||||||
NgIf,
|
NgIf,
|
||||||
NgForOf,
|
NgForOf,
|
||||||
SuggestionsNotificationComponent,
|
SuggestionsNotificationComponent,
|
||||||
|
RouterModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user