diff --git a/lib/content-services/content-node-selector/content-node-dialog.service.spec.ts b/lib/content-services/content-node-selector/content-node-dialog.service.spec.ts index d5ca3f3a41..d8a0102ad7 100644 --- a/lib/content-services/content-node-selector/content-node-dialog.service.spec.ts +++ b/lib/content-services/content-node-selector/content-node-dialog.service.spec.ts @@ -106,7 +106,7 @@ describe('ContentNodeDialogService', () => { expect(materialDialog.open).toHaveBeenCalled(); })); - it('should be able to open the dialog using the first user site', fakeAsync(() => { + it('should be able to open the dialog for files using the first user site', fakeAsync(() => { spyOn(sitesService, 'getSites').and.returnValue(Observable.of(fakeSiteList)); spyOn(documentListService, 'getFolderNode').and.returnValue(Promise.resolve(fakeNode)); service.openFileBrowseDialogBySite().subscribe(); @@ -114,6 +114,14 @@ describe('ContentNodeDialogService', () => { expect(materialDialog.open).toHaveBeenCalled(); })); + it('should be able to open the dialog for folder using the first user site', fakeAsync(() => { + spyOn(sitesService, 'getSites').and.returnValue(Observable.of(fakeSiteList)); + spyOn(documentListService, 'getFolderNode').and.returnValue(Promise.resolve(fakeNode)); + service.openFolderBrowseDialogBySite().subscribe(); + tick(); + expect(materialDialog.open).toHaveBeenCalled(); + })); + it('should be able to close the material dialog', () => { service.close(); expect(materialDialog.closeAll).toHaveBeenCalled(); diff --git a/lib/content-services/content-node-selector/content-node-dialog.service.ts b/lib/content-services/content-node-selector/content-node-dialog.service.ts index 20cd9d7f32..8f375781d4 100644 --- a/lib/content-services/content-node-selector/content-node-dialog.service.ts +++ b/lib/content-services/content-node-selector/content-node-dialog.service.ts @@ -48,6 +48,19 @@ export class ContentNodeDialogService { }); } + openFolderBrowseDialogBySite(): Observable { + return this.siteService.getSites().switchMap((response: SitePaging) => { + return this.openFolderBrowseDialogByFolderId(response.list.entries[0].entry.guid); + }); + } + + openFolderBrowseDialogByFolderId(folderNodeId: string): Observable { + return Observable.fromPromise(this.documentListService.getFolderNode(folderNodeId)) + .switchMap((node: MinimalNodeEntryEntity) => { + return this.openUploadFolderDialog('Choose', node); + }); + } + openCopyMoveDialog(action: string, contentEntry: MinimalNodeEntryEntity, permission?: string): Observable { if (this.contentService.hasPermission(contentEntry, permission)) { @@ -74,6 +87,26 @@ export class ContentNodeDialogService { } } + openUploadFolderDialog(action: string, contentEntry: MinimalNodeEntryEntity): Observable { + const select = new Subject(); + select.subscribe({ + complete: this.close.bind(this) + }); + + const data: ContentNodeSelectorComponentData = { + title: `${action} '${contentEntry.name}' to ...`, + actionName: action, + currentFolderId: contentEntry.id, + imageResolver: this.imageResolver.bind(this), + isSelectionValid: this.hasPermissionOnNodeFolder.bind(this), + rowFilter : this.rowFilter.bind(this, contentEntry.id), + select: select + }; + + this.openContentNodeDialog(data, 'adf-content-node-selector-dialog', '630px'); + return select; + } + openUploadFileDialog(action: string, contentEntry: MinimalNodeEntryEntity): Observable { const select = new Subject(); select.subscribe({ @@ -120,6 +153,14 @@ export class ContentNodeDialogService { return entry.isFile; } + private hasPermissionOnNodeFolder(entry: MinimalNodeEntryEntity): boolean { + return this.isNodeFolder(entry) && this.contentService.hasPermission(entry, 'create'); + } + + private isNodeFolder(entry: MinimalNodeEntryEntity): boolean { + return entry.isFolder; + } + private hasEntityCreatePermission(entry: MinimalNodeEntryEntity): boolean { return this.contentService.hasPermission(entry, 'create'); } diff --git a/lib/core/card-view/card-view.module.ts b/lib/core/card-view/card-view.module.ts index 8cacf66803..416aff18c9 100644 --- a/lib/core/card-view/card-view.module.ts +++ b/lib/core/card-view/card-view.module.ts @@ -54,7 +54,7 @@ import { CardViewUpdateService } from './services/card-view-update.service'; MatIconModule, MatButtonModule, MatDatetimepickerModule, - MatNativeDatetimeModule + MatNativeDatetimeModule ], declarations: [ CardViewComponent, @@ -69,7 +69,7 @@ import { CardViewUpdateService } from './services/card-view-update.service'; CardViewBoolItemComponent, CardViewDateItemComponent, CardViewMapItemComponent, - CardViewTextItemComponent + CardViewTextItemComponent ], exports: [ CardViewComponent, diff --git a/lib/core/viewer/components/viewer.component.ts b/lib/core/viewer/components/viewer.component.ts index 3273aa619c..8e1a6a1dec 100644 --- a/lib/core/viewer/components/viewer.component.ts +++ b/lib/core/viewer/components/viewer.component.ts @@ -455,7 +455,7 @@ export class ViewerComponent implements OnChanges { this.urlFileContent = this.apiService.contentApi.getRenditionUrl(nodeId, 'pdf'); } else if (status === 'NOT_CREATED') { try { - await this.renditionService.convert(nodeId, 'pdf').toPromise() + await this.renditionService.convert(nodeId, 'pdf').toPromise(); this.viewerType = 'pdf'; this.urlFileContent = this.apiService.contentApi.getRenditionUrl(nodeId, 'pdf'); } catch { diff --git a/lib/process-services/content-widget/attach-folder-widget.component.html b/lib/process-services/content-widget/attach-folder-widget.component.html new file mode 100644 index 0000000000..9d67f76422 --- /dev/null +++ b/lib/process-services/content-widget/attach-folder-widget.component.html @@ -0,0 +1,30 @@ +
+ +
+
+ folder +
+ {{selectedFolderName}} + +
+
+ +
+ +
+
+ + +
diff --git a/lib/process-services/content-widget/attach-folder-widget.component.scss b/lib/process-services/content-widget/attach-folder-widget.component.scss new file mode 100644 index 0000000000..a7226dbf0c --- /dev/null +++ b/lib/process-services/content-widget/attach-folder-widget.component.scss @@ -0,0 +1,39 @@ +.adf { + + &-attach-folder-widget-container { + margin-bottom: 15px; + display: flex; + align-items: center; + + input { + cursor: pointer; + height: 100%; + right: 0; + opacity: 0; + position: absolute; + top: 0; + width: 300px; + z-index: 4; + } + } + + &-attach-folder-widget { + width: 100%; + word-break: break-all; + padding: 0.4375em 0; + border-top: 0.84375em solid transparent; + } + + &-attach-folder-files-row { + padding-left: 8px; + .mat-line { + margin-bottom: 0px; + } + } + + &-attach-folder-result { + display: flex; + align-items: center; + } + +} diff --git a/lib/process-services/content-widget/attach-folder-widget.component.spec.ts b/lib/process-services/content-widget/attach-folder-widget.component.spec.ts new file mode 100644 index 0000000000..3f72aed067 --- /dev/null +++ b/lib/process-services/content-widget/attach-folder-widget.component.spec.ts @@ -0,0 +1,169 @@ +/*! + * @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. + */ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { AttachFolderWidgetComponent } from './attach-folder-widget.component'; +import { + FormFieldModel, + FormModel, + FormService, + AlfrescoApiService, + LogService, + ThumbnailService, + SitesService, + NodesApiService +} from '@alfresco/adf-core'; +import { ContentNodeDialogService, DocumentListService } from '@alfresco/adf-content-services'; +import { MaterialModule } from '../material.module'; +import { Observable } from 'rxjs/Observable'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; + +const fakeMinimalNode: MinimalNodeEntryEntity = { + id: 'fake', + name: 'fake-name' +}; + +const definedSourceParams = { + folderSource : { + serviceId: 'goofy-sources', + name: 'pippo-baudo', + selectedFolder: { + accountId: 'goku-share-account-id', + pathId: 'fake-pippo-baudo-id' + } + } +}; + +describe('AttachFolderWidgetComponent', () => { + + let widget: AttachFolderWidgetComponent; + let fixture: ComponentFixture; + let element: HTMLInputElement; + let contentNodeDialogService: ContentNodeDialogService; + let nodeService: NodesApiService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MaterialModule], + declarations: [AttachFolderWidgetComponent], + providers: [ + FormService, + ThumbnailService, + AlfrescoApiService, + LogService, + SitesService, + DocumentListService, + ContentNodeDialogService, + NodesApiService + ] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(AttachFolderWidgetComponent); + widget = fixture.componentInstance; + element = fixture.nativeElement; + contentNodeDialogService = TestBed.get(ContentNodeDialogService); + nodeService = TestBed.get(NodesApiService); + }); + })); + + afterEach(() => { + fixture.destroy(); + }); + + it('should be able to create the widget', () => { + expect(widget).not.toBeNull(); + }); + + it('should be rendered correctly', () => { + expect(widget).not.toBeNull(); + widget.field = new FormFieldModel(new FormModel(), { + type: 'select-folder', + id: 'fake-widget', + value: null + }); + fixture.detectChanges(); + expect(element.querySelector('#folder-fake-widget-button')).toBeDefined(); + expect(element.querySelector('#folder-fake-widget-button')).not.toBeNull(); + }); + + it('should show the folder selected by content node', async(() => { + spyOn(contentNodeDialogService, 'openFolderBrowseDialogBySite').and.returnValue(Observable.of([fakeMinimalNode])); + expect(widget).not.toBeNull(); + widget.field = new FormFieldModel(new FormModel(), { + type: 'select-folder', + id: 'fake-widget', + value: null + }); + fixture.detectChanges(); + fixture.debugElement.query(By.css('#folder-fake-widget-button')).nativeElement.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('#folder-fake-widget')).not.toBeNull(); + }); + })); + + it('should show the folder selected by content node opening on a configured folder', async(() => { + spyOn(contentNodeDialogService, 'openFolderBrowseDialogByFolderId').and.returnValue(Observable.of([fakeMinimalNode])); + expect(widget).not.toBeNull(); + widget.field = new FormFieldModel(new FormModel(), { + type: 'select-folder', + id: 'fake-widget', + value: null, + params: definedSourceParams + }); + fixture.detectChanges(); + fixture.debugElement.query(By.css('#folder-fake-widget-button')).nativeElement.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('#folder-fake-widget')).not.toBeNull(); + }); + })); + + it('should retrieve the node information on init', async(() => { + spyOn(nodeService, 'getNode').and.returnValue(Observable.of(fakeMinimalNode)); + expect(widget).not.toBeNull(); + widget.field = new FormFieldModel(new FormModel(), { + type: 'select-folder', + id: 'fake-widget', + value: 'fake-pippo-baudo-id' + }); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('#folder-fake-widget')).not.toBeNull(); + expect(element.querySelector('#folder-fake-widget-button')).toBeNull(); + }); + })); + + it('should remove the folder via the remove button', async(() => { + spyOn(nodeService, 'getNode').and.returnValue(Observable.of(fakeMinimalNode)); + expect(widget).not.toBeNull(); + widget.field = new FormFieldModel(new FormModel(), { + type: 'select-folder', + id: 'fake-widget', + value: 'fake-pippo-baudo-id' + }); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(element.querySelector('#folder-fake-widget')).not.toBeNull(); + expect(element.querySelector('#folder-fake-widget-button')).toBeNull(); + fixture.debugElement.query(By.css('#folder-fake-widget-remove')).nativeElement.click(); + fixture.detectChanges(); + expect(element.querySelector('#folder-fake-widget')).toBeNull(); + }); + })); + +}); diff --git a/lib/process-services/content-widget/attach-folder-widget.component.ts b/lib/process-services/content-widget/attach-folder-widget.component.ts new file mode 100644 index 0000000000..c8769f2f97 --- /dev/null +++ b/lib/process-services/content-widget/attach-folder-widget.component.ts @@ -0,0 +1,89 @@ +/*! + * @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. + */ + +/* tslint:disable:component-selector*/ + +import { Component, ViewEncapsulation, OnInit } from '@angular/core'; +import { + baseHost, + WidgetComponent, + FormService, + NodesApiService +} from '@alfresco/adf-core'; +import { ContentNodeDialogService } from '@alfresco/adf-content-services'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; + +@Component({ + selector: 'attach-folder-widget', + templateUrl: './attach-folder-widget.component.html', + styleUrls: ['./attach-folder-widget.component.scss'], + host: baseHost, + encapsulation: ViewEncapsulation.None +}) +export class AttachFolderWidgetComponent extends WidgetComponent implements OnInit { + + hasFolder: boolean = false; + selectedFolderName: string = ''; + + constructor(private contentDialog: ContentNodeDialogService, + public formService: FormService, + private nodeService: NodesApiService) { + super(); + } + + ngOnInit() { + if (this.field && + this.field.value) { + this.hasFolder = true; + this.nodeService.getNode(this.field.value).subscribe((node: MinimalNodeEntryEntity) => { + this.selectedFolderName = node.name; + }); + } + } + + isDefinedSourceFolder(): boolean { + return !!this.field.params && + !!this.field.params.folderSource && + !!this.field.params.folderSource.selectedFolder; + } + + openSelectDialogFromFileSource() { + let params = this.field.params; + if (this.isDefinedSourceFolder()) { + this.contentDialog.openFolderBrowseDialogByFolderId(params.folderSource.selectedFolder.pathId).subscribe( + (selections: MinimalNodeEntryEntity[]) => { + this.selectedFolderName = selections[0].name; + this.field.value = selections[0].id; + this.hasFolder = true; + }); + } else { + this.contentDialog.openFolderBrowseDialogBySite().subscribe( + (selections: MinimalNodeEntryEntity[]) => { + this.selectedFolderName = selections[0].name; + this.field.value = selections[0].id; + this.hasFolder = true; + }); + } + } + + removeFolder() { + this.field.value = null; + this.selectedFolderName = ''; + this.hasFolder = false; + } + +} diff --git a/lib/process-services/content-widget/content-widget.module.ts b/lib/process-services/content-widget/content-widget.module.ts index 23febdf281..336ca2705d 100644 --- a/lib/process-services/content-widget/content-widget.module.ts +++ b/lib/process-services/content-widget/content-widget.module.ts @@ -23,6 +23,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { DataColumnModule, DataTableModule, FormModule } from '@alfresco/adf-core'; import { AttachFileWidgetComponent } from './attach-file-widget.component'; +import { AttachFolderWidgetComponent } from './attach-folder-widget.component'; @NgModule({ imports: [ @@ -36,13 +37,16 @@ import { AttachFileWidgetComponent } from './attach-file-widget.component'; FormModule ], entryComponents: [ - AttachFileWidgetComponent + AttachFileWidgetComponent, + AttachFolderWidgetComponent ], declarations: [ - AttachFileWidgetComponent + AttachFileWidgetComponent, + AttachFolderWidgetComponent ], exports: [ - AttachFileWidgetComponent + AttachFileWidgetComponent, + AttachFolderWidgetComponent ] }) export class ContentWidgetModule {} diff --git a/lib/process-services/content-widget/public-api.ts b/lib/process-services/content-widget/public-api.ts index 01d0e88487..592b6804b9 100644 --- a/lib/process-services/content-widget/public-api.ts +++ b/lib/process-services/content-widget/public-api.ts @@ -16,3 +16,4 @@ */ export * from './attach-file-widget.component'; +export * from './attach-folder-widget.component'; diff --git a/lib/process-services/process-list/components/start-process.component.ts b/lib/process-services/process-list/components/start-process.component.ts index f20729e845..c0af3d8cf0 100644 --- a/lib/process-services/process-list/components/start-process.component.ts +++ b/lib/process-services/process-list/components/start-process.component.ts @@ -16,14 +16,12 @@ */ import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; -import { StartFormComponent - , FormRenderingService -} from '@alfresco/adf-core'; +import { StartFormComponent, FormRenderingService } from '@alfresco/adf-core'; import { ProcessInstanceVariable } from '../models/process-instance-variable.model'; import { ProcessDefinitionRepresentation } from './../models/process-definition.model'; import { ProcessInstance } from './../models/process-instance.model'; import { ProcessService } from './../services/process.service'; -import { AttachFileWidgetComponent } from '../../content-widget'; +import { AttachFileWidgetComponent, AttachFolderWidgetComponent } from '../../content-widget'; @Component({ selector: 'adf-start-process', @@ -62,6 +60,7 @@ export class StartProcessInstanceComponent implements OnChanges { constructor(private activitiProcess: ProcessService, private formRenderingService: FormRenderingService) { this.formRenderingService.setComponentTypeResolver('upload', () => AttachFileWidgetComponent, true); + this.formRenderingService.setComponentTypeResolver('select-folder', () => AttachFolderWidgetComponent, true); } ngOnChanges(changes: SimpleChanges) { diff --git a/lib/process-services/task-list/components/task-details.component.ts b/lib/process-services/task-list/components/task-details.component.ts index a77bad3239..17db8dce4e 100644 --- a/lib/process-services/task-list/components/task-details.component.ts +++ b/lib/process-services/task-list/components/task-details.component.ts @@ -35,7 +35,7 @@ import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { TaskDetailsModel } from '../models/task-details.model'; import { TaskListService } from './../services/tasklist.service'; import { CommentsComponent } from '../../comments'; -import { AttachFileWidgetComponent } from '../../content-widget'; +import { AttachFileWidgetComponent, AttachFolderWidgetComponent } from '../../content-widget'; @Component({ selector: 'adf-task-details', @@ -147,6 +147,7 @@ export class TaskDetailsComponent implements OnInit, OnChanges { private cardViewUpdateService: CardViewUpdateService, private dialog: MatDialog) { + this.formRenderingService.setComponentTypeResolver('select-folder', () => AttachFolderWidgetComponent, true); this.formRenderingService.setComponentTypeResolver('upload', () => AttachFileWidgetComponent, true); this.peopleSearch$ = new Observable(observer => this.peopleSearchObserver = observer).share(); }