[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:
Denys Vuika 2020-04-06 18:58:41 +01:00 committed by GitHub
parent 2b910d5a15
commit 5a88c8c852
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 425 additions and 127 deletions

View File

@ -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)

View 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".
![Custom settings group](../images/aca-settings-custom-group.png)
## 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 |

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -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
View File

@ -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",

View File

@ -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",

View File

@ -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';
} }

View File

@ -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) {}
}

View File

@ -133,8 +133,3 @@ export const infoDrawerMetadataAspect = createSelector(
selectApp, selectApp,
state => state.infoDrawerMetadataAspect state => state.infoDrawerMetadataAspect
); );
export const getProcessServicesState = createSelector(
selectApp,
state => state.processServices
);

View File

@ -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 {

View File

@ -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]

View File

@ -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
); );
}); });

View File

@ -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'),

View File

@ -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);

View File

@ -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">
<mat-checkbox <ng-container *ngFor="let param of group.parameters">
[ngModel]="aiExtensions$ | async" <ng-container [ngSwitch]="param.type">
(change)="onToggleAiExtensions($event)" <ng-container *ngSwitchCase="'boolean'">
> <mat-checkbox
Enable AI Extensions [checked]="getBooleanParamValue(param)"
</mat-checkbox> (change)="setParamValue(param, $event.checked)"
>{{ param.name | translate }}</mat-checkbox
>
</ng-container>
<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>

View File

@ -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);
}); });
}); });

View File

@ -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;

View File

@ -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 })
);
} }
} }

View File

@ -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'

View File

@ -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 = {

View File

@ -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
View 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;
}

View File

@ -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",