mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
[ACA-1743] extension settings (#1399)
* upgrade tslib * initial settings skeleton * migrate language picker setting * support string parameters * remove process extensions workaround * update extensions schema * update docs * unit tests * fix unit test
This commit is contained in:
parent
2b910d5a15
commit
5a88c8c852
@ -16,6 +16,7 @@ Learn how to extend the features of the Alfresco Content Application.
|
|||||||
- [Actions](/extending/actions)
|
- [Actions](/extending/actions)
|
||||||
- [Application actions](/extending/application-actions)
|
- [Application actions](/extending/application-actions)
|
||||||
- [Rules](/extending/rules)
|
- [Rules](/extending/rules)
|
||||||
|
- [Settings](/extending/settings)
|
||||||
- [Application features](/extending/application-features)
|
- [Application features](/extending/application-features)
|
||||||
- [Custom icons](/extending/icons)
|
- [Custom icons](/extending/icons)
|
||||||
- [Registration](/extending/registration)
|
- [Registration](/extending/registration)
|
||||||
|
45
docs/extending/settings.md
Normal file
45
docs/extending/settings.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
Title: Settings
|
||||||
|
---
|
||||||
|
|
||||||
|
# Settings
|
||||||
|
|
||||||
|
The application settings can be accessed via the `/settings` route.
|
||||||
|
|
||||||
|
You can project custom configuration groups via the `settings` section:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"id": "extensions.ps.settings",
|
||||||
|
"name": "Extensions: Process Services",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Enable Process Services Extensions",
|
||||||
|
"key": "processServices",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
At runtime, you are going to get an extra group called "Extensions: Process Services"
|
||||||
|
with a custom boolean setting "Enable Process Services Extensions".
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
Each setting parameter object supports the following properties:
|
||||||
|
|
||||||
|
| Property | Description |
|
||||||
|
| -------- | ----------------------------------------------- |
|
||||||
|
| id | (optional) Unique identifier |
|
||||||
|
| name | Public name, can be translation key |
|
||||||
|
| key | The key to use when saving to the storage |
|
||||||
|
| type | The type of the value (boolean / string) |
|
||||||
|
| value | (optional) Default value to use for the setting |
|
BIN
docs/images/aca-settings-custom-group.png
Normal file
BIN
docs/images/aca-settings-custom-group.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
@ -605,6 +605,52 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"settingsGroupRef": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Unique identifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Category name, can be translation key",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"description": "Settings group parameters",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/settingsGroupParameterRef" },
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["id", "name", "parameters"]
|
||||||
|
},
|
||||||
|
"settingsGroupParameterRef": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Unique identifier",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "Public name, can be a translation key",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"description": "The key to use when saving to the storage",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"description": "The type of the value",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"description": "Default value to use for the setting",
|
||||||
|
"type": ["boolean", "string", "number"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "key", "type"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -666,6 +712,12 @@
|
|||||||
"items": { "$ref": "#/definitions/actionRef" },
|
"items": { "$ref": "#/definitions/actionRef" },
|
||||||
"minItems": 1
|
"minItems": 1
|
||||||
},
|
},
|
||||||
|
"settings": {
|
||||||
|
"description": "List of application-specific setting groups",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/settingsGroupRef" },
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
"features": {
|
"features": {
|
||||||
"description": "Application-specific features and extensions",
|
"description": "Application-specific features and extensions",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -13380,9 +13380,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tslib": {
|
"tslib": {
|
||||||
"version": "1.9.3",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
|
||||||
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
|
"integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
|
||||||
},
|
},
|
||||||
"tslint": {
|
"tslint": {
|
||||||
"version": "5.20.1",
|
"version": "5.20.1",
|
||||||
|
@ -91,8 +91,8 @@
|
|||||||
"codelyzer": "^5.2.2",
|
"codelyzer": "^5.2.2",
|
||||||
"commander": "^4.0.1",
|
"commander": "^4.0.1",
|
||||||
"cpr": "^3.0.1",
|
"cpr": "^3.0.1",
|
||||||
"dotenv": "6.2.0",
|
|
||||||
"cspell": "^4.0.55",
|
"cspell": "^4.0.55",
|
||||||
|
"dotenv": "6.2.0",
|
||||||
"husky": "^2.4.0",
|
"husky": "^2.4.0",
|
||||||
"jasmine-core": "~2.8.0",
|
"jasmine-core": "~2.8.0",
|
||||||
"jasmine-reporters": "^2.2.1",
|
"jasmine-reporters": "^2.2.1",
|
||||||
@ -116,7 +116,7 @@
|
|||||||
"selenium-webdriver": "4.0.0-alpha.1",
|
"selenium-webdriver": "4.0.0-alpha.1",
|
||||||
"ts-node": "^8.0.3",
|
"ts-node": "^8.0.3",
|
||||||
"tsickle": "0.34.0",
|
"tsickle": "0.34.0",
|
||||||
"tslib": "^1.9.0",
|
"tslib": "^1.11.1",
|
||||||
"tslint": "^5.20.1",
|
"tslint": "^5.20.1",
|
||||||
"typescript": "3.2.4",
|
"typescript": "3.2.4",
|
||||||
"wait-on": "^3.0.1",
|
"wait-on": "^3.0.1",
|
||||||
|
@ -30,7 +30,6 @@ import * as repository from './repository.rules';
|
|||||||
export interface AcaRuleContext extends RuleContext {
|
export interface AcaRuleContext extends RuleContext {
|
||||||
languagePicker: boolean;
|
languagePicker: boolean;
|
||||||
withCredentials: boolean;
|
withCredentials: boolean;
|
||||||
processServices: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -554,5 +553,5 @@ export function canShowLogout(context: AcaRuleContext): boolean {
|
|||||||
* @param context Rule execution context
|
* @param context Rule execution context
|
||||||
*/
|
*/
|
||||||
export function canShowProcessServices(context: AcaRuleContext): boolean {
|
export function canShowProcessServices(context: AcaRuleContext): boolean {
|
||||||
return context.processServices;
|
return localStorage && localStorage.getItem('processServices') === 'true';
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,8 @@ import { Node, Person, Group, RepositoryInfo } from '@alfresco/js-api';
|
|||||||
import { AppState } from '../states/app.state';
|
import { AppState } from '../states/app.state';
|
||||||
|
|
||||||
export enum AppActionTypes {
|
export enum AppActionTypes {
|
||||||
|
SetSettingsParameter = 'SET_SETTINGS_PARAMETER',
|
||||||
SetInitialState = 'SET_INITIAL_STATE',
|
SetInitialState = 'SET_INITIAL_STATE',
|
||||||
SetLanguagePicker = 'SET_LANGUAGE_PICKER',
|
|
||||||
SetCurrentFolder = 'SET_CURRENT_FOLDER',
|
SetCurrentFolder = 'SET_CURRENT_FOLDER',
|
||||||
SetCurrentUrl = 'SET_CURRENT_URL',
|
SetCurrentUrl = 'SET_CURRENT_URL',
|
||||||
SetUserProfile = 'SET_USER_PROFILE',
|
SetUserProfile = 'SET_USER_PROFILE',
|
||||||
@ -41,8 +41,13 @@ export enum AppActionTypes {
|
|||||||
ResetSelection = 'RESET_SELECTION',
|
ResetSelection = 'RESET_SELECTION',
|
||||||
SetInfoDrawerState = 'SET_INFO_DRAWER_STATE',
|
SetInfoDrawerState = 'SET_INFO_DRAWER_STATE',
|
||||||
SetInfoDrawerMetadataAspect = 'SET_INFO_DRAWER_METADATA_ASPECT',
|
SetInfoDrawerMetadataAspect = 'SET_INFO_DRAWER_METADATA_ASPECT',
|
||||||
CloseModalDialogs = 'CLOSE_MODAL_DIALOGS',
|
CloseModalDialogs = 'CLOSE_MODAL_DIALOGS'
|
||||||
ToggleProcessServices = 'TOGGLE_PROCESS_SERVICES'
|
}
|
||||||
|
|
||||||
|
export class SetSettingsParameterAction implements Action {
|
||||||
|
readonly type = AppActionTypes.SetSettingsParameter;
|
||||||
|
|
||||||
|
constructor(public payload: { name: string; value: any }) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SetInitialStateAction implements Action {
|
export class SetInitialStateAction implements Action {
|
||||||
@ -51,12 +56,6 @@ export class SetInitialStateAction implements Action {
|
|||||||
constructor(public payload: AppState) {}
|
constructor(public payload: AppState) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SetLanguagePickerAction implements Action {
|
|
||||||
readonly type = AppActionTypes.SetLanguagePicker;
|
|
||||||
|
|
||||||
constructor(public payload: boolean) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SetCurrentFolderAction implements Action {
|
export class SetCurrentFolderAction implements Action {
|
||||||
readonly type = AppActionTypes.SetCurrentFolder;
|
readonly type = AppActionTypes.SetCurrentFolder;
|
||||||
|
|
||||||
@ -114,9 +113,3 @@ export class SetRepositoryInfoAction implements Action {
|
|||||||
|
|
||||||
constructor(public payload: RepositoryInfo) {}
|
constructor(public payload: RepositoryInfo) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ToggleProcessServicesAction implements Action {
|
|
||||||
readonly type = AppActionTypes.ToggleProcessServices;
|
|
||||||
|
|
||||||
constructor(public payload: boolean) {}
|
|
||||||
}
|
|
||||||
|
@ -133,8 +133,3 @@ export const infoDrawerMetadataAspect = createSelector(
|
|||||||
selectApp,
|
selectApp,
|
||||||
state => state.infoDrawerMetadataAspect
|
state => state.infoDrawerMetadataAspect
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getProcessServicesState = createSelector(
|
|
||||||
selectApp,
|
|
||||||
state => state.processServices
|
|
||||||
);
|
|
||||||
|
@ -44,7 +44,6 @@ export interface AppState {
|
|||||||
showFacetFilter: boolean;
|
showFacetFilter: boolean;
|
||||||
documentDisplayMode: string;
|
documentDisplayMode: string;
|
||||||
repository: RepositoryInfo;
|
repository: RepositoryInfo;
|
||||||
processServices: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppStore {
|
export interface AppStore {
|
||||||
|
@ -26,8 +26,6 @@
|
|||||||
"viewer.maxRetries": 1,
|
"viewer.maxRetries": 1,
|
||||||
"sharedLinkDateTimePickerType": "date",
|
"sharedLinkDateTimePickerType": "date",
|
||||||
"headerColor": "#ffffff",
|
"headerColor": "#ffffff",
|
||||||
"languagePicker": true,
|
|
||||||
"processServices": true,
|
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"size": 25,
|
"size": 25,
|
||||||
"supportedPageSizes": [25, 50, 100]
|
"supportedPageSizes": [25, 50, 100]
|
||||||
|
@ -42,6 +42,12 @@ describe('AppComponent', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const storageMock: any = {
|
||||||
|
getItem(): string {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
component = new AppComponent(
|
component = new AppComponent(
|
||||||
null,
|
null,
|
||||||
@ -55,7 +61,8 @@ describe('AppComponent', () => {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null
|
null,
|
||||||
|
storageMock
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -30,7 +30,8 @@ import {
|
|||||||
FileUploadErrorEvent,
|
FileUploadErrorEvent,
|
||||||
PageTitleService,
|
PageTitleService,
|
||||||
UploadService,
|
UploadService,
|
||||||
SharedLinksApiService
|
SharedLinksApiService,
|
||||||
|
StorageService
|
||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { ActivatedRoute, Router, ActivationEnd } from '@angular/router';
|
import { ActivatedRoute, Router, ActivationEnd } from '@angular/router';
|
||||||
@ -73,7 +74,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private extensions: AppExtensionService,
|
private extensions: AppExtensionService,
|
||||||
private contentApi: ContentApiService,
|
private contentApi: ContentApiService,
|
||||||
private appService: AppService,
|
private appService: AppService,
|
||||||
private sharedLinksApiService: SharedLinksApiService
|
private sharedLinksApiService: SharedLinksApiService,
|
||||||
|
private storage: StorageService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -178,8 +180,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const state: AppState = {
|
const state: AppState = {
|
||||||
...INITIAL_APP_STATE,
|
...INITIAL_APP_STATE,
|
||||||
languagePicker: this.config.get<boolean>('languagePicker'),
|
languagePicker: this.storage.getItem('languagePicker') === 'true',
|
||||||
processServices: this.config.get<boolean>('processServices'),
|
|
||||||
appName: this.config.get<string>('application.name'),
|
appName: this.config.get<string>('application.name'),
|
||||||
headerColor: this.config.get<string>('headerColor'),
|
headerColor: this.config.get<string>('headerColor'),
|
||||||
logoPath: this.config.get<string>('application.logo'),
|
logoPath: this.config.get<string>('application.logo'),
|
||||||
|
@ -32,7 +32,7 @@ import { Store } from '@ngrx/store';
|
|||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
SetUserProfileAction,
|
SetUserProfileAction,
|
||||||
SetLanguagePickerAction
|
SetSettingsParameterAction
|
||||||
} from '@alfresco/aca-shared/store';
|
} from '@alfresco/aca-shared/store';
|
||||||
|
|
||||||
describe('CurrentUserComponent', () => {
|
describe('CurrentUserComponent', () => {
|
||||||
@ -91,7 +91,9 @@ describe('CurrentUserComponent', () => {
|
|||||||
it('should set language picker state', done => {
|
it('should set language picker state', done => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
store.dispatch(new SetLanguagePickerAction(true));
|
store.dispatch(
|
||||||
|
new SetSettingsParameterAction({ name: 'languagePicker', value: true })
|
||||||
|
);
|
||||||
|
|
||||||
component.languagePicker$.subscribe((languagePicker: boolean) => {
|
component.languagePicker$.subscribe((languagePicker: boolean) => {
|
||||||
expect(languagePicker).toBe(true);
|
expect(languagePicker).toBe(true);
|
||||||
|
@ -68,39 +68,40 @@
|
|||||||
</form>
|
</form>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
<mat-expansion-panel>
|
<mat-expansion-panel *ngFor="let group of settingGroups">
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
<mat-panel-title>
|
<mat-panel-title>{{ group.name | translate }}</mat-panel-title>
|
||||||
{{ 'APP.SETTINGS.APPLICATION-SETTINGS' | translate }}
|
|
||||||
</mat-panel-title>
|
|
||||||
</mat-expansion-panel-header>
|
|
||||||
<mat-checkbox
|
|
||||||
[ngModel]="languagePicker$ | async"
|
|
||||||
(change)="onLanguagePickerValueChanged($event)"
|
|
||||||
>
|
|
||||||
Language Picker
|
|
||||||
</mat-checkbox>
|
|
||||||
</mat-expansion-panel>
|
|
||||||
|
|
||||||
<mat-expansion-panel>
|
|
||||||
<mat-expansion-panel-header>
|
|
||||||
<mat-panel-title>Extensions</mat-panel-title>
|
|
||||||
</mat-expansion-panel-header>
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
<div class="aca-settings-extensions-list">
|
<div class="aca-settings-parameter-list">
|
||||||
|
<ng-container *ngFor="let param of group.parameters">
|
||||||
|
<ng-container [ngSwitch]="param.type">
|
||||||
|
<ng-container *ngSwitchCase="'boolean'">
|
||||||
<mat-checkbox
|
<mat-checkbox
|
||||||
[ngModel]="aiExtensions$ | async"
|
[checked]="getBooleanParamValue(param)"
|
||||||
(change)="onToggleAiExtensions($event)"
|
(change)="setParamValue(param, $event.checked)"
|
||||||
|
>{{ param.name | translate }}</mat-checkbox
|
||||||
>
|
>
|
||||||
Enable AI Extensions
|
</ng-container>
|
||||||
</mat-checkbox>
|
|
||||||
|
|
||||||
<mat-checkbox
|
<ng-container *ngSwitchCase="'string'">
|
||||||
[ngModel]="psExtensions$ | async"
|
<mat-form-field class="settings-input" appearance="outline">
|
||||||
(change)="onTogglePsExtensions($event)"
|
<mat-label>{{ param.name | translate }}</mat-label>
|
||||||
>
|
<input
|
||||||
Enable Process Services Extensions
|
matInput
|
||||||
</mat-checkbox>
|
type="text"
|
||||||
|
[value]="getStringParamValue(param)"
|
||||||
|
(blur)="setParamValue(param, $event.target.value)"
|
||||||
|
(keyup.enter)="setParamValue(param, $event.target.value)"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngSwitchDefault>
|
||||||
|
<span>Unknown parameter type: {{ param.name | translate }}</span>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
|
@ -24,9 +24,131 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { SettingsComponent } from './settings.component';
|
import { SettingsComponent } from './settings.component';
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { setupTestBed, StorageService } from '@alfresco/adf-core';
|
||||||
|
import { AppSettingsModule } from './settings.module';
|
||||||
|
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||||
|
import { SettingsParameterRef } from '../../types';
|
||||||
|
import { AppExtensionService } from '../../extensions/extension.service';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import {
|
||||||
|
TranslateModule,
|
||||||
|
TranslateLoader,
|
||||||
|
TranslateFakeLoader
|
||||||
|
} from '@ngx-translate/core';
|
||||||
|
|
||||||
describe('SettingsComponent', () => {
|
describe('SettingsComponent', () => {
|
||||||
it('should be defined', () => {
|
let fixture: ComponentFixture<SettingsComponent>;
|
||||||
expect(SettingsComponent).toBeDefined();
|
let component: SettingsComponent;
|
||||||
|
let storage: StorageService;
|
||||||
|
let appExtensions: AppExtensionService;
|
||||||
|
|
||||||
|
let stringParam: SettingsParameterRef;
|
||||||
|
let boolParam: SettingsParameterRef;
|
||||||
|
|
||||||
|
setupTestBed({
|
||||||
|
imports: [
|
||||||
|
AppSettingsModule,
|
||||||
|
AppTestingModule,
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SettingsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
|
||||||
|
storage = TestBed.get(StorageService);
|
||||||
|
appExtensions = TestBed.get(AppExtensionService);
|
||||||
|
|
||||||
|
stringParam = {
|
||||||
|
key: 'key',
|
||||||
|
name: 'param1',
|
||||||
|
type: 'string',
|
||||||
|
value: 'paramValue'
|
||||||
|
};
|
||||||
|
|
||||||
|
boolParam = {
|
||||||
|
key: 'key',
|
||||||
|
name: 'param2',
|
||||||
|
type: 'boolean',
|
||||||
|
value: true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve string param value from storage', () => {
|
||||||
|
spyOn(storage, 'getItem').and.returnValue('storageValue');
|
||||||
|
|
||||||
|
const value = component.getStringParamValue(stringParam);
|
||||||
|
expect(value).toBe('storageValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use param value as fallback when storage is empty', () => {
|
||||||
|
spyOn(storage, 'getItem').and.returnValue(null);
|
||||||
|
|
||||||
|
const value = component.getStringParamValue(stringParam);
|
||||||
|
expect(value).toBe('paramValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save param value', () => {
|
||||||
|
spyOn(storage, 'setItem').and.stub();
|
||||||
|
|
||||||
|
component.setParamValue(stringParam, 'test');
|
||||||
|
|
||||||
|
expect(stringParam.value).toBe('test');
|
||||||
|
expect(storage.setItem).toHaveBeenCalledWith(
|
||||||
|
stringParam.key,
|
||||||
|
stringParam.value
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save param value only if changed', () => {
|
||||||
|
spyOn(storage, 'setItem').and.stub();
|
||||||
|
|
||||||
|
component.setParamValue(stringParam, 'test');
|
||||||
|
component.setParamValue(stringParam, 'test');
|
||||||
|
component.setParamValue(stringParam, 'test');
|
||||||
|
|
||||||
|
expect(storage.setItem).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve boolean param value', () => {
|
||||||
|
const getItemSpy = spyOn(storage, 'getItem').and.returnValue('true');
|
||||||
|
expect(component.getBooleanParamValue(boolParam)).toBe(true);
|
||||||
|
|
||||||
|
getItemSpy.and.returnValue('false');
|
||||||
|
expect(component.getBooleanParamValue(boolParam)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fallback to boolean param value when storage is empty', () => {
|
||||||
|
spyOn(storage, 'getItem').and.returnValue(null);
|
||||||
|
expect(component.getBooleanParamValue(boolParam)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render categories as expansion panels', async () => {
|
||||||
|
spyOn(component, 'reset').and.stub();
|
||||||
|
|
||||||
|
appExtensions.settingGroups = [
|
||||||
|
{
|
||||||
|
id: 'group1',
|
||||||
|
name: 'Group 1',
|
||||||
|
parameters: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'group2',
|
||||||
|
name: 'Group 2',
|
||||||
|
parameters: []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const panels = fixture.debugElement.queryAll(
|
||||||
|
By.css('.mat-expansion-panel')
|
||||||
|
);
|
||||||
|
expect(panels.length).toBe(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
$background: map-get($theme, background);
|
$background: map-get($theme, background);
|
||||||
$app-menu-height: 64px;
|
$app-menu-height: 64px;
|
||||||
|
|
||||||
|
.aca-settings-parameter-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.aca-settings {
|
.aca-settings {
|
||||||
&-extensions-list {
|
&-extensions-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -30,19 +30,18 @@ import {
|
|||||||
OauthConfigModel
|
OauthConfigModel
|
||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
import { Validators, FormGroup, FormBuilder } from '@angular/forms';
|
import { Validators, FormGroup, FormBuilder } from '@angular/forms';
|
||||||
import { Observable, BehaviorSubject } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { MatCheckboxChange } from '@angular/material/checkbox';
|
|
||||||
import {
|
import {
|
||||||
AppStore,
|
AppStore,
|
||||||
SetLanguagePickerAction,
|
|
||||||
getHeaderColor,
|
getHeaderColor,
|
||||||
getAppName,
|
getAppName,
|
||||||
getUserProfile,
|
getUserProfile,
|
||||||
getLanguagePickerState,
|
SetSettingsParameterAction
|
||||||
ToggleProcessServicesAction
|
|
||||||
} from '@alfresco/aca-shared/store';
|
} from '@alfresco/aca-shared/store';
|
||||||
import { ProfileState } from '@alfresco/adf-extensions';
|
import { ProfileState } from '@alfresco/adf-extensions';
|
||||||
|
import { AppExtensionService } from '../../extensions/extension.service';
|
||||||
|
import { SettingsGroupRef, SettingsParameterRef } from '../../types';
|
||||||
|
|
||||||
interface RepositoryConfig {
|
interface RepositoryConfig {
|
||||||
ecmHost: string;
|
ecmHost: string;
|
||||||
@ -64,11 +63,13 @@ export class SettingsComponent implements OnInit {
|
|||||||
profile$: Observable<ProfileState>;
|
profile$: Observable<ProfileState>;
|
||||||
appName$: Observable<string>;
|
appName$: Observable<string>;
|
||||||
headerColor$: Observable<string>;
|
headerColor$: Observable<string>;
|
||||||
languagePicker$: Observable<boolean>;
|
|
||||||
aiExtensions$: Observable<boolean>;
|
get settingGroups(): SettingsGroupRef[] {
|
||||||
psExtensions$: Observable<boolean>;
|
return this.appExtensions.settingGroups;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private appExtensions: AppExtensionService,
|
||||||
private store: Store<AppStore>,
|
private store: Store<AppStore>,
|
||||||
private appConfig: AppConfigService,
|
private appConfig: AppConfigService,
|
||||||
private storage: StorageService,
|
private storage: StorageService,
|
||||||
@ -76,23 +77,14 @@ export class SettingsComponent implements OnInit {
|
|||||||
) {
|
) {
|
||||||
this.profile$ = store.select(getUserProfile);
|
this.profile$ = store.select(getUserProfile);
|
||||||
this.appName$ = store.select(getAppName);
|
this.appName$ = store.select(getAppName);
|
||||||
this.languagePicker$ = store.select(getLanguagePickerState);
|
|
||||||
this.headerColor$ = store.select(getHeaderColor);
|
this.headerColor$ = store.select(getHeaderColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
get logo() {
|
get logo(): string {
|
||||||
return this.appConfig.get('application.logo', this.defaultPath);
|
return this.appConfig.get('application.logo', this.defaultPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.aiExtensions$ = new BehaviorSubject(
|
|
||||||
this.storage.getItem('ai') === 'true'
|
|
||||||
);
|
|
||||||
|
|
||||||
this.psExtensions$ = new BehaviorSubject(
|
|
||||||
this.storage.getItem('processServices') === 'true'
|
|
||||||
);
|
|
||||||
|
|
||||||
this.form = this.fb.group({
|
this.form = this.fb.group({
|
||||||
ecmHost: [
|
ecmHost: [
|
||||||
'',
|
'',
|
||||||
@ -139,17 +131,33 @@ export class SettingsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onLanguagePickerValueChanged(event: MatCheckboxChange) {
|
getStringParamValue(param: SettingsParameterRef): string {
|
||||||
this.storage.setItem('languagePicker', event.checked.toString());
|
return this.storage.getItem(param.key) || param.value;
|
||||||
this.store.dispatch(new SetLanguagePickerAction(event.checked));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggleAiExtensions(event: MatCheckboxChange) {
|
setParamValue(param: SettingsParameterRef, value: any) {
|
||||||
this.storage.setItem('ai', event.checked.toString());
|
if (param.value !== value) {
|
||||||
|
param.value = value;
|
||||||
|
this.saveToStorage(param);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTogglePsExtensions(event: MatCheckboxChange) {
|
getBooleanParamValue(param: SettingsParameterRef): boolean {
|
||||||
this.storage.setItem('processServices', event.checked.toString());
|
const result = this.storage.getItem(param.key);
|
||||||
this.store.dispatch(new ToggleProcessServicesAction(event.checked));
|
if (result) {
|
||||||
|
return result === 'true';
|
||||||
|
} else {
|
||||||
|
return param.value ? true : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveToStorage(param: SettingsParameterRef) {
|
||||||
|
this.storage.setItem(
|
||||||
|
param.key,
|
||||||
|
param.value ? param.value.toString() : param.value
|
||||||
|
);
|
||||||
|
this.store.dispatch(
|
||||||
|
new SetSettingsParameterAction({ name: param.key, value: param.value })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,7 @@ import { DomSanitizer } from '@angular/platform-browser';
|
|||||||
import {
|
import {
|
||||||
AppStore,
|
AppStore,
|
||||||
getRuleContext,
|
getRuleContext,
|
||||||
getLanguagePickerState,
|
getLanguagePickerState
|
||||||
getProcessServicesState
|
|
||||||
} from '@alfresco/aca-shared/store';
|
} from '@alfresco/aca-shared/store';
|
||||||
import { NodePermissionService } from '@alfresco/aca-shared';
|
import { NodePermissionService } from '@alfresco/aca-shared';
|
||||||
import {
|
import {
|
||||||
@ -60,6 +59,7 @@ import { AppConfigService, AuthenticationService } from '@alfresco/adf-core';
|
|||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { RepositoryInfo, NodeEntry } from '@alfresco/js-api';
|
import { RepositoryInfo, NodeEntry } from '@alfresco/js-api';
|
||||||
import { ViewerRules } from './viewer.rules';
|
import { ViewerRules } from './viewer.rules';
|
||||||
|
import { SettingsGroupRef } from '../types';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -84,6 +84,7 @@ export class AppExtensionService implements RuleContext {
|
|||||||
contentMetadata: any;
|
contentMetadata: any;
|
||||||
viewerRules: ViewerRules = {};
|
viewerRules: ViewerRules = {};
|
||||||
userActions: Array<ContentActionRef> = [];
|
userActions: Array<ContentActionRef> = [];
|
||||||
|
settingGroups: Array<SettingsGroupRef> = [];
|
||||||
|
|
||||||
documentListPresets: {
|
documentListPresets: {
|
||||||
files: Array<DocumentListPresetRef>;
|
files: Array<DocumentListPresetRef>;
|
||||||
@ -111,7 +112,6 @@ export class AppExtensionService implements RuleContext {
|
|||||||
repository: RepositoryInfo;
|
repository: RepositoryInfo;
|
||||||
withCredentials: boolean;
|
withCredentials: boolean;
|
||||||
languagePicker: boolean;
|
languagePicker: boolean;
|
||||||
processServices: boolean;
|
|
||||||
|
|
||||||
references$: Observable<ExtensionRef[]>;
|
references$: Observable<ExtensionRef[]>;
|
||||||
|
|
||||||
@ -137,10 +137,6 @@ export class AppExtensionService implements RuleContext {
|
|||||||
this.store.select(getLanguagePickerState).subscribe(result => {
|
this.store.select(getLanguagePickerState).subscribe(result => {
|
||||||
this.languagePicker = result;
|
this.languagePicker = result;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.store.select(getProcessServicesState).subscribe(result => {
|
|
||||||
this.processServices = result;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
@ -153,6 +149,12 @@ export class AppExtensionService implements RuleContext {
|
|||||||
console.error('Extension configuration not found');
|
console.error('Extension configuration not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.settingGroups = this.loader.getElements<SettingsGroupRef>(
|
||||||
|
config,
|
||||||
|
'settings'
|
||||||
|
);
|
||||||
|
|
||||||
this.headerActions = this.loader.getContentActions(
|
this.headerActions = this.loader.getContentActions(
|
||||||
config,
|
config,
|
||||||
'features.header'
|
'features.header'
|
||||||
|
@ -54,8 +54,7 @@ export const INITIAL_APP_STATE: AppState = {
|
|||||||
status: <any>{
|
status: <any>{
|
||||||
isQuickShareEnabled: true
|
isQuickShareEnabled: true
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
processServices: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const INITIAL_STATE: AppStore = {
|
export const INITIAL_STATE: AppStore = {
|
||||||
|
@ -30,7 +30,6 @@ import {
|
|||||||
NodeActionTypes,
|
NodeActionTypes,
|
||||||
SearchActionTypes,
|
SearchActionTypes,
|
||||||
SetUserProfileAction,
|
SetUserProfileAction,
|
||||||
SetLanguagePickerAction,
|
|
||||||
SetCurrentFolderAction,
|
SetCurrentFolderAction,
|
||||||
SetCurrentUrlAction,
|
SetCurrentUrlAction,
|
||||||
SetInitialStateAction,
|
SetInitialStateAction,
|
||||||
@ -38,7 +37,7 @@ import {
|
|||||||
SetRepositoryInfoAction,
|
SetRepositoryInfoAction,
|
||||||
SetInfoDrawerStateAction,
|
SetInfoDrawerStateAction,
|
||||||
SetInfoDrawerMetadataAspectAction,
|
SetInfoDrawerMetadataAspectAction,
|
||||||
ToggleProcessServicesAction
|
SetSettingsParameterAction
|
||||||
} from '@alfresco/aca-shared/store';
|
} from '@alfresco/aca-shared/store';
|
||||||
import { INITIAL_APP_STATE } from '../initial-state';
|
import { INITIAL_APP_STATE } from '../initial-state';
|
||||||
|
|
||||||
@ -52,15 +51,18 @@ export function appReducer(
|
|||||||
case AppActionTypes.SetInitialState:
|
case AppActionTypes.SetInitialState:
|
||||||
newState = Object.assign({}, (<SetInitialStateAction>action).payload);
|
newState = Object.assign({}, (<SetInitialStateAction>action).payload);
|
||||||
break;
|
break;
|
||||||
|
case AppActionTypes.SetSettingsParameter:
|
||||||
|
newState = handleSettingsUpdate(
|
||||||
|
state,
|
||||||
|
action as SetSettingsParameterAction
|
||||||
|
);
|
||||||
|
break;
|
||||||
case NodeActionTypes.SetSelection:
|
case NodeActionTypes.SetSelection:
|
||||||
newState = updateSelectedNodes(state, <SetSelectedNodesAction>action);
|
newState = updateSelectedNodes(state, <SetSelectedNodesAction>action);
|
||||||
break;
|
break;
|
||||||
case AppActionTypes.SetUserProfile:
|
case AppActionTypes.SetUserProfile:
|
||||||
newState = updateUser(state, <SetUserProfileAction>action);
|
newState = updateUser(state, <SetUserProfileAction>action);
|
||||||
break;
|
break;
|
||||||
case AppActionTypes.SetLanguagePicker:
|
|
||||||
newState = updateLanguagePicker(state, <SetLanguagePickerAction>action);
|
|
||||||
break;
|
|
||||||
case AppActionTypes.SetCurrentFolder:
|
case AppActionTypes.SetCurrentFolder:
|
||||||
newState = updateCurrentFolder(state, <SetCurrentFolderAction>action);
|
newState = updateCurrentFolder(state, <SetCurrentFolderAction>action);
|
||||||
break;
|
break;
|
||||||
@ -93,11 +95,6 @@ export function appReducer(
|
|||||||
case SearchActionTypes.HideFilter:
|
case SearchActionTypes.HideFilter:
|
||||||
newState = hideSearchFilter(state);
|
newState = hideSearchFilter(state);
|
||||||
break;
|
break;
|
||||||
case AppActionTypes.ToggleProcessServices:
|
|
||||||
newState = updateProcessServices(state, <ToggleProcessServicesAction>(
|
|
||||||
action
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
newState = Object.assign({}, state);
|
newState = Object.assign({}, state);
|
||||||
}
|
}
|
||||||
@ -123,15 +120,6 @@ function showSearchFilter(state: AppState): AppState {
|
|||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateLanguagePicker(
|
|
||||||
state: AppState,
|
|
||||||
action: SetLanguagePickerAction
|
|
||||||
): AppState {
|
|
||||||
const newState = Object.assign({}, state);
|
|
||||||
newState.languagePicker = action.payload;
|
|
||||||
return newState;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUser(state: AppState, action: SetUserProfileAction): AppState {
|
function updateUser(state: AppState, action: SetUserProfileAction): AppState {
|
||||||
const newState = Object.assign({}, state);
|
const newState = Object.assign({}, state);
|
||||||
const user = action.payload.person;
|
const user = action.payload.person;
|
||||||
@ -275,11 +263,15 @@ function updateRepositoryStatus(
|
|||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateProcessServices(
|
function handleSettingsUpdate(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
action: ToggleProcessServicesAction
|
action: SetSettingsParameterAction
|
||||||
) {
|
): AppState {
|
||||||
const newState = Object.assign({}, state);
|
const newState = { ...state };
|
||||||
newState.processServices = action.payload;
|
const { payload } = action;
|
||||||
|
|
||||||
|
if (payload.name === 'languagePicker') {
|
||||||
|
newState.languagePicker = payload.value ? true : false;
|
||||||
|
}
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
38
src/app/types.ts
Normal file
38
src/app/types.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Alfresco Example Content Application
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
||||||
|
*
|
||||||
|
* This file is part of the Alfresco Example Content Application.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface SettingsGroupRef {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
parameters: Array<SettingsParameterRef>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SettingsParameterRef {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
type: 'string' | 'boolean';
|
||||||
|
value?: any;
|
||||||
|
}
|
@ -9,6 +9,45 @@
|
|||||||
"$description": "Core application extensions and features",
|
"$description": "Core application extensions and features",
|
||||||
"$references": ["aos.plugin.json", "app.header.json"],
|
"$references": ["aos.plugin.json", "app.header.json"],
|
||||||
|
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"id": "app.settings",
|
||||||
|
"name": "APP.SETTINGS.APPLICATION-SETTINGS",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Language Picker",
|
||||||
|
"key": "languagePicker",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "extensions.ai.settings",
|
||||||
|
"name": "Extensions: AI",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Enable AI Extensions",
|
||||||
|
"key": "ai",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "extensions.ps.settings",
|
||||||
|
"name": "Extensions: Process Services",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "Enable Process Services Extensions",
|
||||||
|
"key": "processServices",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"id": "app.toolbar.favorite.canAdd",
|
"id": "app.toolbar.favorite.canAdd",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user