diff --git a/src/app.config.json b/src/app.config.json index a7b4d95cd..372c1a261 100644 --- a/src/app.config.json +++ b/src/app.config.json @@ -54,6 +54,17 @@ } ], "actions": [ + { + "id": "aca:actions/create-folder", + "type": "CREATE_FOLDER", + "payload": null + }, + { + "id": "aca:actions/edit-folder", + "type": "EDIT_FOLDER", + "payload": null + }, + { "id": "aca:actions/info", "type": "SNACKBAR_INFO", @@ -78,11 +89,15 @@ "features": { "create": [ { - "id": "aca:create/action1", + "disabled": false, + "id": "aca:create/folder", "order": 100, - "icon": "build", - "title": "Error", - "action": "aca:actions/error" + "icon": "create_new_folder", + "title": "ext: Create Folder", + "target": { + "permissions": ["create"], + "action": "aca:actions/create-folder" + } } ], "navigation": { @@ -175,28 +190,29 @@ "actions": [ { "disabled": false, - "id": "aca:action1", - "order": 100, - "title": "Info", - "icon": "build", + "id": "aca:toolbar/create-folder", + "order": 10, + "title": "APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER", + "icon": "create_new_folder", "target": { - "type": "folder", - "permissions": ["one", "two"], - "action": "aca:actions/info" + "types": [], + "permissions": ["parent.create"], + "action": "aca:actions/create-folder" } }, { "disabled": false, - "id": "aca:action2", - "order": 101, - "title": "Node name", - "icon": "feedback", + "id": "aca:toolbar/edit-folder", + "order": 20, + "title": "APP.ACTIONS.EDIT", + "icon": "create", "target": { - "type": "folder", - "permissions": ["one", "two"], - "action": "aca:actions/node-name" + "types": ["folder"], + "permissions": ["update"], + "action": "aca:actions/edit-folder" } }, + { "disabled": false, "id": "aca:action3", @@ -204,8 +220,8 @@ "title": "Settings", "icon": "settings_applications", "target": { - "type": "folder", - "permissions": ["one", "two"], + "types": [], + "permissions": [], "action": "aca:actions/settings" } }, @@ -216,8 +232,8 @@ "title": "Error", "icon": "report_problem", "target": { - "type": "file", - "permissions": ["one", "two"], + "types": ["file"], + "permissions": ["update", "delete"], "action": "aca:actions/error" } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 888b18fec..c3c5937ab 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -61,7 +61,6 @@ import { NodePermanentDeleteDirective } from './common/directives/node-permanent import { NodeUnshareDirective } from './common/directives/node-unshare.directive'; import { NodeVersionsDirective } from './common/directives/node-versions.directive'; import { NodeVersionsDialogComponent } from './dialogs/node-versions/node-versions.dialog'; -import { BrowsingFilesService } from './common/services/browsing-files.service'; import { ContentManagementService } from './common/services/content-management.service'; import { NodeActionsService } from './common/services/node-actions.service'; import { NodePermissionService } from './common/services/node-permission.service'; @@ -72,7 +71,6 @@ import { ExperimentalGuard } from './common/services/experimental-guard.service' import { InfoDrawerComponent } from './components/info-drawer/info-drawer.component'; import { EditFolderDirective } from './directives/edit-folder.directive'; -import { CreateFolderDirective } from './directives/create-folder.directive'; import { DownloadNodesDirective } from './directives/download-nodes.directive'; import { AppStoreModule } from './store/app-store.module'; import { PaginationDirective } from './directives/pagination.directive'; @@ -136,7 +134,6 @@ import { SearchResultsRowComponent } from './components/search/search-results-ro InfoDrawerComponent, SharedLinkViewComponent, EditFolderDirective, - CreateFolderDirective, DownloadNodesDirective, PaginationDirective, DocumentListDirective, @@ -152,7 +149,6 @@ import { SearchResultsRowComponent } from './components/search/search-results-ro source: 'assets' } }, - BrowsingFilesService, ContentManagementService, NodeActionsService, NodePermissionService, diff --git a/src/app/common/services/browsing-files.service.spec.ts b/src/app/common/services/browsing-files.service.spec.ts deleted file mode 100644 index fea859cf5..000000000 --- a/src/app/common/services/browsing-files.service.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * @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 { BrowsingFilesService } from './browsing-files.service'; - -describe('BrowsingFilesService', () => { - let service: BrowsingFilesService; - - beforeEach(() => { - service = new BrowsingFilesService(); - }); - - it('subscribes to event', () => { - const value: any = 'test-value'; - - service.onChangeParent.subscribe((result) => { - expect(result).toBe(value); - }); - - service.onChangeParent.next(value); - }); -}); diff --git a/src/app/common/services/browsing-files.service.ts b/src/app/common/services/browsing-files.service.ts deleted file mode 100644 index e55f7e723..000000000 --- a/src/app/common/services/browsing-files.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*! - * @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 { Subject } from 'rxjs/Rx'; -import { Injectable } from '@angular/core'; - -import { MinimalNodeEntryEntity } from 'alfresco-js-api'; - -@Injectable() -export class BrowsingFilesService { - onChangeParent = new Subject(); -} diff --git a/src/app/common/services/content-management.service.ts b/src/app/common/services/content-management.service.ts index 0d9e7284a..4827297c2 100644 --- a/src/app/common/services/content-management.service.ts +++ b/src/app/common/services/content-management.service.ts @@ -25,6 +25,12 @@ import { Subject } from 'rxjs/Rx'; import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material'; +import { FolderDialogComponent } from '@alfresco/adf-content-services'; +import { SnackbarErrorAction } from '../../store/actions'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states'; +import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; @Injectable() export class ContentManagementService { @@ -36,4 +42,50 @@ export class ContentManagementService { folderCreated = new Subject(); siteDeleted = new Subject(); linksUnshared = new Subject(); + + constructor(private store: Store, private dialogRef: MatDialog) {} + + createFolder(parentNodeId: string) { + const dialogInstance = this.dialogRef.open(FolderDialogComponent, { + data: { + parentNodeId: parentNodeId, + createTitle: undefined, + nodeType: 'cm:folder' + }, + width: '400px' + }); + + dialogInstance.componentInstance.error.subscribe(message => { + this.store.dispatch(new SnackbarErrorAction(message)); + }); + + dialogInstance.afterClosed().subscribe(node => { + if (node) { + this.folderCreated.next(node); + } + }); + } + + editFolder(folder: MinimalNodeEntity) { + if (!folder) { + return; + } + + const dialog = this.dialogRef.open(FolderDialogComponent, { + data: { + folder: folder.entry + }, + width: '400px' + }); + + dialog.componentInstance.error.subscribe(message => { + this.store.dispatch(new SnackbarErrorAction(message)); + }); + + dialog.afterClosed().subscribe((node: MinimalNodeEntryEntity) => { + if (node) { + this.folderEdited.next(node); + } + }); + } } diff --git a/src/app/components/favorites/favorites.component.html b/src/app/components/favorites/favorites.component.html index 67b3c0cfe..78a200c0e 100644 --- a/src/app/components/favorites/favorites.component.html +++ b/src/app/components/favorites/favorites.component.html @@ -3,14 +3,17 @@ - - + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - -
diff --git a/src/app/components/files/files.component.html b/src/app/components/files/files.component.html index 947e8da3b..033540933 100644 --- a/src/app/components/files/files.component.html +++ b/src/app/components/files/files.component.html @@ -6,14 +6,17 @@ (navigate)="onBreadcrumbNavigate($event)"> - - + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - -
diff --git a/src/app/components/files/files.component.spec.ts b/src/app/components/files/files.component.spec.ts index 07ddab203..678321691 100644 --- a/src/app/components/files/files.component.spec.ts +++ b/src/app/components/files/files.component.spec.ts @@ -33,7 +33,6 @@ import { } from '@alfresco/adf-core'; import { DocumentListComponent } from '@alfresco/adf-content-services'; import { ContentManagementService } from '../../common/services/content-management.service'; -import { BrowsingFilesService } from '../../common/services/browsing-files.service'; import { NodeActionsService } from '../../common/services/node-actions.service'; import { FilesComponent } from './files.component'; import { AppTestingModule } from '../../testing/app-testing.module'; @@ -48,7 +47,6 @@ describe('FilesComponent', () => { let contentManagementService: ContentManagementService; let uploadService: UploadService; let router: Router; - let browsingFilesService: BrowsingFilesService; let nodeActionsService: NodeActionsService; let contentApi: ContentApiService; @@ -86,7 +84,6 @@ describe('FilesComponent', () => { contentManagementService = TestBed.get(ContentManagementService); uploadService = TestBed.get(UploadService); router = TestBed.get(Router); - browsingFilesService = TestBed.get(BrowsingFilesService); nodeActionsService = TestBed.get(NodeActionsService); contentApi = TestBed.get(ContentApiService); }); @@ -146,17 +143,6 @@ describe('FilesComponent', () => { expect(component.fetchNodes).toHaveBeenCalled(); }); - it('emits onChangeParent event', () => { - spyOn(contentApi, 'getNode').and.returnValue(Observable.of({ entry: node })); - spyOn(component, 'fetchNodes').and.returnValue(Observable.of(page)); - spyOn(browsingFilesService.onChangeParent, 'next').and.callFake((val) => val); - - fixture.detectChanges(); - - expect(browsingFilesService.onChangeParent.next) - .toHaveBeenCalledWith(node); - }); - it('if should navigate to parent if node is not a folder', () => { node.isFolder = false; node.parentId = 'parent-id'; diff --git a/src/app/components/files/files.component.ts b/src/app/components/files/files.component.ts index ee74f4092..1b57e5fdb 100644 --- a/src/app/components/files/files.component.ts +++ b/src/app/components/files/files.component.ts @@ -29,7 +29,6 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { MinimalNodeEntity, MinimalNodeEntryEntity, NodePaging, PathElement, PathElementEntity } from 'alfresco-js-api'; import { Observable } from 'rxjs/Rx'; -import { BrowsingFilesService } from '../../common/services/browsing-files.service'; import { ContentManagementService } from '../../common/services/content-management.service'; import { NodeActionsService } from '../../common/services/node-actions.service'; import { NodePermissionService } from '../../common/services/node-permission.service'; @@ -37,6 +36,7 @@ import { AppStore } from '../../store/states/app.state'; import { PageComponent } from '../page.component'; import { ContentApiService } from '../../services/content-api.service'; import { ExtensionService } from '../../extensions/extension.service'; +import { SetCurrentFolderAction } from '../../store/actions'; @Component({ templateUrl: './files.component.html' @@ -54,7 +54,6 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { private nodeActionsService: NodeActionsService, private uploadService: UploadService, private contentManagementService: ContentManagementService, - private browsingFilesService: BrowsingFilesService, public permission: NodePermissionService, extensions: ExtensionService) { super(store, extensions); @@ -103,7 +102,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { ngOnDestroy() { super.ngOnDestroy(); - this.browsingFilesService.onChangeParent.next(null); + this.store.dispatch(new SetCurrentFolderAction(null)); } fetchNodes(parentNodeId?: string): Observable { @@ -222,7 +221,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy { } this.node = node; - this.browsingFilesService.onChangeParent.next(node); + this.store.dispatch(new SetCurrentFolderAction(node)); } // todo: review this approach once 5.2.3 is out diff --git a/src/app/components/layout/layout.component.html b/src/app/components/layout/layout.component.html index 52c37d768..6f10394a1 100644 --- a/src/app/components/layout/layout.component.html +++ b/src/app/components/layout/layout.component.html @@ -1,7 +1,7 @@
+ [disabled]="!canUpload"> { let fixture: ComponentFixture; let component: LayoutComponent; - let browsingFilesService: BrowsingFilesService; let appConfig: AppConfigService; let userPreference: UserPreferencesService; - const navItem = { - label: 'some-label', - route: { - url: '/some-url' - } - }; - beforeEach(() => { TestBed.configureTestingModule({ imports: [ AppTestingModule ], @@ -67,25 +57,10 @@ describe('LayoutComponent', () => { fixture = TestBed.createComponent(LayoutComponent); component = fixture.componentInstance; - browsingFilesService = TestBed.get(BrowsingFilesService); appConfig = TestBed.get(AppConfigService); userPreference = TestBed.get(UserPreferencesService); }); - it('sets current node', () => { - appConfig.config = { - navigation: [navItem] - }; - - const currentNode = { id: 'someId' }; - - fixture.detectChanges(); - - browsingFilesService.onChangeParent.next(currentNode); - - expect(component.node).toEqual(currentNode); - }); - describe('sidenav state', () => { it('should get state from configuration', () => { appConfig.config = { diff --git a/src/app/components/layout/layout.component.ts b/src/app/components/layout/layout.component.ts index 4f1cf43bc..3f6da924f 100644 --- a/src/app/components/layout/layout.component.ts +++ b/src/app/components/layout/layout.component.ts @@ -24,11 +24,14 @@ */ import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; -import { Subscription } from 'rxjs/Rx'; +import { Subject } from 'rxjs/Rx'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; -import { BrowsingFilesService } from '../../common/services/browsing-files.service'; import { NodePermissionService } from '../../common/services/node-permission.service'; import { SidenavViewsManagerDirective } from './sidenav-views-manager.directive'; +import { Store } from '@ngrx/store'; +import { AppStore } from '../../store/states'; +import { currentFolder } from '../../store/selectors/app.selectors'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-layout', @@ -38,14 +41,14 @@ import { SidenavViewsManagerDirective } from './sidenav-views-manager.directive' export class LayoutComponent implements OnInit, OnDestroy { @ViewChild(SidenavViewsManagerDirective) manager: SidenavViewsManagerDirective; + onDestroy$: Subject = new Subject(); expandedSidenav: boolean; node: MinimalNodeEntryEntity; - - private subscriptions: Subscription[] = []; + canUpload = false; constructor( - private browsingFilesService: BrowsingFilesService, - public permission: NodePermissionService) {} + protected store: Store, + private permission: NodePermissionService) {} ngOnInit() { if (!this.manager.minimizeSidenav) { @@ -56,12 +59,16 @@ export class LayoutComponent implements OnInit, OnDestroy { this.manager.run(true); - this.subscriptions.concat([ - this.browsingFilesService.onChangeParent.subscribe((node: MinimalNodeEntryEntity) => this.node = node) - ]); + this.store.select(currentFolder) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(node => { + this.node = node; + this.canUpload = this.permission.check(node, ['create']); + }); } ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()); + this.onDestroy$.next(true); + this.onDestroy$.complete(); } } diff --git a/src/app/components/libraries/libraries.component.html b/src/app/components/libraries/libraries.component.html index f46f6a0d1..85900e602 100644 --- a/src/app/components/libraries/libraries.component.html +++ b/src/app/components/libraries/libraries.component.html @@ -3,29 +3,33 @@ - - - - - + + + + + - + + + +
diff --git a/src/app/components/page.component.ts b/src/app/components/page.component.ts index d883f9b50..f2113c574 100644 --- a/src/app/components/page.component.ts +++ b/src/app/components/page.component.ts @@ -73,22 +73,8 @@ export abstract class PageComponent implements OnInit, OnDestroy { this.selection = selection; if (selection.isEmpty) { this.infoDrawerOpened = false; - this.actions = []; - } else { - this.actions = this.extensions.contentActions.filter(action => { - if (action.target && action.target.type) { - switch (action.target.type.toLowerCase()) { - case 'folder': - return selection.folder ? true : false; - case 'file': - return selection.file ? true : false; - default: - return false; - } - } - return false; - }); } + this.actions = this.extensions.getSelectedContentActions(selection, this.node); }); } diff --git a/src/app/components/recent-files/recent-files.component.html b/src/app/components/recent-files/recent-files.component.html index aba56b74c..5f57db181 100644 --- a/src/app/components/recent-files/recent-files.component.html +++ b/src/app/components/recent-files/recent-files.component.html @@ -3,14 +3,17 @@ - - + + + + + - - + + - + - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - diff --git a/src/app/components/search/search-results/search-results.component.html b/src/app/components/search/search-results/search-results.component.html index e80d449c3..6041de430 100644 --- a/src/app/components/search/search-results/search-results.component.html +++ b/src/app/components/search/search-results/search-results.component.html @@ -2,8 +2,9 @@
- + + + - - - - - - - - + - - - - - + + + + + + + + + + + + + + +
diff --git a/src/app/components/shared-files/shared-files.component.html b/src/app/components/shared-files/shared-files.component.html index 79ad47d63..648019342 100644 --- a/src/app/components/shared-files/shared-files.component.html +++ b/src/app/components/shared-files/shared-files.component.html @@ -3,14 +3,17 @@ - - + + + + + - - - - - - - - + + + - + + - + - - + + + + + + + + + diff --git a/src/app/components/sidenav/sidenav.component.html b/src/app/components/sidenav/sidenav.component.html index dd1e53610..851a0b6b4 100644 --- a/src/app/components/sidenav/sidenav.component.html +++ b/src/app/components/sidenav/sidenav.component.html @@ -9,17 +9,18 @@ - + + + + + - - + + + + + @@ -97,7 +104,7 @@ diff --git a/src/app/components/trashcan/trashcan.component.ts b/src/app/components/trashcan/trashcan.component.ts index f02a03dbf..f9e943342 100644 --- a/src/app/components/trashcan/trashcan.component.ts +++ b/src/app/components/trashcan/trashcan.component.ts @@ -31,17 +31,19 @@ import { selectUser } from '../../store/selectors/app.selectors'; import { AppStore } from '../../store/states/app.state'; import { ProfileState } from '../../store/states/profile.state'; import { ExtensionService } from '../../extensions/extension.service'; +import { Observable } from 'rxjs/Observable'; @Component({ templateUrl: './trashcan.component.html' }) export class TrashcanComponent extends PageComponent implements OnInit { - user: ProfileState; + user$: Observable; constructor(private contentManagementService: ContentManagementService, extensions: ExtensionService, store: Store) { super(store, extensions); + this.user$ = this.store.select(selectUser); } ngOnInit() { @@ -51,7 +53,6 @@ export class TrashcanComponent extends PageComponent implements OnInit { this.contentManagementService.nodesRestored.subscribe(() => this.reload()), this.contentManagementService.nodesPurged.subscribe(() => this.reload()), this.contentManagementService.nodesRestored.subscribe(() => this.reload()), - this.store.select(selectUser).subscribe((user) => this.user = user) ); } } diff --git a/src/app/directives/create-folder.directive.ts b/src/app/directives/create-folder.directive.ts deleted file mode 100644 index 4dcb19015..000000000 --- a/src/app/directives/create-folder.directive.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * @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 { Directive, HostListener, Input } from '@angular/core'; -import { MatDialog, MatDialogConfig } from '@angular/material'; -import { FolderDialogComponent } from '@alfresco/adf-content-services'; -import { Store } from '@ngrx/store'; -import { AppStore } from '../store/states/app.state'; -import { SnackbarErrorAction } from '../store/actions'; -import { ContentManagementService } from '../common/services/content-management.service'; - -@Directive({ - selector: '[acaCreateFolder]' -}) -export class CreateFolderDirective { - /** Parent folder where the new folder will be located after creation. */ - // tslint:disable-next-line:no-input-rename - @Input('acaCreateFolder') parentNodeId: string; - - /** Title of folder creation dialog. */ - @Input() dialogTitle: string = null; - - /** Type of node to create. */ - @Input() nodeType = 'cm:folder'; - - @HostListener('click', ['$event']) - onClick(event: Event) { - if (this.parentNodeId) { - event.preventDefault(); - this.openDialog(); - } - } - - constructor( - private store: Store, - private dialogRef: MatDialog, - private content: ContentManagementService - ) {} - - private get dialogConfig(): MatDialogConfig { - return { - data: { - parentNodeId: this.parentNodeId, - createTitle: this.dialogTitle, - nodeType: this.nodeType - }, - width: '400px' - }; - } - - private openDialog(): void { - const dialogInstance = this.dialogRef.open( - FolderDialogComponent, - this.dialogConfig - ); - - dialogInstance.componentInstance.error.subscribe(message => { - this.store.dispatch(new SnackbarErrorAction(message)); - }); - - dialogInstance.afterClosed().subscribe(node => { - if (node) { - this.content.folderCreated.next(node); - } - }); - } -} diff --git a/src/app/directives/edit-folder.directive.ts b/src/app/directives/edit-folder.directive.ts index e532ec87d..650e2c3bc 100644 --- a/src/app/directives/edit-folder.directive.ts +++ b/src/app/directives/edit-folder.directive.ts @@ -24,59 +24,24 @@ */ import { Directive, Input, HostListener } from '@angular/core'; -import { MinimalNodeEntryEntity, MinimalNodeEntity } from 'alfresco-js-api'; -import { MatDialog, MatDialogConfig } from '@angular/material'; -import { FolderDialogComponent } from '@alfresco/adf-content-services'; +import { MinimalNodeEntity } from 'alfresco-js-api'; import { Store } from '@ngrx/store'; -import { AppStore } from '../store/states/app.state'; -import { SnackbarErrorAction } from '../store/actions'; -import { ContentManagementService } from '../common/services/content-management.service'; +import { AppStore } from '../store/states'; +import { EditFolderAction } from '../store/actions'; @Directive({ selector: '[acaEditFolder]' }) export class EditFolderDirective { - /** Folder node to edit. */ // tslint:disable-next-line:no-input-rename - @Input('acaEditFolder') - folder: MinimalNodeEntity; + @Input('acaEditFolder') folder: MinimalNodeEntity; - @HostListener('click', [ '$event' ]) + @HostListener('click', ['$event']) onClick(event) { event.preventDefault(); - - if (this.folder) { - this.openDialog(); - } + this.store.dispatch(new EditFolderAction(this.folder)); } - constructor( - private store: Store, - private dialogRef: MatDialog, - private content: ContentManagementService - ) {} - - private get dialogConfig(): MatDialogConfig { - return { - data: { - folder: this.folder.entry - }, - width: '400px' - }; - } - - private openDialog(): void { - const dialog = this.dialogRef.open(FolderDialogComponent, this.dialogConfig); - - dialog.componentInstance.error.subscribe(message => { - this.store.dispatch(new SnackbarErrorAction(message)); - }); - - dialog.afterClosed().subscribe((node: MinimalNodeEntryEntity) => { - if (node) { - this.content.folderEdited.next(node); - } - }); - } + constructor(private store: Store) {} } diff --git a/src/app/extensions/content-action.extension.ts b/src/app/extensions/content-action.extension.ts index c11158f76..2209dd4ac 100644 --- a/src/app/extensions/content-action.extension.ts +++ b/src/app/extensions/content-action.extension.ts @@ -30,7 +30,7 @@ export interface ContentActionExtension { icon?: string; disabled?: boolean; target: { - type: string; + types: Array; permissions: Array, action: string; }; diff --git a/src/app/extensions/extension.service.ts b/src/app/extensions/extension.service.ts index 1f59e862c..3463a646a 100644 --- a/src/app/extensions/extension.service.ts +++ b/src/app/extensions/extension.service.ts @@ -29,11 +29,11 @@ import { ActionExtension } from './action.extension'; import { AppConfigService } from '@alfresco/adf-core'; import { ContentActionExtension } from './content-action.extension'; import { OpenWithExtension } from './open-with.extension'; -import { AppStore } from '../store/states'; +import { AppStore, SelectionState } from '../store/states'; import { Store } from '@ngrx/store'; import { NavigationExtension } from './navigation.extension'; import { Route } from '@angular/router'; -import { CreateExtension } from './create.extension'; +import { Node } from 'alfresco-js-api'; @Injectable() export class ExtensionService { @@ -42,7 +42,7 @@ export class ExtensionService { contentActions: Array = []; openWithActions: Array = []; - createActions: Array = []; + createActions: Array = []; authGuards: { [key: string]: Type<{}> } = {}; components: { [key: string]: Type<{}> } = {}; @@ -70,7 +70,6 @@ export class ExtensionService { 'extensions.core.features.content.actions', [] ) - .filter(entry => !entry.disabled) .sort(this.sortByOrder); this.openWithActions = this.config @@ -82,8 +81,10 @@ export class ExtensionService { .sort(this.sortByOrder); this.createActions = this.config - .get>('extensions.core.features.create', []) - .filter(entry => !entry.disabled) + .get>( + 'extensions.core.features.create', + [] + ) .sort(this.sortByOrder); } @@ -170,11 +171,48 @@ export class ExtensionService { component: this.getComponentById(route.component), data: route.data } - ], + ] }; }); } + // evaluates create actions for the folder node + getFolderCreateActions(folder: Node): Array { + return this.createActions.filter(this.filterOutDisabled).map(action => { + if ( + action.target && + action.target.permissions && + action.target.permissions.length > 0 + ) { + return { + ...action, + disabled: !this.nodeHasPermissions( + folder, + action.target.permissions + ), + target: { + ...action.target + } + }; + } + return action; + }); + } + + // evaluates content actions for the selection and parent folder node + getSelectedContentActions( + selection: SelectionState, + parentNode: Node + ): Array { + return this.contentActions + .filter(this.filterOutDisabled) + .filter(action => action.target) + .filter(action => this.filterByTarget(selection, action)) + .filter(action => + this.filterByPermission(selection, action, parentNode) + ); + } + private sortByOrder( a: { order?: number | undefined }, b: { order?: number | undefined } @@ -183,4 +221,81 @@ export class ExtensionService { 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( + selection: SelectionState, + action: ContentActionExtension + ): boolean { + const types = action.target.types; + + if (!types || types.length === 0) { + return true; + } + + if (selection && selection.folder && types.includes('folder')) { + return true; + } + + if (selection && selection.file && types.includes('file')) { + return true; + } + + return false; + } + + // todo: support multiple selected nodes + private filterByPermission( + selection: SelectionState, + action: ContentActionExtension, + parentNode: Node + ): boolean { + const permissions = action.target.permissions; + + if (!permissions || permissions.length === 0) { + return true; + } + + return permissions.some(permission => { + if (permission.startsWith('parent.')) { + if (parentNode) { + const parentQuery = permission.split('.')[1]; + // console.log(parentNode.allowableOperations, parentQuery); + return this.nodeHasPermissions(parentNode, [parentQuery]); + } + return false; + } + + if (selection && selection.first) { + return this.nodeHasPermissions( + selection.first.entry, + permissions + ); + } + + return true; + }); + + return true; + } + + private nodeHasPermissions( + node: Node, + permissions: string[] = [] + ): boolean { + if ( + node && + node.allowableOperations && + node.allowableOperations.length > 0 + ) { + return permissions.some(permission => + node.allowableOperations.includes(permission) + ); + } + return false; + } } diff --git a/src/app/store/actions/app.actions.ts b/src/app/store/actions/app.actions.ts index 2cfce276d..9c8c2dd5a 100644 --- a/src/app/store/actions/app.actions.ts +++ b/src/app/store/actions/app.actions.ts @@ -24,12 +24,14 @@ */ import { Action } from '@ngrx/store'; +import { Node } from 'alfresco-js-api'; export const SET_APP_NAME = 'SET_APP_NAME'; export const SET_HEADER_COLOR = 'SET_HEADER_COLOR'; export const SET_LOGO_PATH = 'SET_LOGO_PATH'; export const SET_LANGUAGE_PICKER = 'SET_LANGUAGE_PICKER'; export const SET_SHARED_URL = 'SET_SHARED_URL'; +export const SET_CURRENT_FOLDER = 'SET_CURRENT_FOLDER'; export class SetAppNameAction implements Action { readonly type = SET_APP_NAME; @@ -55,3 +57,8 @@ export class SetSharedUrlAction implements Action { readonly type = SET_SHARED_URL; constructor(public payload: string) {} } + +export class SetCurrentFolderAction implements Action { + readonly type = SET_CURRENT_FOLDER; + constructor(public payload: Node) {} +} diff --git a/src/app/store/actions/node.actions.ts b/src/app/store/actions/node.actions.ts index c8b1a9063..04a57c3aa 100644 --- a/src/app/store/actions/node.actions.ts +++ b/src/app/store/actions/node.actions.ts @@ -25,6 +25,7 @@ import { Action } from '@ngrx/store'; import { NodeInfo } from '../models'; +import { MinimalNodeEntity } from 'alfresco-js-api'; export const SET_SELECTED_NODES = 'SET_SELECTED_NODES'; export const DELETE_NODES = 'DELETE_NODES'; @@ -32,6 +33,8 @@ export const UNDO_DELETE_NODES = 'UNDO_DELETE_NODES'; export const RESTORE_DELETED_NODES = 'RESTORE_DELETED_NODES'; export const PURGE_DELETED_NODES = 'PURGE_DELETED_NODES'; export const DOWNLOAD_NODES = 'DOWNLOAD_NODES'; +export const CREATE_FOLDER = 'CREATE_FOLDER'; +export const EDIT_FOLDER = 'EDIT_FOLDER'; export class SetSelectedNodesAction implements Action { readonly type = SET_SELECTED_NODES; @@ -62,3 +65,13 @@ export class DownloadNodesAction implements Action { readonly type = DOWNLOAD_NODES; constructor(public payload: NodeInfo[] = []) {} } + +export class CreateFolderAction implements Action { + readonly type = CREATE_FOLDER; + constructor(public payload: string) {} +} + +export class EditFolderAction implements Action { + readonly type = EDIT_FOLDER; + constructor(public payload: MinimalNodeEntity) {} +} diff --git a/src/app/store/effects/node.effects.ts b/src/app/store/effects/node.effects.ts index c0adad2a3..cf8eb9185 100644 --- a/src/app/store/effects/node.effects.ts +++ b/src/app/store/effects/node.effects.ts @@ -39,12 +39,16 @@ import { SnackbarUserAction, SnackbarAction, UndoDeleteNodesAction, - UNDO_DELETE_NODES + UNDO_DELETE_NODES, + CreateFolderAction, + CREATE_FOLDER } from '../actions'; import { ContentManagementService } from '../../common/services/content-management.service'; import { Observable } from 'rxjs/Rx'; import { NodeInfo, DeleteStatus, DeletedNodeInfo } from '../models'; import { ContentApiService } from '../../services/content-api.service'; +import { currentFolder, appSelection } from '../selectors/app.selectors'; +import { EditFolderAction, EDIT_FOLDER } from '../actions/node.actions'; @Injectable() export class NodeEffects { @@ -83,6 +87,44 @@ export class NodeEffects { }) ); + @Effect({ dispatch: false }) + createFolder$ = this.actions$.pipe( + ofType(CREATE_FOLDER), + map(action => { + if (action.payload) { + this.contentManagementService.createFolder(action.payload); + } else { + this.store + .select(currentFolder) + .take(1) + .subscribe(node => { + if (node && node.id) { + this.contentManagementService.createFolder(node.id); + } + }); + } + }) + ); + + @Effect({ dispatch: false }) + editFolder$ = this.actions$.pipe( + ofType(EDIT_FOLDER), + map(action => { + if (action.payload) { + this.contentManagementService.editFolder(action.payload); + } else { + this.store + .select(appSelection) + .take(1) + .subscribe(selection => { + if (selection && selection.folder) { + this.contentManagementService.editFolder(selection.folder); + } + }); + } + }) + ); + private deleteNodes(items: NodeInfo[]): void { const batch: Observable[] = []; @@ -113,7 +155,8 @@ export class NodeEffects { private deleteNode(node: NodeInfo): Observable { const { id, name } = node; - return this.contentApi.deleteNode(id) + return this.contentApi + .deleteNode(id) .map(() => { return { id, @@ -206,7 +249,8 @@ export class NodeEffects { private undoDeleteNode(item: DeletedNodeInfo): Observable { const { id, name } = item; - return this.contentApi.restoreNode(id) + return this.contentApi + .restoreNode(id) .map(() => { return { id, @@ -263,7 +307,8 @@ export class NodeEffects { private purgeDeletedNode(node: NodeInfo): Observable { const { id, name } = node; - return this.contentApi.purgeDeletedNode(id) + return this.contentApi + .purgeDeletedNode(id) .map(() => ({ status: 1, id, diff --git a/src/app/store/reducers/app.reducer.ts b/src/app/store/reducers/app.reducer.ts index b77d9e18b..caa6b7377 100644 --- a/src/app/store/reducers/app.reducer.ts +++ b/src/app/store/reducers/app.reducer.ts @@ -35,14 +35,14 @@ import { SET_SELECTED_NODES, SetSelectedNodesAction, SET_USER, - SetUserAction -} from '../actions'; -import { + SetUserAction, SET_LANGUAGE_PICKER, SetLanguagePickerAction, SET_SHARED_URL, - SetSharedUrlAction -} from '../actions/app.actions'; + SetSharedUrlAction, + SET_CURRENT_FOLDER +} from '../actions'; +import { SetCurrentFolderAction } from '../actions/app.actions'; export function appReducer( state: AppState = INITIAL_APP_STATE, @@ -74,7 +74,10 @@ export function appReducer( )); break; case SET_SHARED_URL: - newState = updateSharedUrl(state, ( + newState = updateSharedUrl(state, action); + break; + case SET_CURRENT_FOLDER: + newState = updateCurrentFolder(state, ( action )); break; @@ -149,6 +152,12 @@ function updateUser(state: AppState, action: SetUserAction): AppState { return newState; } +function updateCurrentFolder(state: AppState, action: SetCurrentFolderAction) { + const newState = Object.assign({}, state); + newState.navigation.currentFolder = action.payload; + return newState; +} + function updateSelectedNodes( state: AppState, action: SetSelectedNodesAction diff --git a/src/app/store/selectors/app.selectors.ts b/src/app/store/selectors/app.selectors.ts index e5196364a..49277c9d8 100644 --- a/src/app/store/selectors/app.selectors.ts +++ b/src/app/store/selectors/app.selectors.ts @@ -34,3 +34,4 @@ 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 currentFolder = createSelector(selectApp, state => state.navigation.currentFolder); diff --git a/src/app/store/states/app.state.ts b/src/app/store/states/app.state.ts index ca5b4eb0b..2b134d029 100644 --- a/src/app/store/states/app.state.ts +++ b/src/app/store/states/app.state.ts @@ -25,6 +25,7 @@ import { SelectionState } from './selection.state'; import { ProfileState } from './profile.state'; +import { NavigationState } from './navigation.state'; export interface AppState { appName: string; @@ -34,6 +35,7 @@ export interface AppState { sharedUrl: string; selection: SelectionState; user: ProfileState; + navigation: NavigationState; } export const INITIAL_APP_STATE: AppState = { @@ -52,6 +54,9 @@ export const INITIAL_APP_STATE: AppState = { nodes: [], isEmpty: true, count: 0 + }, + navigation: { + currentFolder: null } }; diff --git a/src/app/extensions/create.extension.ts b/src/app/store/states/navigation.state.ts similarity index 87% rename from src/app/extensions/create.extension.ts rename to src/app/store/states/navigation.state.ts index 8393f9758..dd41681fe 100644 --- a/src/app/extensions/create.extension.ts +++ b/src/app/store/states/navigation.state.ts @@ -23,11 +23,8 @@ * along with Alfresco. If not, see . */ -export interface CreateExtension { - id: string; - order?: number; - title: string; - icon?: string; - action: string; - disabled?: boolean; +import { Node } from 'alfresco-js-api'; + +export interface NavigationState { + currentFolder?: Node; } diff --git a/src/app/testing/app-testing.module.ts b/src/app/testing/app-testing.module.ts index 08f47e774..81472d3b1 100644 --- a/src/app/testing/app-testing.module.ts +++ b/src/app/testing/app-testing.module.ts @@ -60,7 +60,6 @@ import { MaterialModule } from '../material.module'; import { ContentManagementService } from '../common/services/content-management.service'; import { NodeActionsService } from '../common/services/node-actions.service'; import { NodePermissionService } from '../common/services/node-permission.service'; -import { BrowsingFilesService } from '../common/services/browsing-files.service'; import { ContentApiService } from '../services/content-api.service'; import { ExtensionService } from '../extensions/extension.service'; @@ -116,7 +115,6 @@ import { ExtensionService } from '../extensions/extension.service'; ContentManagementService, NodeActionsService, NodePermissionService, - BrowsingFilesService, ContentApiService, ExtensionService ]