mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-24 17:31:52 +00:00
[ACA-1544] extensions: toolbar separators and menus (#504)
* toolbar separators * remove the need for "target" for separators * simplify code * menu stub, reducing separators * toolbar action component * render menu items * menu items
This commit is contained in:
@@ -200,8 +200,13 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"actions": [
|
"actions": [
|
||||||
{
|
{
|
||||||
"disabled": false,
|
"id": "aca:toolbar/separator-1",
|
||||||
|
"order": 5,
|
||||||
|
"type": "separator"
|
||||||
|
},
|
||||||
|
{
|
||||||
"id": "aca:toolbar/create-folder",
|
"id": "aca:toolbar/create-folder",
|
||||||
|
"type": "button",
|
||||||
"order": 10,
|
"order": 10,
|
||||||
"title": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER",
|
"title": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER",
|
||||||
"icon": "create_new_folder",
|
"icon": "create_new_folder",
|
||||||
@@ -212,8 +217,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"disabled": false,
|
|
||||||
"id": "aca:toolbar/preview",
|
"id": "aca:toolbar/preview",
|
||||||
|
"type": "button",
|
||||||
"order": 15,
|
"order": 15,
|
||||||
"title": "APP.ACTIONS.VIEW",
|
"title": "APP.ACTIONS.VIEW",
|
||||||
"icon": "open_in_browser",
|
"icon": "open_in_browser",
|
||||||
@@ -224,8 +229,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"disabled": false,
|
|
||||||
"id": "aca:toolbar/download",
|
"id": "aca:toolbar/download",
|
||||||
|
"type": "button",
|
||||||
"order": 20,
|
"order": 20,
|
||||||
"title": "APP.ACTIONS.DOWNLOAD",
|
"title": "APP.ACTIONS.DOWNLOAD",
|
||||||
"icon": "get_app",
|
"icon": "get_app",
|
||||||
@@ -237,8 +242,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"disabled": false,
|
|
||||||
"id": "aca:toolbar/edit-folder",
|
"id": "aca:toolbar/edit-folder",
|
||||||
|
"type": "button",
|
||||||
"order": 30,
|
"order": 30,
|
||||||
"title": "APP.ACTIONS.EDIT",
|
"title": "APP.ACTIONS.EDIT",
|
||||||
"icon": "create",
|
"icon": "create",
|
||||||
@@ -249,30 +254,44 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"disabled": false,
|
"id": "aca:toolbar/separator-2",
|
||||||
"id": "aca:action3",
|
"order": 200,
|
||||||
"order": 101,
|
"type": "separator"
|
||||||
"title": "Settings",
|
|
||||||
"icon": "settings_applications",
|
|
||||||
"target": {
|
|
||||||
"types": [],
|
|
||||||
"permissions": [],
|
|
||||||
"action": "aca:actions/settings"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"disabled": false,
|
"id": "aca:toolbar/menu-1",
|
||||||
"id": "aca:action4",
|
"type": "menu",
|
||||||
"order": 101,
|
"icon": "storage",
|
||||||
"title": "Error",
|
"order": 300,
|
||||||
"icon": "report_problem",
|
"children": [
|
||||||
"target": {
|
{
|
||||||
"types": ["file"],
|
"id": "aca:action3",
|
||||||
"permissions": ["update", "delete"],
|
"type": "button",
|
||||||
"action": "aca:actions/error"
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aca:toolbar/separator-3",
|
||||||
|
"type": "separator"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -13,15 +13,9 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ifExperimental="'extensions'">
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
<ng-container *ngFor="let entry of actions">
|
||||||
<button *ngFor="let entry of actions"
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
mat-icon-button
|
</ng-container>
|
||||||
color="primary"
|
|
||||||
title="{{ entry.title | translate }}"
|
|
||||||
(click)="runAction(entry.target.action)">
|
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
<ng-container *ngIf="!selection.isEmpty">
|
||||||
|
@@ -16,15 +16,9 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ifExperimental="'extensions'">
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
<ng-container *ngFor="let entry of actions">
|
||||||
<button *ngFor="let entry of actions"
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
mat-icon-button
|
</ng-container>
|
||||||
color="primary"
|
|
||||||
title="{{ entry.title | translate }}"
|
|
||||||
(click)="runAction(entry.target.action)">
|
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
<ng-container *ngIf="!selection.isEmpty">
|
||||||
|
@@ -83,7 +83,8 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
if (selection.isEmpty) {
|
if (selection.isEmpty) {
|
||||||
this.infoDrawerOpened = false;
|
this.infoDrawerOpened = false;
|
||||||
}
|
}
|
||||||
this.actions = this.extensions.getSelectedContentActions(selection, this.node);
|
const selectedNodes = selection ? selection.nodes : null;
|
||||||
|
this.actions = this.extensions.getAllowedContentActions(selectedNodes, this.node);
|
||||||
this.canUpdateFile = this.selection.file && this.content.canUpdateNode(selection.file);
|
this.canUpdateFile = this.selection.file && this.content.canUpdateNode(selection.file);
|
||||||
this.canUpdateNode = this.selection.count === 1 && this.content.canUpdateNode(selection.first);
|
this.canUpdateNode = this.selection.count === 1 && this.content.canUpdateNode(selection.first);
|
||||||
this.canDelete = !this.selection.isEmpty && this.content.canDeleteNodes(selection.nodes);
|
this.canDelete = !this.selection.isEmpty && this.content.canDeleteNodes(selection.nodes);
|
||||||
|
@@ -13,15 +13,9 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ifExperimental="'extensions'">
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
<ng-container *ngFor="let entry of actions">
|
||||||
<button *ngFor="let entry of actions"
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
mat-icon-button
|
</ng-container>
|
||||||
color="primary"
|
|
||||||
title="{{ entry.title | translate }}"
|
|
||||||
(click)="runAction(entry.target.action)">
|
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
<ng-container *ngIf="!selection.isEmpty">
|
||||||
|
@@ -4,15 +4,9 @@
|
|||||||
</adf-breadcrumb>
|
</adf-breadcrumb>
|
||||||
<adf-toolbar class="inline">
|
<adf-toolbar class="inline">
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ifExperimental="'extensions'">
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
<ng-container *ngFor="let entry of actions">
|
||||||
<button *ngFor="let entry of actions"
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
mat-icon-button
|
</ng-container>
|
||||||
color="primary"
|
|
||||||
title="{{ entry.title | translate }}"
|
|
||||||
(click)="runAction(entry.target.action)">
|
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
<ng-container *ngIf="!selection.isEmpty">
|
||||||
|
@@ -13,15 +13,9 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ifExperimental="'extensions'">
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
<ng-container *ngFor="let entry of actions">
|
||||||
<button *ngFor="let entry of actions"
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
mat-icon-button
|
</ng-container>
|
||||||
color="primary"
|
|
||||||
title="{{ entry.title | translate }}"
|
|
||||||
(click)="runAction(entry.target.action)">
|
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
<ng-container *ngIf="!selection.isEmpty">
|
||||||
|
@@ -13,15 +13,9 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ng-container *ifExperimental="'extensions'">
|
<ng-container *ifExperimental="'extensions'">
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
<ng-container *ngFor="let entry of actions">
|
||||||
<button *ngFor="let entry of actions"
|
<aca-toolbar-action [entry]="entry"></aca-toolbar-action>
|
||||||
mat-icon-button
|
</ng-container>
|
||||||
color="primary"
|
|
||||||
title="{{ entry.title | translate }}"
|
|
||||||
(click)="runAction(entry.target.action)">
|
|
||||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
<adf-toolbar-divider></adf-toolbar-divider>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="!selection.isEmpty">
|
<ng-container *ngIf="!selection.isEmpty">
|
||||||
|
@@ -0,0 +1,30 @@
|
|||||||
|
<ng-container [ngSwitch]="entry.type">
|
||||||
|
<button *ngSwitchCase="'button'"
|
||||||
|
mat-icon-button
|
||||||
|
color="primary"
|
||||||
|
title="{{ entry.title | translate }}"
|
||||||
|
(click)="runAction(entry.target.action)">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
<adf-toolbar-divider *ngSwitchCase="'separator'"></adf-toolbar-divider>
|
||||||
|
<ng-container *ngSwitchCase="'menu'">
|
||||||
|
<button
|
||||||
|
color="primary"
|
||||||
|
mat-icon-button
|
||||||
|
title="{{ entry.title | translate }}"
|
||||||
|
[matMenuTriggerFor]="menu">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<mat-menu #menu="matMenu"
|
||||||
|
[overlapTrigger]="false">
|
||||||
|
<ng-container *ngFor="let child of entry.children">
|
||||||
|
<button mat-menu-item
|
||||||
|
(click)="runAction(child.target.action)">
|
||||||
|
<mat-icon>{{ child.icon }}</mat-icon>
|
||||||
|
<span>{{ child.title | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</mat-menu>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
@@ -0,0 +1,81 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
ViewEncapsulation,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy
|
||||||
|
} from '@angular/core';
|
||||||
|
import { ContentActionExtension } from '../../content-action.extension';
|
||||||
|
import { AppStore, SelectionState } from '../../../store/states';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { ExtensionService } from '../../extension.service';
|
||||||
|
import { appSelection } from '../../../store/selectors/app.selectors';
|
||||||
|
import { Subject } from 'rxjs/Rx';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'aca-toolbar-action',
|
||||||
|
templateUrl: './toolbar-action.component.html',
|
||||||
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
host: { class: 'aca-toolbar-action' }
|
||||||
|
})
|
||||||
|
export class ToolbarActionComponent implements OnInit, OnDestroy {
|
||||||
|
@Input() entry: ContentActionExtension;
|
||||||
|
|
||||||
|
selection: SelectionState;
|
||||||
|
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected store: Store<AppStore>,
|
||||||
|
protected extensions: ExtensionService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.store
|
||||||
|
.select(appSelection)
|
||||||
|
.pipe(takeUntil(this.onDestroy$))
|
||||||
|
.subscribe(selection => {
|
||||||
|
this.selection = selection;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.onDestroy$.next(true);
|
||||||
|
this.onDestroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
runAction(actionId: string) {
|
||||||
|
const context = {
|
||||||
|
selection: this.selection
|
||||||
|
};
|
||||||
|
|
||||||
|
this.extensions.runActionById(actionId, context);
|
||||||
|
}
|
||||||
|
}
|
@@ -23,12 +23,21 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export enum ContentActionType {
|
||||||
|
default = 'button',
|
||||||
|
button = 'button',
|
||||||
|
separator = 'separator',
|
||||||
|
menu = 'menu'
|
||||||
|
}
|
||||||
|
|
||||||
export interface ContentActionExtension {
|
export interface ContentActionExtension {
|
||||||
id: string;
|
id: string;
|
||||||
|
type: ContentActionType;
|
||||||
order?: number;
|
order?: number;
|
||||||
title: string;
|
title: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
children?: Array<ContentActionExtension>;
|
||||||
target: {
|
target: {
|
||||||
types: Array<string>;
|
types: Array<string>;
|
||||||
permissions: Array<string>,
|
permissions: Array<string>,
|
||||||
|
@@ -24,14 +24,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { AuthGuardEcm } from '@alfresco/adf-core';
|
import { AuthGuardEcm, CoreModule } from '@alfresco/adf-core';
|
||||||
import { ExtensionService } from './extension.service';
|
import { ExtensionService } from './extension.service';
|
||||||
import { AboutComponent } from '../components/about/about.component';
|
import { AboutComponent } from '../components/about/about.component';
|
||||||
import { LayoutComponent } from '../components/layout/layout.component';
|
import { LayoutComponent } from '../components/layout/layout.component';
|
||||||
|
import { ToolbarActionComponent } from './components/toolbar-action/toolbar-action.component';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [],
|
imports: [
|
||||||
declarations: [],
|
CommonModule,
|
||||||
|
CoreModule.forChild()
|
||||||
|
],
|
||||||
|
declarations: [ToolbarActionComponent],
|
||||||
|
exports: [ToolbarActionComponent],
|
||||||
entryComponents: [AboutComponent]
|
entryComponents: [AboutComponent]
|
||||||
})
|
})
|
||||||
export class CoreExtensionsModule {
|
export class CoreExtensionsModule {
|
||||||
|
@@ -27,13 +27,14 @@ import { Injectable, Type } from '@angular/core';
|
|||||||
import { RouteExtension } from './route.extension';
|
import { RouteExtension } from './route.extension';
|
||||||
import { ActionExtension } from './action.extension';
|
import { ActionExtension } from './action.extension';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
import { AppConfigService } from '@alfresco/adf-core';
|
||||||
import { ContentActionExtension } from './content-action.extension';
|
import { ContentActionExtension, ContentActionType } from './content-action.extension';
|
||||||
import { OpenWithExtension } from './open-with.extension';
|
import { OpenWithExtension } from './open-with.extension';
|
||||||
import { AppStore, SelectionState } from '../store/states';
|
import { AppStore } from '../store/states';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { NavigationExtension } from './navigation.extension';
|
import { NavigationExtension } from './navigation.extension';
|
||||||
import { Route } from '@angular/router';
|
import { Route } from '@angular/router';
|
||||||
import { Node } from 'alfresco-js-api';
|
import { Node, MinimalNodeEntity } from 'alfresco-js-api';
|
||||||
|
import { reduceSeparators, sortByOrder, filterEnabled, copyAction, reduceEmptyMenus } from './utils';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExtensionService {
|
export class ExtensionService {
|
||||||
@@ -70,7 +71,7 @@ export class ExtensionService {
|
|||||||
'extensions.core.features.content.actions',
|
'extensions.core.features.content.actions',
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
.sort(this.sortByOrder);
|
.sort(sortByOrder);
|
||||||
|
|
||||||
this.openWithActions = this.config
|
this.openWithActions = this.config
|
||||||
.get<Array<OpenWithExtension>>(
|
.get<Array<OpenWithExtension>>(
|
||||||
@@ -78,14 +79,14 @@ export class ExtensionService {
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
.filter(entry => !entry.disabled)
|
.filter(entry => !entry.disabled)
|
||||||
.sort(this.sortByOrder);
|
.sort(sortByOrder);
|
||||||
|
|
||||||
this.createActions = this.config
|
this.createActions = this.config
|
||||||
.get<Array<ContentActionExtension>>(
|
.get<Array<ContentActionExtension>>(
|
||||||
'extensions.core.features.create',
|
'extensions.core.features.create',
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
.sort(this.sortByOrder);
|
.sort(sortByOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRouteById(id: string): RouteExtension {
|
getRouteById(id: string): RouteExtension {
|
||||||
@@ -178,7 +179,7 @@ export class ExtensionService {
|
|||||||
|
|
||||||
// evaluates create actions for the folder node
|
// evaluates create actions for the folder node
|
||||||
getFolderCreateActions(folder: Node): Array<ContentActionExtension> {
|
getFolderCreateActions(folder: Node): Array<ContentActionExtension> {
|
||||||
return this.createActions.filter(this.filterOutDisabled).map(action => {
|
return this.createActions.filter(filterEnabled).map(action => {
|
||||||
if (
|
if (
|
||||||
action.target &&
|
action.target &&
|
||||||
action.target.permissions &&
|
action.target.permissions &&
|
||||||
@@ -200,64 +201,72 @@ export class ExtensionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// evaluates content actions for the selection and parent folder node
|
// evaluates content actions for the selection and parent folder node
|
||||||
getSelectedContentActions(
|
getAllowedContentActions(
|
||||||
selection: SelectionState,
|
nodes: MinimalNodeEntity[],
|
||||||
parentNode: Node
|
parentNode: Node
|
||||||
): Array<ContentActionExtension> {
|
): Array<ContentActionExtension> {
|
||||||
return this.contentActions
|
return this.contentActions
|
||||||
.filter(this.filterOutDisabled)
|
.filter(filterEnabled)
|
||||||
.filter(action => action.target)
|
.filter(action => this.filterByTarget(nodes, action))
|
||||||
.filter(action => this.filterByTarget(selection, action))
|
.filter(action => this.filterByPermission(nodes, action, parentNode))
|
||||||
.filter(action =>
|
.reduce(reduceSeparators, [])
|
||||||
this.filterByPermission(selection, action, parentNode)
|
.map(action => {
|
||||||
);
|
if (action.type === ContentActionType.menu) {
|
||||||
|
const copy = copyAction(action);
|
||||||
|
if (copy.children && copy.children.length > 0) {
|
||||||
|
copy.children = copy.children
|
||||||
|
.filter(childAction => this.filterByTarget(nodes, childAction))
|
||||||
|
.filter(childAction => this.filterByPermission(nodes, childAction, parentNode))
|
||||||
|
.reduce(reduceSeparators, []);
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
})
|
||||||
|
.reduce(reduceEmptyMenus, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sortByOrder(
|
|
||||||
a: { order?: number | undefined },
|
|
||||||
b: { order?: number | undefined }
|
|
||||||
) {
|
|
||||||
const left = a.order === undefined ? Number.MAX_SAFE_INTEGER : a.order;
|
|
||||||
const right = b.order === undefined ? Number.MAX_SAFE_INTEGER : b.order;
|
|
||||||
return left - right;
|
|
||||||
}
|
|
||||||
|
|
||||||
private filterOutDisabled(entry: { disabled?: boolean }): boolean {
|
|
||||||
return !entry.disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: support multiple selected nodes
|
|
||||||
private filterByTarget(
|
private filterByTarget(
|
||||||
selection: SelectionState,
|
nodes: MinimalNodeEntity[],
|
||||||
action: ContentActionExtension
|
action: ContentActionExtension
|
||||||
): boolean {
|
): boolean {
|
||||||
|
|
||||||
|
if (!action) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!action.target) {
|
||||||
|
return action.type === ContentActionType.separator
|
||||||
|
|| action.type === ContentActionType.menu;
|
||||||
|
}
|
||||||
|
|
||||||
const types = action.target.types;
|
const types = action.target.types;
|
||||||
|
|
||||||
if (!types || types.length === 0) {
|
if (!types || types.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection && !selection.isEmpty) {
|
if (nodes && nodes.length > 0) {
|
||||||
|
|
||||||
if (selection.nodes.length === 1) {
|
if (nodes.length === 1) {
|
||||||
if (selection.folder && types.includes('folder')) {
|
if (types.includes('folder')) {
|
||||||
return true;
|
return nodes.every(node => node.entry.isFolder);
|
||||||
}
|
}
|
||||||
if (selection.file && types.includes('file')) {
|
if (types.includes('file')) {
|
||||||
return true;
|
return nodes.every(node => node.entry.isFile);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if (types.length === 1) {
|
if (types.length === 1) {
|
||||||
if (types.includes('folder')) {
|
if (types.includes('folder')) {
|
||||||
if (action.target.multiple) {
|
if (action.target.multiple) {
|
||||||
return selection.nodes.every(node => node.entry.isFolder);
|
return nodes.every(node => node.entry.isFolder);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (types.includes('file')) {
|
if (types.includes('file')) {
|
||||||
if (action.target.multiple) {
|
if (action.target.multiple) {
|
||||||
return selection.nodes.every(node => node.entry.isFile);
|
return nodes.every(node => node.entry.isFile);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -265,13 +274,13 @@ export class ExtensionService {
|
|||||||
return types.some(type => {
|
return types.some(type => {
|
||||||
if (type === 'folder') {
|
if (type === 'folder') {
|
||||||
return action.target.multiple
|
return action.target.multiple
|
||||||
? selection.nodes.some(node => node.entry.isFolder)
|
? nodes.some(node => node.entry.isFolder)
|
||||||
: selection.nodes.every(node => node.entry.isFolder);
|
: nodes.every(node => node.entry.isFolder);
|
||||||
}
|
}
|
||||||
if (type === 'file') {
|
if (type === 'file') {
|
||||||
return action.target.multiple
|
return action.target.multiple
|
||||||
? selection.nodes.some(node => node.entry.isFile)
|
? nodes.some(node => node.entry.isFile)
|
||||||
: selection.nodes.every(node => node.entry.isFile);
|
: nodes.every(node => node.entry.isFile);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@@ -284,10 +293,19 @@ export class ExtensionService {
|
|||||||
|
|
||||||
// todo: support multiple selected nodes
|
// todo: support multiple selected nodes
|
||||||
private filterByPermission(
|
private filterByPermission(
|
||||||
selection: SelectionState,
|
nodes: MinimalNodeEntity[],
|
||||||
action: ContentActionExtension,
|
action: ContentActionExtension,
|
||||||
parentNode: Node
|
parentNode: Node
|
||||||
): boolean {
|
): boolean {
|
||||||
|
if (!action) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!action.target) {
|
||||||
|
return action.type === ContentActionType.separator
|
||||||
|
|| action.type === ContentActionType.menu;
|
||||||
|
}
|
||||||
|
|
||||||
const permissions = action.target.permissions;
|
const permissions = action.target.permissions;
|
||||||
|
|
||||||
if (!permissions || permissions.length === 0) {
|
if (!permissions || permissions.length === 0) {
|
||||||
@@ -298,15 +316,14 @@ export class ExtensionService {
|
|||||||
if (permission.startsWith('parent.')) {
|
if (permission.startsWith('parent.')) {
|
||||||
if (parentNode) {
|
if (parentNode) {
|
||||||
const parentQuery = permission.split('.')[1];
|
const parentQuery = permission.split('.')[1];
|
||||||
// console.log(parentNode.allowableOperations, parentQuery);
|
|
||||||
return this.nodeHasPermissions(parentNode, [parentQuery]);
|
return this.nodeHasPermissions(parentNode, [parentQuery]);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection && selection.first) {
|
if (nodes && nodes.length > 0) {
|
||||||
return this.nodeHasPermissions(
|
return this.nodeHasPermissions(
|
||||||
selection.first.entry,
|
nodes[0].entry,
|
||||||
permissions
|
permissions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
87
src/app/extensions/utils.ts
Normal file
87
src/app/extensions/utils.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ContentActionExtension,
|
||||||
|
ContentActionType
|
||||||
|
} from './content-action.extension';
|
||||||
|
|
||||||
|
export function reduceSeparators(
|
||||||
|
acc: ContentActionExtension[],
|
||||||
|
el: ContentActionExtension,
|
||||||
|
i: number,
|
||||||
|
arr: ContentActionExtension[]
|
||||||
|
): ContentActionExtension[] {
|
||||||
|
// remove duplicate separators
|
||||||
|
if (i > 0) {
|
||||||
|
const prev = arr[i - 1];
|
||||||
|
if (
|
||||||
|
prev.type === ContentActionType.separator &&
|
||||||
|
el.type === ContentActionType.separator
|
||||||
|
) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove trailing separator
|
||||||
|
if (i === arr.length - 1) {
|
||||||
|
if (el.type === ContentActionType.separator) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc.concat(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function reduceEmptyMenus(
|
||||||
|
acc: ContentActionExtension[],
|
||||||
|
el: ContentActionExtension
|
||||||
|
): ContentActionExtension[] {
|
||||||
|
if (el.type === ContentActionType.menu) {
|
||||||
|
if ((el.children || []).length === 0) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc.concat(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortByOrder(
|
||||||
|
a: { order?: number | undefined },
|
||||||
|
b: { order?: number | undefined }
|
||||||
|
) {
|
||||||
|
const left = a.order === undefined ? Number.MAX_SAFE_INTEGER : a.order;
|
||||||
|
const right = b.order === undefined ? Number.MAX_SAFE_INTEGER : b.order;
|
||||||
|
return left - right;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterEnabled(entry: { disabled?: boolean }): boolean {
|
||||||
|
return !entry.disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copyAction(action: ContentActionExtension): ContentActionExtension {
|
||||||
|
return {
|
||||||
|
...action,
|
||||||
|
children: (action.children || []).map(copyAction)
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user