mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
extensions: wave 2 (#497)
* introduce "create folder" action * track opened folder via store * "create folder" action, support mulitple targets * fix card view colors and toolbar layouts * basic support for permissions * simplify create menu and add permissions * add toolbar separators for extension entries * "edit folder" extension command * minor code improvements
This commit is contained in:
parent
bc22053e2e
commit
fe683015c5
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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(<any>value);
|
||||
});
|
||||
});
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Subject } from 'rxjs/Rx';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
|
||||
@Injectable()
|
||||
export class BrowsingFilesService {
|
||||
onChangeParent = new Subject<MinimalNodeEntryEntity>();
|
||||
}
|
@ -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<any>();
|
||||
siteDeleted = new Subject<string>();
|
||||
linksUnshared = new Subject<any>();
|
||||
|
||||
constructor(private store: Store<AppStore>, 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,17 @@
|
||||
<adf-breadcrumb root="APP.BROWSE.FAVORITES.TITLE">
|
||||
</adf-breadcrumb>
|
||||
|
||||
<adf-toolbar class="inline">
|
||||
<button *ifExperimental="'cardview'"
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
(click)="toggleGalleryView()">
|
||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||
|
||||
<ng-container *ifExperimental="'extensions'">
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
<button *ngFor="let entry of actions"
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
@ -18,7 +21,10 @@
|
||||
(click)="runAction(entry.target.action)">
|
||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!selection.isEmpty">
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
@ -111,6 +117,7 @@
|
||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
</adf-toolbar>
|
||||
</div>
|
||||
<div class="inner-layout__content">
|
||||
|
@ -6,14 +6,17 @@
|
||||
(navigate)="onBreadcrumbNavigate($event)">
|
||||
</adf-breadcrumb>
|
||||
|
||||
<adf-toolbar class="inline">
|
||||
<button *ifExperimental="'cardview'"
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
(click)="toggleGalleryView()">
|
||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||
|
||||
<ng-container *ifExperimental="'extensions'">
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
<button *ngFor="let entry of actions"
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
@ -21,7 +24,10 @@
|
||||
(click)="runAction(entry.target.action)">
|
||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!selection.isEmpty">
|
||||
<button
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
@ -116,6 +122,7 @@
|
||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
</adf-toolbar>
|
||||
</div>
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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<NodePaging> {
|
||||
@ -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
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="layout">
|
||||
<adf-upload-drag-area
|
||||
[parentId]="node?.id"
|
||||
[disabled]="!permission.check(node, ['create'])">
|
||||
[disabled]="!canUpload">
|
||||
|
||||
<adf-sidenav-layout
|
||||
#sidenavManager="acaSidenavManager"
|
||||
|
@ -25,10 +25,8 @@
|
||||
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { PeopleContentService, AppConfigService, UserPreferencesService } from '@alfresco/adf-core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
|
||||
import { LayoutComponent } from './layout.component';
|
||||
import { SidenavViewsManagerDirective } from './sidenav-views-manager.directive';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
@ -36,17 +34,9 @@ import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
describe('LayoutComponent', () => {
|
||||
let fixture: ComponentFixture<LayoutComponent>;
|
||||
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 = <MinimalNodeEntryEntity>{ id: 'someId' };
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
browsingFilesService.onChangeParent.next(currentNode);
|
||||
|
||||
expect(component.node).toEqual(currentNode);
|
||||
});
|
||||
|
||||
describe('sidenav state', () => {
|
||||
it('should get state from configuration', () => {
|
||||
appConfig.config = {
|
||||
|
@ -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<boolean> = new Subject<boolean>();
|
||||
expandedSidenav: boolean;
|
||||
node: MinimalNodeEntryEntity;
|
||||
|
||||
private subscriptions: Subscription[] = [];
|
||||
canUpload = false;
|
||||
|
||||
constructor(
|
||||
private browsingFilesService: BrowsingFilesService,
|
||||
public permission: NodePermissionService) {}
|
||||
protected store: Store<AppStore>,
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,16 @@
|
||||
<adf-breadcrumb root="APP.BROWSE.LIBRARIES.TITLE">
|
||||
</adf-breadcrumb>
|
||||
|
||||
<adf-toolbar class="inline">
|
||||
<button *ifExperimental="'cardview'"
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
(click)="toggleGalleryView()">
|
||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||
|
||||
<ng-container *ngIf="!selection.isEmpty">
|
||||
<ng-container *ifExperimental="'libraries'">
|
||||
<button
|
||||
color="primary"
|
||||
@ -27,6 +30,7 @@
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</adf-toolbar>
|
||||
</div>
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,14 +3,17 @@
|
||||
<adf-breadcrumb root="APP.BROWSE.RECENT.TITLE">
|
||||
</adf-breadcrumb>
|
||||
|
||||
<adf-toolbar class="inline">
|
||||
<button *ifExperimental="'cardview'"
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
(click)="toggleGalleryView()">
|
||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||
|
||||
<ng-container *ifExperimental="'extensions'">
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
<button *ngFor="let entry of actions"
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
@ -18,7 +21,10 @@
|
||||
(click)="runAction(entry.target.action)">
|
||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!selection.isEmpty">
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
@ -44,9 +50,10 @@
|
||||
</button>
|
||||
|
||||
<ng-container *ifExperimental="'share'">
|
||||
<button mat-icon-button
|
||||
<button *ngIf="selection.file"
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
title="{{ 'APP.ACTIONS.SHARE' | translate }}"
|
||||
*ngIf="selection.file"
|
||||
[baseShareUrl]="sharedPreviewUrl$ | async"
|
||||
[adf-share]="selection.file">
|
||||
<mat-icon>share</mat-icon>
|
||||
@ -103,6 +110,7 @@
|
||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
</adf-toolbar>
|
||||
</div>
|
||||
|
||||
|
@ -2,8 +2,9 @@
|
||||
<div class="inner-layout__header">
|
||||
<adf-breadcrumb root="APP.BROWSE.SEARCH.TITLE">
|
||||
</adf-breadcrumb>
|
||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||
<adf-toolbar class="inline">
|
||||
<ng-container *ifExperimental="'extensions'">
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
<button *ngFor="let entry of actions"
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
@ -11,7 +12,10 @@
|
||||
(click)="runAction(entry.target.action)">
|
||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!selection.isEmpty">
|
||||
<button
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
@ -69,6 +73,7 @@
|
||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
</adf-toolbar>
|
||||
</div>
|
||||
|
||||
|
@ -3,14 +3,17 @@
|
||||
<adf-breadcrumb root="APP.BROWSE.SHARED.TITLE">
|
||||
</adf-breadcrumb>
|
||||
|
||||
<adf-toolbar class="inline">
|
||||
<button *ifExperimental="'cardview'"
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
(click)="toggleGalleryView()">
|
||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||
|
||||
<ng-container *ifExperimental="'extensions'">
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
<button *ngFor="let entry of actions"
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
@ -18,7 +21,10 @@
|
||||
(click)="runAction(entry.target.action)">
|
||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!selection.isEmpty">
|
||||
<button
|
||||
*ngIf="selection.count === 1"
|
||||
color="primary"
|
||||
@ -101,6 +107,7 @@
|
||||
<span>{{ 'APP.ACTIONS.VERSIONS' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
</adf-toolbar>
|
||||
</div>
|
||||
|
||||
|
@ -9,17 +9,18 @@
|
||||
<ng-container *ifExperimental="'extensions'">
|
||||
<button *ngFor="let entry of createActions"
|
||||
mat-menu-item
|
||||
(click)="runAction(entry.action)">
|
||||
[disabled]="entry.disabled"
|
||||
(click)="runAction(entry.target.action)">
|
||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||
<span>{{ entry.title | translate }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="!permission.check(node, ['create'])"
|
||||
[acaCreateFolder]="node?.id"
|
||||
[disabled]="!canCreateContent"
|
||||
(click)="createNewFolder()"
|
||||
[attr.title]="
|
||||
( permission.check(node, ['create'])
|
||||
( canCreateContent
|
||||
? 'APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER'
|
||||
: 'APP.NEW_MENU.TOOLTIPS.CREATE_FOLDER_NOT_ALLOWED'
|
||||
) | translate">
|
||||
@ -29,11 +30,11 @@
|
||||
|
||||
<adf-upload-button
|
||||
[tooltip]="
|
||||
(permission.check(node, ['create'])
|
||||
(canCreateContent
|
||||
? 'APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES'
|
||||
: 'APP.NEW_MENU.TOOLTIPS.UPLOAD_FILES_NOT_ALLOWED'
|
||||
) | translate"
|
||||
[disabled]="!permission.check(node, ['create'])"
|
||||
[disabled]="!canCreateContent"
|
||||
[rootFolderId]="node?.id"
|
||||
[multipleFiles]="true"
|
||||
[uploadFolders]="false"
|
||||
@ -42,11 +43,11 @@
|
||||
|
||||
<adf-upload-button
|
||||
[tooltip]="
|
||||
(permission.check(node, ['create'])
|
||||
(canCreateContent
|
||||
? 'APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS'
|
||||
: 'APP.NEW_MENU.TOOLTIPS.UPLOAD_FOLDERS_NOT_ALLOWED'
|
||||
) | translate"
|
||||
[disabled]="!permission.check(node, ['create'])"
|
||||
[disabled]="!canCreateContent"
|
||||
[rootFolderId]="node?.id"
|
||||
[multipleFiles]="true"
|
||||
[uploadFolders]="true"
|
||||
|
@ -25,7 +25,6 @@
|
||||
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
|
||||
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
|
||||
import { SidenavComponent } from './sidenav.component';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { NodeEffects } from '../../store/effects/node.effects';
|
||||
@ -35,7 +34,6 @@ import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
describe('SidenavComponent', () => {
|
||||
let fixture: ComponentFixture<SidenavComponent>;
|
||||
let component: SidenavComponent;
|
||||
let browsingService: BrowsingFilesService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -51,19 +49,12 @@ describe('SidenavComponent', () => {
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
browsingService = TestBed.get(BrowsingFilesService);
|
||||
|
||||
fixture = TestBed.createComponent(SidenavComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
}));
|
||||
|
||||
it('should update node on change', () => {
|
||||
fixture.detectChanges();
|
||||
const node: any = { entry: { id: 'someNodeId' } };
|
||||
|
||||
browsingService.onChangeParent.next(<any>node);
|
||||
|
||||
expect(component.node).toBe(node);
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
@ -23,14 +23,18 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Subscription } from 'rxjs/Rx';
|
||||
import { Subject } from 'rxjs/Rx';
|
||||
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
|
||||
import { Node } from 'alfresco-js-api';
|
||||
import { NodePermissionService } from '../../common/services/node-permission.service';
|
||||
import { ExtensionService } from '../../extensions/extension.service';
|
||||
import { NavigationExtension } from '../../extensions/navigation.extension';
|
||||
import { CreateExtension } from '../../extensions/create.extension';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states';
|
||||
import { CreateFolderAction } from '../../store/actions';
|
||||
import { currentFolder } from '../../store/selectors/app.selectors';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { ContentActionExtension } from '../../extensions/content-action.extension';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sidenav',
|
||||
@ -40,31 +44,40 @@ import { CreateExtension } from '../../extensions/create.extension';
|
||||
export class SidenavComponent implements OnInit, OnDestroy {
|
||||
@Input() showLabel: boolean;
|
||||
|
||||
node: MinimalNodeEntryEntity = null;
|
||||
node: Node = null;
|
||||
groups: Array<NavigationExtension[]> = [];
|
||||
createActions: Array<CreateExtension> = [];
|
||||
|
||||
private subscriptions: Subscription[] = [];
|
||||
createActions: Array<ContentActionExtension> = [];
|
||||
canCreateContent = false;
|
||||
onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
private browsingFilesService: BrowsingFilesService,
|
||||
public permission: NodePermissionService,
|
||||
private store: Store<AppStore>,
|
||||
private permission: NodePermissionService,
|
||||
private extensions: ExtensionService
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.groups = this.extensions.getNavigationGroups();
|
||||
this.createActions = this.extensions.createActions;
|
||||
|
||||
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.createActions = this.extensions.getFolderCreateActions(node);
|
||||
this.canCreateContent = this.permission.check(node, ['create']);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscriptions.forEach(s => s.unsubscribe());
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
createNewFolder() {
|
||||
if (this.node && this.node.id) {
|
||||
this.store.dispatch(new CreateFolderAction(this.node.id));
|
||||
}
|
||||
}
|
||||
|
||||
// this is where each application decides how to treat an action and what to do
|
||||
|
@ -3,14 +3,17 @@
|
||||
<adf-breadcrumb root="APP.BROWSE.TRASHCAN.TITLE">
|
||||
</adf-breadcrumb>
|
||||
|
||||
<adf-toolbar class="inline">
|
||||
<button *ifExperimental="'cardview'"
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
(click)="toggleGalleryView()">
|
||||
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
|
||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||
|
||||
<ng-container *ifExperimental="'extensions'">
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
<button *ngFor="let entry of actions"
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
@ -18,7 +21,10 @@
|
||||
(click)="runAction(entry.target.action)">
|
||||
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||
</button>
|
||||
<adf-toolbar-divider></adf-toolbar-divider>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!selection.isEmpty">
|
||||
<button
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
@ -34,6 +40,7 @@
|
||||
title="{{ 'APP.ACTIONS.RESTORE' | translate }}">
|
||||
<mat-icon>restore</mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
</adf-toolbar>
|
||||
</div>
|
||||
|
||||
@ -97,7 +104,7 @@
|
||||
</data-column>
|
||||
|
||||
<data-column
|
||||
*ngIf="user.isAdmin"
|
||||
*ngIf="(user$ | async)?.isAdmin"
|
||||
class="adf-data-table-cell--ellipsis"
|
||||
key="archivedByUser.displayName"
|
||||
title="APP.DOCUMENT_LIST.COLUMNS.DELETED_BY">
|
||||
|
@ -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<ProfileState>;
|
||||
|
||||
constructor(private contentManagementService: ContentManagementService,
|
||||
extensions: ExtensionService,
|
||||
store: Store<AppStore>) {
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<AppStore>,
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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'])
|
||||
onClick(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.folder) {
|
||||
this.openDialog();
|
||||
}
|
||||
this.store.dispatch(new EditFolderAction(this.folder));
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
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<AppStore>) {}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export interface ContentActionExtension {
|
||||
icon?: string;
|
||||
disabled?: boolean;
|
||||
target: {
|
||||
type: string;
|
||||
types: Array<string>;
|
||||
permissions: Array<string>,
|
||||
action: string;
|
||||
};
|
||||
|
@ -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<ContentActionExtension> = [];
|
||||
openWithActions: Array<OpenWithExtension> = [];
|
||||
createActions: Array<CreateExtension> = [];
|
||||
createActions: Array<ContentActionExtension> = [];
|
||||
|
||||
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<Array<CreateExtension>>('extensions.core.features.create', [])
|
||||
.filter(entry => !entry.disabled)
|
||||
.get<Array<ContentActionExtension>>(
|
||||
'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<ContentActionExtension> {
|
||||
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<ContentActionExtension> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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<CreateFolderAction>(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<EditFolderAction>(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<DeletedNodeInfo>[] = [];
|
||||
|
||||
@ -113,7 +155,8 @@ export class NodeEffects {
|
||||
private deleteNode(node: NodeInfo): Observable<DeletedNodeInfo> {
|
||||
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<DeletedNodeInfo> {
|
||||
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<DeletedNodeInfo> {
|
||||
const { id, name } = node;
|
||||
|
||||
return this.contentApi.purgeDeletedNode(id)
|
||||
return this.contentApi
|
||||
.purgeDeletedNode(id)
|
||||
.map(() => ({
|
||||
status: 1,
|
||||
id,
|
||||
|
@ -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, <SetSharedUrlAction>(
|
||||
newState = updateSharedUrl(state, <SetSharedUrlAction>action);
|
||||
break;
|
||||
case SET_CURRENT_FOLDER:
|
||||
newState = updateCurrentFolder(state, <SetCurrentFolderAction>(
|
||||
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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -23,11 +23,8 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
@ -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
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user