diff --git a/demo-shell-ng2/app/components/files/files.component.html b/demo-shell-ng2/app/components/files/files.component.html index 1a30ea81f9..62feaab3c0 100644 --- a/demo-shell-ng2/app/components/files/files.component.html +++ b/demo-shell-ng2/app/components/files/files.component.html @@ -150,6 +150,13 @@

+

+ +

+
Upload

@@ -164,7 +171,9 @@ [rootFolderId]="documentList.currentFolderId" [multipleFiles]="multipleFileUpload" [uploadFolders]="folderUpload" - [versioning] = "versioning"> + [versioning] = "versioning" + [disableWithNoPermission]="disableWithNoPermission" + (permissionEvent)="onUploadPermissionFailed($event)">
@@ -175,7 +184,9 @@ [acceptedFilesType]="acceptedFilesType" [multipleFiles]="multipleFileUpload" [uploadFolders]="folderUpload" - [versioning] = "versioning"> + [versioning] = "versioning" + [disableWithNoPermission]="disableWithNoPermission" + (permissionEvent)="onUploadPermissionFailed($event)">
diff --git a/demo-shell-ng2/app/components/files/files.component.ts b/demo-shell-ng2/app/components/files/files.component.ts index 1f1f4eeeb7..c2a9c034e4 100644 --- a/demo-shell-ng2/app/components/files/files.component.ts +++ b/demo-shell-ng2/app/components/files/files.component.ts @@ -35,6 +35,7 @@ export class FilesComponent implements OnInit, AfterViewInit { fileNodeId: any; fileShowed: boolean = false; multipleFileUpload: boolean = false; + disableWithNoPermission: boolean = false; folderUpload: boolean = false; acceptedFilesTypeShow: boolean = false; versioning: boolean = false; @@ -86,6 +87,11 @@ export class FilesComponent implements OnInit, AfterViewInit { return this.multipleFileUpload; } + toggleDisableWithNoPermission() { + this.disableWithNoPermission = !this.disableWithNoPermission; + return this.disableWithNoPermission; + } + toggleFolder() { this.multipleFileUpload = false; this.folderUpload = !this.folderUpload; @@ -173,6 +179,10 @@ export class FilesComponent implements OnInit, AfterViewInit { this.notificationService.openSnackMessage(`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000); } + onUploadPermissionFailed(event: any) { + this.notificationService.openSnackMessage(`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000); + } + reload(event: any) { if (event && event.value && event.value.entry && event.value.entry.parentId) { if (this.documentList.currentFolderId === event.value.entry.parentId) { diff --git a/ng2-components/ng2-alfresco-upload/README.md b/ng2-components/ng2-alfresco-upload/README.md index 932c74d67d..589667e2ad 100644 --- a/ng2-components/ng2-alfresco-upload/README.md +++ b/ng2-components/ng2-alfresco-upload/README.md @@ -178,6 +178,43 @@ Attribute | Options | Default | Description | Mandatory `currentFolderPath` | *string* | '/Sites/swsdp/documentLibrary' | define the path where the files are uploaded | `versioning` | *boolean* | false | Versioning false is the default uploader behaviour and it rename using an integer suffix if there is a name clash. Versioning true to indicate that a major version should be created | `staticTitle` | *string* | 'FILE_UPLOAD.BUTTON.UPLOAD_FILE' or 'FILE_UPLOAD.BUTTON.UPLOAD_FOLDER' string in the JSON text file | define the text of the upload button| +`disableWithNoPermission` | *boolean* | false | If the value is true and the user doesn't have the permission to delete the node the button will be disabled | + +### How to show notification message with no permission +You can show a notification error when the user doesn't have the right permission to perform the action. +The UploadButtonComponent provides the event permissionEvent that is raised when the delete permission is missing +You can subscribe to this event from your component and use the NotificationService to show a message. + +```html + + + +export class MyComponent { + +onUploadPermissionFailed(event: any) { + this.notificationService.openSnackMessage(`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000); +} + +} +``` + +![Upload notification message](docs/assets/upload-notification-message.png) + +#### How to disable the button when the delete permission is missing +You can easily disable the button when the user doesn't own the permission to perform the action. +The UploadButtonComponent provides the property disableWithNoPermission that can be true. In this way the button should be disabled if the delete permission is missing for the node. + +```html + + +``` + +![Upload disable button](docs/assets/upload-disable-button.png) + ### Drag and drop diff --git a/ng2-components/ng2-alfresco-upload/docs/assets/upload-disable-button.png b/ng2-components/ng2-alfresco-upload/docs/assets/upload-disable-button.png new file mode 100644 index 0000000000..344e330228 Binary files /dev/null and b/ng2-components/ng2-alfresco-upload/docs/assets/upload-disable-button.png differ diff --git a/ng2-components/ng2-alfresco-upload/docs/assets/upload-notification-message.png b/ng2-components/ng2-alfresco-upload/docs/assets/upload-notification-message.png new file mode 100644 index 0000000000..762f3fab0f Binary files /dev/null and b/ng2-components/ng2-alfresco-upload/docs/assets/upload-notification-message.png differ diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.css b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.css index 37396e1d5a..7358dca90a 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.css +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.css @@ -9,6 +9,27 @@ z-index: 4; } + +.mdl-button--file--disabled { + background: rgba(0,0,0,.12); + color: rgba(0,0,0,.26); +} + +.mdl-button--file--disabled:hover { + background: rgba(0,0,0,.12); + color: rgba(0,0,0,.26); +} + +.mdl-button--file--disabled:focus:not(:active) { + box-shadow: 0 0 8px rgba(0,0,0,.18), 0 8px 16px rgba(0,0,0,.36); + background-color: rgba(158,158,158,.4); +} + +.mdl-button--file--disabled input[type="file"]:disabled { + cursor: default; + background-color: transparent; +} + .mdl-textfield--file .mdl-textfield__input { box-sizing: border-box; width: calc(100% - 32px); diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.html b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.html index 6ead8b50a4..c93621f5b1 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.html +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.html @@ -1,6 +1,6 @@
-
+
file_upload @@ -11,6 +11,7 @@ (change)="onFilesAdded($event)" multiple="multiple" accept="{{acceptedFilesType}}" + [attr.disabled]="disableButton" #uploadFiles> @@ -21,6 +22,7 @@
@@ -34,6 +36,7 @@ (change)="onDirectoryAdded($event)" multiple="multiple" accept="{{acceptedFilesType}}" + [attr.disabled]="disableButton" webkitdirectory directory multiple #uploadFolders>
diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.spec.ts b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.spec.ts index f710cd562f..9c61bebc7a 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.spec.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.spec.ts @@ -21,6 +21,7 @@ import { DebugElement } from '@angular/core'; import { CoreModule, AlfrescoTranslationService, NotificationService } from 'ng2-alfresco-core'; import { TranslationMock } from '../assets/translation.service.mock'; import { UploadService } from '../services/upload.service'; +import { Observable } from 'rxjs/Rx'; describe('UploadButtonComponent', () => { @@ -53,6 +54,25 @@ describe('UploadButtonComponent', () => { } }; + let fakeFolderNodeWithoutPermission = { + allowableOperations: [ + 'update' + ], + isFolder: true, + name: 'Folder Fake Name', + nodeType: 'cm:folder' + }; + + let fakeFolderNodeWithPermission = { + allowableOperations: [ + 'create', + 'update' + ], + isFolder: true, + name: 'Folder Fake Name', + nodeType: 'cm:folder' + }; + let fakeRejectPromise = new Promise(function (resolve, reject) { reject(fakeRejectRest); }); @@ -117,9 +137,70 @@ describe('UploadButtonComponent', () => { expect(compiled.querySelector('#uploadFolder')).toBeDefined(); }); + it('should emit the permissionEvent, without permission and disableWithNoPermission false', (done) => { + component.rootFolderId = '-my-'; + component.disableWithNoPermission = false; + + spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission)); + + fixture.detectChanges(); + + component.permissionEvent.subscribe( permission => { + expect(permission).toBeDefined(); + expect(permission.type).toEqual('content'); + expect(permission.action).toEqual('upload'); + expect(permission.permission).toEqual('create'); + done(); + }); + + component.onFilesAdded(fakeEvent); + }); + + it('should show the disabled button, without permission and disableWithNoPermission true', () => { + component.rootFolderId = '-my-'; + component.disableWithNoPermission = true; + + spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission)); + + component.onFilesAdded(fakeEvent); + let compiled = fixture.debugElement.nativeElement; + fixture.detectChanges(); + expect(compiled.querySelector('#upload-single-file')).toBeDefined(); + expect(compiled.querySelector('#upload-single-file').disabled).toBe(true); + }); + + it('should show the enabled button with permission and disableWithNoPermission true', () => { + component.rootFolderId = '-my-'; + component.disableWithNoPermission = true; + + spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); + + component.onFilesAdded(fakeEvent); + let compiled = fixture.debugElement.nativeElement; + fixture.detectChanges(); + expect(compiled.querySelector('#upload-single-file')).toBeDefined(); + expect(compiled.querySelector('#upload-single-file').disabled).toBe(false); + }); + + it('should show the enabled button with permission and disableWithNoPermission false', () => { + component.rootFolderId = '-my-'; + component.disableWithNoPermission = false; + + spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); + + component.onFilesAdded(fakeEvent); + let compiled = fixture.debugElement.nativeElement; + fixture.detectChanges(); + expect(compiled.querySelector('#upload-single-file')).toBeDefined(); + expect(compiled.querySelector('#upload-single-file').disabled).toBe(false); + }); + it('should call uploadFile with the default root folder', () => { component.currentFolderPath = '/root-fake-/sites-fake/folder-fake'; component.onSuccess = null; + + spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); + uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue'); fixture.detectChanges(); @@ -132,6 +213,8 @@ describe('UploadButtonComponent', () => { component.currentFolderPath = '/root-fake-/sites-fake/folder-fake'; component.rootFolderId = '-my-'; component.onSuccess = null; + + spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue'); fixture.detectChanges(); @@ -144,6 +227,7 @@ describe('UploadButtonComponent', () => { component.currentFolderPath = '/fake-root-path'; fixture.detectChanges(); + spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakeResolvePromise); component.onSuccess.subscribe(e => { @@ -161,6 +245,7 @@ describe('UploadButtonComponent', () => { }); it('should emit an onError event when the folder already exist', (done) => { + spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakeRejectPromise); component.onError.subscribe(e => { expect(e.value).toEqual('FILE_UPLOAD.MESSAGES.FOLDER_ALREADY_EXIST'); diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts index 2de9d609eb..4bfdef9a2c 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts @@ -15,11 +15,12 @@ * limitations under the License. */ -import { Component, ElementRef, Input, Output, EventEmitter } from '@angular/core'; -import 'rxjs/Rx'; +import { Component, ElementRef, Input, Output, EventEmitter, OnInit, OnChanges } from '@angular/core'; +import { Subject } from 'rxjs/Rx'; import { AlfrescoTranslationService, LogService, NotificationService } from 'ng2-alfresco-core'; import { UploadService } from '../services/upload.service'; import { FileModel } from '../models/file.model'; +import { PermissionModel } from '../models/permissions.model'; declare let componentHandler: any; @@ -51,7 +52,7 @@ const ERROR_FOLDER_ALREADY_EXIST = 409; templateUrl: './upload-button.component.html', styleUrls: ['./upload-button.component.css'] }) -export class UploadButtonComponent { +export class UploadButtonComponent implements OnInit, OnChanges { private static DEFAULT_ROOT_ID: string = '-root-'; @@ -79,6 +80,9 @@ export class UploadButtonComponent { @Input() rootFolderId: string = UploadButtonComponent.DEFAULT_ROOT_ID; + @Input() + disableWithNoPermission: boolean = false; + @Output() onSuccess = new EventEmitter(); @@ -88,6 +92,13 @@ export class UploadButtonComponent { @Output() createFolder = new EventEmitter(); + @Output() + permissionEvent: EventEmitter = new EventEmitter(); + + private disableButton: boolean = false; + + private permissionValue: Subject = new Subject(); + constructor(private el: ElementRef, private uploadService: UploadService, private translateService: AlfrescoTranslationService, @@ -98,7 +109,18 @@ export class UploadButtonComponent { } } + ngOnInit() { + this.permissionValue.subscribe((hasPermission: boolean) => { + if (!hasPermission && this.disableWithNoPermission) { + this.disableButton = true; + } else { + this.disableButton = undefined; + } + }); + } + ngOnChanges(changes) { + this.checkPermission(); let formFields = this.createFormFields(); this.uploadService.setOptions(formFields, this.versioning); } @@ -110,7 +132,15 @@ export class UploadButtonComponent { */ onFilesAdded($event: any): void { let files = $event.currentTarget.files; - this.uploadFiles(this.currentFolderPath, files); + this.permissionValue.subscribe((hasPermission: boolean) => { + if (hasPermission) { + this.uploadFiles(this.currentFolderPath, files); + } else { + this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'})); + } + }); + + this.checkPermission(); // reset the value of the input file $event.target.value = ''; } @@ -122,31 +152,39 @@ export class UploadButtonComponent { */ onDirectoryAdded($event: any): void { let files = $event.currentTarget.files; - let hashMapDir = this.convertIntoHashMap(files); + this.permissionValue.subscribe((hasPermission: boolean) => { + if (hasPermission) { + let hashMapDir = this.convertIntoHashMap(files); - hashMapDir.forEach((filesDir, directoryPath) => { - let directoryName = this.getDirectoryName(directoryPath); - let absolutePath = this.currentFolderPath + this.getDirectoryPath(directoryPath); + hashMapDir.forEach((filesDir, directoryPath) => { + let directoryName = this.getDirectoryName(directoryPath); + let absolutePath = this.currentFolderPath + this.getDirectoryPath(directoryPath); - this.uploadService.createFolder(absolutePath, directoryName, this.rootFolderId) - .subscribe( - res => { - let relativeDir = this.currentFolderPath + '/' + directoryPath; - this.uploadFiles(relativeDir, filesDir); - }, - error => { - let errorMessagePlaceholder = this.getErrorMessage(error.response); - if (errorMessagePlaceholder) { - this.onError.emit({value: errorMessagePlaceholder}); - let errorMessage = this.formatString(errorMessagePlaceholder, [directoryName]); - if (errorMessage) { - this._showErrorNotificationBar(errorMessage); + this.uploadService.createFolder(absolutePath, directoryName, this.rootFolderId) + .subscribe( + res => { + let relativeDir = this.currentFolderPath + '/' + directoryPath; + this.uploadFiles(relativeDir, filesDir); + }, + error => { + let errorMessagePlaceholder = this.getErrorMessage(error.response); + if (errorMessagePlaceholder) { + this.onError.emit({value: errorMessagePlaceholder}); + let errorMessage = this.formatString(errorMessagePlaceholder, [directoryName]); + if (errorMessage) { + this._showErrorNotificationBar(errorMessage); + } + } + this.logService.error(error); } - } - this.logService.error(error); - } - ); + ); + }); + } else { + this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'})); + } }); + + this.checkPermission(); // reset the value of the input file $event.target.value = ''; } @@ -272,4 +310,28 @@ export class UploadButtonComponent { } }; } + + checkPermission() { + if (this.rootFolderId) { + this.uploadService.getFolderNode(this.rootFolderId).subscribe( + res => { + this.permissionValue.next(this.hasCreatePermission(res)); + }, + error => { + this.logService.error(error); + } + ); + } + } + + private hasCreatePermission(node: any): boolean { + if (this.hasPermissions(node)) { + return node.allowableOperations.find(permision => permision === 'create') ? true : false; + } + return false; + } + + private hasPermissions(node: any): boolean { + return node && node.allowableOperations ? true : false; + } } diff --git a/ng2-components/ng2-alfresco-upload/src/models/permissions.model.ts b/ng2-components/ng2-alfresco-upload/src/models/permissions.model.ts new file mode 100644 index 0000000000..6aff3fe03e --- /dev/null +++ b/ng2-components/ng2-alfresco-upload/src/models/permissions.model.ts @@ -0,0 +1,30 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class PermissionModel { + type: string; + action: string; + permission: string; + + constructor(obj?: any) { + if (obj) { + this.type = obj.type || null; + this.action = obj.action || null; + this.permission = obj.permission || null; + } + } +} diff --git a/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts b/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts index 11ec3c1875..2bf3a8b8ac 100644 --- a/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts +++ b/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts @@ -20,7 +20,7 @@ import { Response } from '@angular/http'; import { Observer, Observable } from 'rxjs/Rx'; import { AlfrescoApiService, LogService } from 'ng2-alfresco-core'; import { FileModel } from '../models/file.model'; -import { MinimalNodeEntity } from 'alfresco-js-api'; +import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; /** * @@ -184,4 +184,17 @@ export class UploadService { this.totalCompletedObserver.next(total); } } + + getFolderNode(nodeId: string): Observable { + let opts: any = { + includeSource: true, + include: ['allowableOperations'] + }; + + return Observable.fromPromise(this.apiService.getInstance().nodes.getNodeInfo(nodeId, opts)) + .map((response: any) => { + return response; + }) + .catch(err => this.handleError(err)); + } }