diff --git a/demo-shell/resources/i18n/en.json b/demo-shell/resources/i18n/en.json index c6662b5cdd..b37beae63e 100644 --- a/demo-shell/resources/i18n/en.json +++ b/demo-shell/resources/i18n/en.json @@ -23,6 +23,7 @@ "UPLOADER": "Uploader", "WEBSCRIPT": "Webscript", "TAG": "Tag", + "TRASHCAN": "Trashcan", "SOCIAL": "Social", "SETTINGS": "Settings", "OVERLAY_VIEWER": "Overlay Viewer", @@ -33,6 +34,17 @@ "SEARCH_CREATED_BY" : "Created By", "SEARCH_SERVICE_APPROACH": "Check this to disable the input property and configure using the service" }, + "TRASHCAN" :{ + "ACTIONS":{ + "DELETE_PERMANENT":"Delete permanent", + "RESTORE":"Restore" + }, + "EMPTY_STATE": { + "TITLE": "Trash is empty", + "FIRST_TEXT": "Items you delete are moved to the Trash.", + "SECOND_TEXT": "Empty Trash to permanently delete items." + } + }, "DOCUMENT_LIST": { "MULTISELECT_CHECKBOXES" :"Multiselect (with checkboxes)", "MULTIPLE_FILE_UPLOAD" :"Multiple File Upload", @@ -49,7 +61,10 @@ "TAG": "Tag", "CREATED_BY": "Created by", "CREATED_ON": "Created on", - "CREATED": "Created" + "CREATED": "Created", + "SIZE": "Size", + "DELETED_ON": "Deleted", + "DELETED_BY": "Deleted by" }, "TOOLBAR": { "CARDVIEW":"Card view mode", diff --git a/demo-shell/src/app/app.module.ts b/demo-shell/src/app/app.module.ts index 75b2230b14..6a023f0f15 100644 --- a/demo-shell/src/app/app.module.ts +++ b/demo-shell/src/app/app.module.ts @@ -29,6 +29,7 @@ import { FormViewerComponent } from './components/process-service/form-viewer.co import { FormNodeViewerComponent } from './components/process-service/form-node-viewer.component'; import { AppsViewComponent } from './components/process-service/apps-view.component'; import { DataTableComponent } from './components/datatable/datatable.component'; +import { TrashcanComponent } from './components/trashcan/trashcan.component'; import { FilesComponent } from './components/files/files.component'; import { FileViewComponent } from './components/file-view/file-view.component'; import { WebscriptComponent } from './components/webscript/webscript.component'; @@ -78,6 +79,7 @@ import { SharedLinkViewComponent } from './components/shared-link-view/shared-li DataTableComponent, FilesComponent, FileViewComponent, + TrashcanComponent, FormComponent, FormListComponent, WebscriptComponent, diff --git a/demo-shell/src/app/app.routes.ts b/demo-shell/src/app/app.routes.ts index 6911f17814..0302f0a23b 100644 --- a/demo-shell/src/app/app.routes.ts +++ b/demo-shell/src/app/app.routes.ts @@ -30,6 +30,7 @@ import { FormNodeViewerComponent } from './components/process-service/form-node- import { AppsViewComponent } from './components/process-service/apps-view.component'; import { SearchResultComponent } from './components/search/search-result.component'; import { SearchExtendedComponent } from './components/search/search-extended.component'; +import { TrashcanComponent } from './components/trashcan/trashcan.component'; import { DataTableComponent } from './components/datatable/datatable.component'; import { WebscriptComponent } from './components/webscript/webscript.component'; @@ -65,8 +66,12 @@ export const appRoutes: Routes = [ { path: 'home', component: HomeComponent - } - , + }, + { + path: 'trashcan', + component: TrashcanComponent, + canActivate: [AuthGuardEcm] + }, { path: 'files', component: FilesComponent, diff --git a/demo-shell/src/app/components/app-layout/app-layout.component.ts b/demo-shell/src/app/components/app-layout/app-layout.component.ts index 7791e05ab3..74a0634768 100644 --- a/demo-shell/src/app/components/app-layout/app-layout.component.ts +++ b/demo-shell/src/app/components/app-layout/app-layout.component.ts @@ -30,6 +30,7 @@ export class AppLayoutComponent { links: Array = [ { href: '/home', icon: 'home', title: 'APP_LAYOUT.HOME' }, { href: '/files', icon: 'folder_open', title: 'APP_LAYOUT.CONTENT_SERVICES' }, + { href: '/trashcan', icon: 'delete', title: 'APP_LAYOUT.TRASHCAN' }, { href: '/activiti', icon: 'device_hub', title: 'APP_LAYOUT.PROCESS_SERVICES' }, { href: '/login', icon: 'vpn_key', title: 'APP_LAYOUT.LOGIN' }, { href: '/dl-custom-sources', icon: 'extension', title: 'APP_LAYOUT.CUSTOM_SOURCES' }, diff --git a/demo-shell/src/app/components/files/files.component.html b/demo-shell/src/app/components/files/files.component.html index 32d936f184..7815dbfbff 100644 --- a/demo-shell/src/app/components/files/files.component.html +++ b/demo-shell/src/app/components/files/files.component.html @@ -199,7 +199,7 @@ --> diff --git a/demo-shell/src/app/components/trashcan/trashcan.component.html b/demo-shell/src/app/components/trashcan/trashcan.component.html new file mode 100644 index 0000000000..c7002e8e05 --- /dev/null +++ b/demo-shell/src/app/components/trashcan/trashcan.component.html @@ -0,0 +1,96 @@ +
+
+ + + + + + + + +
+ +
+ + + + + +
+ delete +

{{ 'TRASHCAN.EMPTY_STATE.TITLE' | translate }}

+

{{ 'TRASHCAN.EMPTY_STATE.FIRST_TEXT' | translate }}

+

{{ 'TRASHCAN.EMPTY_STATE.SECOND_TEXT' | translate }}

+
+
+
+ + + + + + + + + {{ value }} + + + + + + + + + {{ value | adfTimeAgo }} + + + + + + + + +
+ + +
+
diff --git a/demo-shell/src/app/components/trashcan/trashcan.component.scss b/demo-shell/src/app/components/trashcan/trashcan.component.scss new file mode 100644 index 0000000000..b554b878f8 --- /dev/null +++ b/demo-shell/src/app/components/trashcan/trashcan.component.scss @@ -0,0 +1,35 @@ +.empty-list { + .adf-data-table { + height: 100%; + + tr:hover, tr:focus { + cursor: default; + } + } + + &__block { + display: flex; + flex-direction: column; + align-items: center; + + p { + line-height: 0; + } + } + + &__title { + font-size: 18px; + font-weight: 600; + } + + &__subtitle { + font-size: 14px; + font-weight: 300; + } + + &__block > mat-icon { + font-size: 52px; + height: 52px; + width: 52px; + } +} diff --git a/demo-shell/src/app/components/trashcan/trashcan.component.ts b/demo-shell/src/app/components/trashcan/trashcan.component.ts new file mode 100644 index 0000000000..36bb2b001c --- /dev/null +++ b/demo-shell/src/app/components/trashcan/trashcan.component.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2017 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 { Component, ViewChild } from '@angular/core'; +import { DocumentListComponent } from '@alfresco/adf-content-services'; + +@Component({ + templateUrl: './trashcan.component.html', + styleUrls: ['trashcan.component.scss'] +}) +export class TrashcanComponent { + + @ViewChild(DocumentListComponent) + documentList; + + refresh() { + this.documentList.loadTrashcan(); + this.documentList.resetSelection(); + } + +} diff --git a/docs/node-delete.directive.md b/docs/node-delete.directive.md index a025cf45a0..bc56bfe1bc 100644 --- a/docs/node-delete.directive.md +++ b/docs/node-delete.directive.md @@ -25,7 +25,7 @@ Deletes multiple files and folders. | Name | Type | Default value | Description | | ---- | ---- | ------------- | ----------- | -| selection | `MinimalNodeEntity[]` | | Array of nodes to delete. | +| selection | `MinimalNodeEntity[] | DeletedNodeEntity[]` | | Array of nodes to delete. | | permanent | `boolean` | `false` | If true then the nodes are deleted immediately rather than being put in the trash. | ### Events @@ -36,7 +36,7 @@ Deletes multiple files and folders. ## Details -See **Demo Shell** +Note it the file is already in the trashcan so is a DeletedNodeEntity the performing of this action will delete the file premanently ## See also diff --git a/lib/core/directives/node-delete.directive.spec.ts b/lib/core/directives/node-delete.directive.spec.ts index 87864bb986..141fb976db 100644 --- a/lib/core/directives/node-delete.directive.spec.ts +++ b/lib/core/directives/node-delete.directive.spec.ts @@ -36,7 +36,8 @@ class TestComponent { @Component({ template: ` -
` }) @@ -46,13 +47,33 @@ class TestWithPermissionsComponent { done = jasmine.createSpy('done'); } +@Component({ + template: ` + delete permanent +
+
` +}) +class TestDeletePermanentComponent { + selection = []; + + permanent = true; + + done = jasmine.createSpy('done'); +} + describe('NodeDeleteDirective', () => { let fixture: ComponentFixture; let fixtureWithPermissions: ComponentFixture; + let fixtureWithPermanentComponent: ComponentFixture; let element: DebugElement; let elementWithPermissions: DebugElement; + let elementWithPermanentDelete: DebugElement; let component: TestComponent; let componentWithPermissions: TestWithPermissionsComponent; + let componentWithPermanentDelete: TestDeletePermanentComponent; let alfrescoApi: AlfrescoApiService; let notification: NotificationService; let nodeApi; @@ -62,17 +83,23 @@ describe('NodeDeleteDirective', () => { declarations: [ TestComponent, - TestWithPermissionsComponent + TestWithPermissionsComponent, + TestDeletePermanentComponent ] }) .compileComponents() .then(() => { fixture = TestBed.createComponent(TestComponent); fixtureWithPermissions = TestBed.createComponent(TestWithPermissionsComponent); + fixtureWithPermanentComponent = TestBed.createComponent(TestDeletePermanentComponent); + component = fixture.componentInstance; componentWithPermissions = fixtureWithPermissions.componentInstance; + componentWithPermanentDelete = fixtureWithPermanentComponent.componentInstance; + element = fixture.debugElement.query(By.directive(NodeDeleteDirective)); elementWithPermissions = fixtureWithPermissions.debugElement.query(By.directive(NodeDeleteDirective)); + elementWithPermanentDelete = fixtureWithPermanentComponent.debugElement.query(By.directive(NodeDeleteDirective)); alfrescoApi = TestBed.get(AlfrescoApiService); nodeApi = alfrescoApi.getInstance().nodes; @@ -268,5 +295,43 @@ describe('NodeDeleteDirective', () => { expect(elementWithPermissions.nativeElement.disabled).toEqual(false); })); + describe('Permanent', () => { + + it('should call the api with permamnet delete option if permanent directive input is true', fakeAsync(() => { + let deleteApi = spyOn(nodeApi, 'deleteNode').and.returnValue(Promise.resolve()); + + fixtureWithPermanentComponent.detectChanges(); + + componentWithPermanentDelete.selection = [ + { entry: { id: '1', name: 'name1'} + ]; + + fixtureWithPermanentComponent.detectChanges(); + + elementWithPermanentDelete.triggerEventHandler('click', null); + tick(); + + expect(deleteApi).toHaveBeenCalledWith('1', { permanent: true }); + })); + + it('should call the traschan api if permanent directive input is true and the file is already in the trashcan ', fakeAsync(() => { + let deleteApi = spyOn(nodeApi, 'purgeDeletedNode').and.returnValue(Promise.resolve()); + + fixtureWithPermanentComponent.detectChanges(); + + componentWithPermanentDelete.selection = [ + { entry: { id: '1', name: 'name1', archivedAt: 'archived' } } + ]; + + fixtureWithPermanentComponent.detectChanges(); + + elementWithPermanentDelete.triggerEventHandler('click', null); + tick(); + + expect(deleteApi).toHaveBeenCalledWith('1'); + })); + + }); }); + }); diff --git a/lib/core/directives/node-delete.directive.ts b/lib/core/directives/node-delete.directive.ts index 266936e8cb..5d04c224ac 100644 --- a/lib/core/directives/node-delete.directive.ts +++ b/lib/core/directives/node-delete.directive.ts @@ -18,7 +18,7 @@ /* tslint:disable:no-input-rename */ import { Directive, ElementRef, EventEmitter, HostListener, Input, OnChanges, Output } from '@angular/core'; -import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { MinimalNodeEntity, MinimalNodeEntryEntity, DeletedNodeEntity, DeletedNodeMinimalEntry } from 'alfresco-js-api'; import { Observable } from 'rxjs/Observable'; import { AlfrescoApiService } from '../services/alfresco-api.service'; import { NotificationService } from '../services/notification.service'; @@ -28,7 +28,7 @@ import 'rxjs/observable/forkJoin'; import 'rxjs/add/operator/catch'; interface ProcessedNodeData { - entry: MinimalNodeEntryEntity; + entry: MinimalNodeEntryEntity | DeletedNodeMinimalEntry; status: number; } @@ -55,7 +55,7 @@ interface ProcessStatus { export class NodeDeleteDirective implements OnChanges { /** Array of nodes to delete. */ @Input('adf-delete') - selection: MinimalNodeEntity[]; + selection: MinimalNodeEntity[] | DeletedNodeEntity[]; /** If true then the nodes are deleted immediately rather than being * put in the trash. @@ -92,33 +92,38 @@ export class NodeDeleteDirective implements OnChanges { this.elementRef.nativeElement.disabled = disable; } - private process(selection: MinimalNodeEntity[]) { - if (!selection.length) { - return; + private process(selection: MinimalNodeEntity[] | DeletedNodeEntity[]) { + if (selection && selection.length) { + + const batch = this.getDeleteNodesBatch(selection); + + Observable.forkJoin(...batch) + .subscribe((data: ProcessedNodeData[]) => { + const processedItems: ProcessStatus = this.processStatus(data); + + this.notify(processedItems); + + if (processedItems.someSucceeded) { + this.delete.emit(); + } + }); } - - const batch = this.getDeleteNodesBatch(selection); - - Observable.forkJoin(...batch) - .subscribe((data: ProcessedNodeData[]) => { - const processedItems: ProcessStatus = this.processStatus(data); - - this.notify(processedItems); - - if (processedItems.someSucceeded) { - this.delete.emit(); - } - }); } - private getDeleteNodesBatch(selection: MinimalNodeEntity[]): Observable[] { + private getDeleteNodesBatch(selection: any): Observable[] { return selection.map((node) => this.deleteNode(node)); } - private deleteNode(node: MinimalNodeEntity): Observable { + private deleteNode(node: MinimalNodeEntity | DeletedNodeEntity): Observable { const id = ( node.entry).nodeId || node.entry.id; - const promise = this.alfrescoApiService.getInstance().nodes.deleteNode(id, { permanent: this.permanent }); + let promise; + + if (node.entry.hasOwnProperty('archivedAt')) { + promise = this.alfrescoApiService.getInstance().nodes.purgeDeletedNode(id); + } else { + promise = this.alfrescoApiService.getInstance().nodes.deleteNode(id, { permanent: this.permanent }); + } return Observable.fromPromise(promise) .map(() => ({ diff --git a/lib/core/services/translate-loader.service.ts b/lib/core/services/translate-loader.service.ts index 9e3c54f134..304d15f9ab 100644 --- a/lib/core/services/translate-loader.service.ts +++ b/lib/core/services/translate-loader.service.ts @@ -23,7 +23,7 @@ import { Observable } from 'rxjs/Observable'; import { ComponentTranslationModel } from '../models/component.model'; import { ObjectUtils } from '../utils/object-utils'; import { LogService } from './log.service'; -import { map } from 'rxjs/operators' +import { map } from 'rxjs/operators'; import 'rxjs/observable/forkJoin'; import 'rxjs/add/observable/forkJoin';