diff --git a/src/app.config.json b/src/app.config.json index 39041e5ec..bbccd407a 100644 --- a/src/app.config.json +++ b/src/app.config.json @@ -42,6 +42,28 @@ "plugin2.json" ], "core": { + "rules": [ + { + "id": "app.create.canCreateFolder", + "type": "app.navigation.folder.canCreate" + }, + { + "id": "app.toolbar.canEditFolder", + "type": "core.every", + "parameters": [ + { "type": "rule", "value": "app.selection.folder" }, + { "type": "rule", "value": "app.selection.folder.canUpdate" } + ] + }, + { + "id": "app.toolbar.canViewFile", + "type": "app.selection.file" + }, + { + "id": "app.toolbar.canDownload", + "type": "app.selection.canDownload" + } + ], "routes": [ { "id": "aca:routes/about", @@ -81,11 +103,6 @@ "type": "SNACKBAR_INFO", "payload": "I'm a nice little popup raised by extension." }, - { - "id": "aca:actions/error", - "type": "SNACKBAR_ERROR", - "payload": "Aw, Snap!" - }, { "id": "aca:actions/node-name", "type": "SNACKBAR_INFO", @@ -100,14 +117,14 @@ "features": { "create": [ { - "disabled": false, - "id": "aca:create/folder", - "order": 100, + "id": "app.create.folder", "icon": "create_new_folder", "title": "ext: Create Folder", - "target": { - "permissions": ["create"], - "action": "aca:actions/create-folder" + "actions": { + "click": "aca:actions/create-folder" + }, + "rules": { + "enabled": "app.create.canCreateFolder" } } ], @@ -210,10 +227,11 @@ "order": 10, "title": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER", "icon": "create_new_folder", - "target": { - "types": [], - "permissions": ["parent.create"], - "action": "aca:actions/create-folder" + "actions": { + "click": "aca:actions/create-folder" + }, + "rules": { + "visible": "app.create.canCreateFolder" } }, { @@ -222,10 +240,11 @@ "order": 15, "title": "APP.ACTIONS.VIEW", "icon": "open_in_browser", - "target": { - "types": ["file"], - "permissions": [], - "action": "aca:actions/preview" + "actions": { + "click": "aca:actions/preview" + }, + "rules": { + "visible": "app.toolbar.canViewFile" } }, { @@ -234,11 +253,11 @@ "order": 20, "title": "APP.ACTIONS.DOWNLOAD", "icon": "get_app", - "target": { - "types": ["file", "folder"], - "permissions": [], - "action": "aca:actions/download", - "multiple": true + "actions": { + "click": "aca:actions/download" + }, + "rules": { + "visible": "app.toolbar.canDownload" } }, { @@ -247,10 +266,11 @@ "order": 30, "title": "APP.ACTIONS.EDIT", "icon": "create", - "target": { - "types": ["folder"], - "permissions": ["update"], - "action": "aca:actions/edit-folder" + "actions": { + "click": "aca:actions/edit-folder" + }, + "rules": { + "visible": "app.toolbar.canEditFolder" } }, @@ -270,21 +290,8 @@ "type": "button", "title": "Settings", "icon": "settings_applications", - "target": { - "types": [], - "permissions": [], - "action": "aca:actions/settings" - } - }, - { - "id": "aca:action4", - "type": "button", - "title": "Error", - "icon": "report_problem", - "target": { - "types": ["file"], - "permissions": ["update", "delete"], - "action": "aca:actions/error" + "actions": { + "click": "aca:actions/settings" } } ] diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 09e65bc1c..2bf23b841 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -85,6 +85,7 @@ import { SearchResultsRowComponent } from './components/search/search-results-ro import { NodePermissionsDialogComponent } from './dialogs/node-permissions/node-permissions.dialog'; import { NodePermissionsDirective } from './common/directives/node-permissions.directive'; import { PermissionsManagerComponent } from './components/permission-manager/permissions-manager.component'; +import { RuleService } from './extensions/rules/rule.service'; @NgModule({ imports: [ @@ -161,7 +162,8 @@ import { PermissionsManagerComponent } from './components/permission-manager/per ProfileResolver, ExperimentalGuard, ContentApiService, - ExtensionService + ExtensionService, + RuleService ], entryComponents: [ LibraryDialogComponent, diff --git a/src/app/components/page.component.ts b/src/app/components/page.component.ts index e84c547a0..95c3dd909 100644 --- a/src/app/components/page.component.ts +++ b/src/app/components/page.component.ts @@ -83,8 +83,7 @@ export abstract class PageComponent implements OnInit, OnDestroy { if (selection.isEmpty) { this.infoDrawerOpened = false; } - const selectedNodes = selection ? selection.nodes : null; - this.actions = this.extensions.getAllowedContentActions(selectedNodes, this.node); + this.actions = this.extensions.getAllowedContentActions(); this.canUpdateFile = this.selection.file && this.content.canUpdateNode(selection.file); this.canUpdateNode = this.selection.count === 1 && this.content.canUpdateNode(selection.first); this.canDelete = !this.selection.isEmpty && this.content.canDeleteNodes(selection.nodes); diff --git a/src/app/components/sidenav/sidenav.component.html b/src/app/components/sidenav/sidenav.component.html index 851a0b6b4..042d0b5ba 100644 --- a/src/app/components/sidenav/sidenav.component.html +++ b/src/app/components/sidenav/sidenav.component.html @@ -10,7 +10,7 @@ diff --git a/src/app/extensions/components/toolbar-action/toolbar-action.component.html b/src/app/extensions/components/toolbar-action/toolbar-action.component.html index c4e03c64d..9684bf899 100644 --- a/src/app/extensions/components/toolbar-action/toolbar-action.component.html +++ b/src/app/extensions/components/toolbar-action/toolbar-action.component.html @@ -3,7 +3,7 @@ mat-icon-button color="primary" title="{{ entry.title | translate }}" - (click)="runAction(entry.target.action)"> + (click)="runAction(entry.actions.click)"> {{ entry.icon }} @@ -20,7 +20,7 @@ [overlapTrigger]="false"> diff --git a/src/app/extensions/content-action.extension.ts b/src/app/extensions/content-action.extension.ts index 0f4478c2d..65e805de7 100644 --- a/src/app/extensions/content-action.extension.ts +++ b/src/app/extensions/content-action.extension.ts @@ -38,10 +38,13 @@ export interface ContentActionExtension { icon?: string; disabled?: boolean; children?: Array; - target: { - types: Array; - permissions: Array, - action: string; - multiple?: boolean; + actions?: { + click?: string; + [key: string]: string; + }; + rules: { + enabled?: string; + visible?: string; + [key: string]: string; }; } diff --git a/src/app/extensions/extension.config.ts b/src/app/extensions/extension.config.ts new file mode 100644 index 000000000..0a5e9af20 --- /dev/null +++ b/src/app/extensions/extension.config.ts @@ -0,0 +1,35 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 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 . + */ + +import { RouteExtension } from './route.extension'; +import { ActionExtension } from './action.extension'; +import { RuleRef } from './rules/rule-ref'; + +export interface ExtensionConfig { + rules?: Array; + routes?: Array; + actions?: Array; + features?: { [key: string]: any }; +} diff --git a/src/app/extensions/extension.service.spec.ts b/src/app/extensions/extension.service.spec.ts index e0819e4c8..fa1e27278 100644 --- a/src/app/extensions/extension.service.spec.ts +++ b/src/app/extensions/extension.service.spec.ts @@ -491,63 +491,6 @@ describe('ExtensionService', () => { }); }); - describe('permissions', () => { - it('should approve node permission', () => { - const node: any = { - allowableOperations: ['create'] - }; - - expect(extensions.nodeHasPermission(node, 'create')).toBeTruthy(); - }); - - it('should not approve node permission', () => { - const node: any = { - allowableOperations: ['create'] - }; - - expect(extensions.nodeHasPermission(node, 'update')).toBeFalsy(); - }); - - it('should not approve node permission when node missing property', () => { - const node: any = { - allowableOperations: null - }; - - expect(extensions.nodeHasPermission(node, 'update')).toBeFalsy(); - }); - - it('should require node to check permission', () => { - expect(extensions.nodeHasPermission(null, 'create')).toBeFalsy(); - }); - - it('should require permission value to check', () => { - const node: any = { - allowableOperations: ['create'] - }; - expect(extensions.nodeHasPermission(node, null)).toBeFalsy(); - }); - - it('should approve multiple permissions', () => { - const node: any = { - allowableOperations: ['create', 'update', 'delete'] - }; - expect( - extensions.nodeHasPermissions(node, ['create', 'delete']) - ).toBeTruthy(); - }); - - it('should require node to check multiple permissions', () => { - expect(extensions.nodeHasPermissions(null, ['create'])).toBeFalsy(); - }); - - it('should require multiple permissions to check', () => { - const node: any = { - allowableOperations: ['create', 'update', 'delete'] - }; - expect(extensions.nodeHasPermissions(node, null)).toBeFalsy(); - }); - }); - describe('sorting', () => { it('should sort by provided order', () => { const sorted = [ diff --git a/src/app/extensions/extension.service.ts b/src/app/extensions/extension.service.ts index 40e7c10b0..6991c81fa 100644 --- a/src/app/extensions/extension.service.ts +++ b/src/app/extensions/extension.service.ts @@ -36,7 +36,8 @@ import { AppStore } from '../store/states'; import { Store } from '@ngrx/store'; import { NavigationExtension } from './navigation.extension'; import { Route } from '@angular/router'; -import { Node, MinimalNodeEntity } from 'alfresco-js-api'; +import { Node } from 'alfresco-js-api'; +import { RuleService } from './rules/rule.service'; @Injectable() export class ExtensionService { @@ -52,7 +53,8 @@ export class ExtensionService { constructor( private config: AppConfigService, - private store: Store + private store: Store, + private ruleService: RuleService ) {} // initialise extension service @@ -89,6 +91,8 @@ export class ExtensionService { [] ) .sort(this.sortByOrder); + + this.ruleService.init(); } getRouteById(id: string): RouteExtension { @@ -183,38 +187,28 @@ export class ExtensionService { // evaluates create actions for the folder node getFolderCreateActions(folder: Node): Array { - return this.createActions.filter(this.filterEnabled).map(action => { - if ( - action.target && - action.target.permissions && - action.target.permissions.length > 0 - ) { + return this.createActions + .filter(this.filterEnabled) + .filter(action => this.filterByRules(action)) + .map(action => { + let disabled = false; + + if (action.rules && action.rules.enabled) { + disabled = !this.ruleService.evaluateRule(action.rules.enabled); + } + return { ...action, - disabled: !this.nodeHasPermissions( - folder, - action.target.permissions - ), - target: { - ...action.target - } + disabled }; - } - return action; }); } // evaluates content actions for the selection and parent folder node - getAllowedContentActions( - nodes: MinimalNodeEntity[], - parentNode: Node - ): Array { + getAllowedContentActions(): Array { return this.contentActions .filter(this.filterEnabled) - .filter(action => this.filterByTarget(nodes, action)) - .filter(action => - this.filterByPermission(nodes, action, parentNode) - ) + .filter(action => this.filterByRules(action)) .reduce(this.reduceSeparators, []) .map(action => { if (action.type === ContentActionType.menu) { @@ -222,14 +216,7 @@ export class ExtensionService { if (copy.children && copy.children.length > 0) { copy.children = copy.children .filter(childAction => - this.filterByTarget(nodes, childAction) - ) - .filter(childAction => - this.filterByPermission( - nodes, - childAction, - parentNode - ) + this.filterByRules(childAction) ) .reduce(this.reduceSeparators, []); } @@ -301,108 +288,10 @@ export class ExtensionService { }; } - filterByTarget( - nodes: MinimalNodeEntity[], - action: ContentActionExtension - ): boolean { - if (!action) { - return false; + filterByRules(action: ContentActionExtension): boolean { + if (action && action.rules && action.rules.visible) { + return this.ruleService.evaluateRule(action.rules.visible); } - - if (!action.target) { - return ( - action.type === ContentActionType.separator || - action.type === ContentActionType.menu - ); - } - - const types = action.target.types || []; - - if (types.length === 0) { - return true; - } - - if (nodes && nodes.length > 0) { - return types.some(type => { - if (type === 'folder') { - return action.target.multiple - ? nodes.some(node => node.entry.isFolder) - : nodes.length === 1 && - nodes.every(node => node.entry.isFolder); - } - if (type === 'file') { - return action.target.multiple - ? nodes.some(node => node.entry.isFile) - : nodes.length === 1 && - nodes.every(node => node.entry.isFile); - } - return false; - }); - } - - return false; - } - - filterByPermission( - nodes: MinimalNodeEntity[], - action: ContentActionExtension, - parentNode: Node - ): boolean { - if (!action) { - return false; - } - - if (!action.target) { - return ( - action.type === ContentActionType.separator || - action.type === ContentActionType.menu - ); - } - - const permissions = action.target.permissions || []; - - if (permissions.length === 0) { - return true; - } - - return permissions.some(permission => { - if (permission.startsWith('parent.')) { - if (parentNode) { - const parentQuery = permission.split('.')[1]; - return this.nodeHasPermission(parentNode, parentQuery); - } - return false; - } - - if (nodes && nodes.length > 0) { - return action.target.multiple - ? nodes.some(node => - this.nodeHasPermission(node.entry, permission) - ) - : nodes.length === 1 && - nodes.every(node => - this.nodeHasPermission(node.entry, permission) - ); - } - - return false; - }); - } - - nodeHasPermissions(node: Node, permissions: string[]): boolean { - if (node && permissions && permissions.length > 0) { - return permissions.some(permission => - this.nodeHasPermission(node, permission) - ); - } - return false; - } - - nodeHasPermission(node: Node, permission: string): boolean { - if (node && permission) { - const allowableOperations = node.allowableOperations || []; - return allowableOperations.includes(permission); - } - return false; + return true; } } diff --git a/src/app/extensions/rules/app.evaluators.ts b/src/app/extensions/rules/app.evaluators.ts new file mode 100644 index 000000000..51a7560f5 --- /dev/null +++ b/src/app/extensions/rules/app.evaluators.ts @@ -0,0 +1,72 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 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 . + */ + +import { RuleContext } from './rule-context'; +import { RuleParameter } from './rule-parameter'; +import { Node } from 'alfresco-js-api'; + +export function canCreateFolder(context: RuleContext, ...args: RuleParameter[]): boolean { + const folder = context.navigation.currentFolder; + if (folder) { + return nodeHasPermission(folder, 'create'); + } + return false; +} + +export function canDownloadSelection(context: RuleContext, ...args: RuleParameter[]): boolean { + if (!context.selection.isEmpty) { + return context.selection.nodes.every(node => { + return node.entry && (node.entry.isFile || node.entry.isFolder); + }); + } + return false; + +} + +export function hasFolderSelected(context: RuleContext, ...args: RuleParameter[]): boolean { + const folder = context.selection.folder; + return folder ? true : false; +} + +export function hasFileSelected(context: RuleContext, ...args: RuleParameter[]): boolean { + const file = context.selection.file; + return file ? true : false; +} + +export function canUpdateSelectedFolder(context: RuleContext, ...args: RuleParameter[]): boolean { + const folder = context.selection.folder; + if (folder && folder.entry) { + return nodeHasPermission(folder.entry, 'update'); + } + return false; +} + +export function nodeHasPermission(node: Node, permission: string): boolean { + if (node && permission) { + const allowableOperations = node.allowableOperations || []; + return allowableOperations.includes(permission); + } + return false; +} diff --git a/src/app/extensions/rules/core.evaluators.ts b/src/app/extensions/rules/core.evaluators.ts new file mode 100644 index 000000000..c69b7888e --- /dev/null +++ b/src/app/extensions/rules/core.evaluators.ts @@ -0,0 +1,47 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 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 . + */ + +import { RuleContext } from './rule-context'; +import { RuleParameter } from './rule-parameter'; + +export function every(context: RuleContext, ...args: RuleParameter[]): boolean { + if (!args || args.length === 0) { + return false; + } + + return args + .map(arg => context.evaluators[arg.value]) + .every(evaluator => evaluator(context)); +} + +export function some(context: RuleContext, ...args: RuleParameter[]): boolean { + if (!args || args.length === 0) { + return false; + } + + return args + .map(arg => context.evaluators[arg.value]) + .some(evaluator => evaluator(context)); +} diff --git a/src/app/extensions/rules/rule-context.ts b/src/app/extensions/rules/rule-context.ts new file mode 100644 index 000000000..80ac871fe --- /dev/null +++ b/src/app/extensions/rules/rule-context.ts @@ -0,0 +1,34 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 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 . + */ + +import { SelectionState } from '../../store/states'; +import { RuleEvaluator } from './rule.service'; +import { NavigationState } from '../../store/states/navigation.state'; + +export interface RuleContext { + selection: SelectionState; + navigation: NavigationState; + evaluators: { [key: string]: RuleEvaluator }; +} diff --git a/src/app/extensions/rules/rule-parameter.ts b/src/app/extensions/rules/rule-parameter.ts new file mode 100644 index 000000000..798976503 --- /dev/null +++ b/src/app/extensions/rules/rule-parameter.ts @@ -0,0 +1,29 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 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 . + */ + +export interface RuleParameter { + type: string; + value: any; +} diff --git a/src/app/extensions/rules/rule-ref.ts b/src/app/extensions/rules/rule-ref.ts new file mode 100644 index 000000000..da8fdb7fd --- /dev/null +++ b/src/app/extensions/rules/rule-ref.ts @@ -0,0 +1,34 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 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 . + */ + +import { RuleParameter } from './rule-parameter'; +import { RuleEvaluator } from './rule.service'; + +export class RuleRef { + type: string; + id?: string; + parameters?: Array; + evaluator?: RuleEvaluator; +} diff --git a/src/app/extensions/rules/rule.service.ts b/src/app/extensions/rules/rule.service.ts new file mode 100644 index 000000000..34daf6063 --- /dev/null +++ b/src/app/extensions/rules/rule.service.ts @@ -0,0 +1,97 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 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 . + */ + +import { Injectable } from '@angular/core'; +import { AppConfigService } from '@alfresco/adf-core'; +import { every, some } from './core.evaluators'; +import { RuleContext } from './rule-context'; +import { RuleRef } from './rule-ref'; +import { createSelector, Store } from '@ngrx/store'; +import { + appSelection, + appNavigation +} from '../../store/selectors/app.selectors'; +import { AppStore, SelectionState } from '../../store/states'; +import { NavigationState } from '../../store/states/navigation.state'; +import { canCreateFolder, hasFolderSelected, canUpdateSelectedFolder, hasFileSelected, canDownloadSelection } from './app.evaluators'; + +export type RuleEvaluator = (context: RuleContext, ...args: any[]) => boolean; + +export const selectionWithFolder = createSelector( + appSelection, + appNavigation, + (selection, navigation) => { + return { + selection, + navigation + }; + } +); + +@Injectable() +export class RuleService implements RuleContext { + rules: Array = []; + evaluators: { [key: string]: RuleEvaluator } = {}; + selection: SelectionState; + navigation: NavigationState; + + constructor( + private config: AppConfigService, + private store: Store + ) { + this.evaluators['core.every'] = every; + this.evaluators['core.some'] = some; + this.evaluators['app.selection.canDownload'] = canDownloadSelection; + this.evaluators['app.selection.file'] = hasFileSelected; + this.evaluators['app.selection.folder'] = hasFolderSelected; + this.evaluators['app.selection.folder.canUpdate'] = canUpdateSelectedFolder; + this.evaluators['app.navigation.folder.canCreate'] = canCreateFolder; + + this.store + .select(selectionWithFolder) + .subscribe(result => { + this.selection = result.selection; + this.navigation = result.navigation; + }); + } + + init() { + this.rules = this.config + .get>('extensions.core.rules', []) + .map(rule => { + rule.evaluator = this.evaluators[rule.type]; + return rule; + }); + } + + evaluateRule(ruleId: string): boolean { + const ruleRef = this.rules.find(ref => ref.id === ruleId); + + if (ruleRef.evaluator) { + return ruleRef.evaluator(this, ...ruleRef.parameters); + } + return false; + } +} diff --git a/src/app/store/selectors/app.selectors.ts b/src/app/store/selectors/app.selectors.ts index 49277c9d8..da23f79e1 100644 --- a/src/app/store/selectors/app.selectors.ts +++ b/src/app/store/selectors/app.selectors.ts @@ -34,4 +34,5 @@ export const appSelection = createSelector(selectApp, state => state.selection) export const appLanguagePicker = createSelector(selectApp, state => state.languagePicker); export const selectUser = createSelector(selectApp, state => state.user); export const sharedUrl = createSelector(selectApp, state => state.sharedUrl); +export const appNavigation = createSelector(selectApp, state => state.navigation); export const currentFolder = createSelector(selectApp, state => state.navigation.currentFolder); diff --git a/src/app/testing/app-testing.module.ts b/src/app/testing/app-testing.module.ts index ee3142e6d..1376ba23b 100644 --- a/src/app/testing/app-testing.module.ts +++ b/src/app/testing/app-testing.module.ts @@ -60,6 +60,7 @@ import { NodeActionsService } from '../common/services/node-actions.service'; import { NodePermissionService } from '../common/services/node-permission.service'; import { ContentApiService } from '../services/content-api.service'; import { ExtensionService } from '../extensions/extension.service'; +import { RuleService } from '../extensions/rules/rule.service'; @NgModule({ imports: [ @@ -112,7 +113,8 @@ import { ExtensionService } from '../extensions/extension.service'; NodeActionsService, NodePermissionService, ContentApiService, - ExtensionService + ExtensionService, + RuleService ] }) export class AppTestingModule {}