[ADF-325] added attach folder widget (#2831)

[ADF-325] added new attach folder widget
This commit is contained in:
Vito
2018-01-16 15:28:39 +01:00
committed by Eugenio Romano
parent 3671c12f35
commit 66262c4822
12 changed files with 393 additions and 12 deletions

View File

@@ -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();

View File

@@ -48,6 +48,19 @@ export class ContentNodeDialogService {
});
}
openFolderBrowseDialogBySite(): Observable<MinimalNodeEntryEntity[]> {
return this.siteService.getSites().switchMap((response: SitePaging) => {
return this.openFolderBrowseDialogByFolderId(response.list.entries[0].entry.guid);
});
}
openFolderBrowseDialogByFolderId(folderNodeId: string): Observable<MinimalNodeEntryEntity[]> {
return Observable.fromPromise(this.documentListService.getFolderNode(folderNodeId))
.switchMap((node: MinimalNodeEntryEntity) => {
return this.openUploadFolderDialog('Choose', node);
});
}
openCopyMoveDialog(action: string, contentEntry: MinimalNodeEntryEntity, permission?: string): Observable<MinimalNodeEntryEntity[]> {
if (this.contentService.hasPermission(contentEntry, permission)) {
@@ -74,6 +87,26 @@ export class ContentNodeDialogService {
}
}
openUploadFolderDialog(action: string, contentEntry: MinimalNodeEntryEntity): Observable<MinimalNodeEntryEntity[]> {
const select = new Subject<MinimalNodeEntryEntity[]>();
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<MinimalNodeEntryEntity[]> {
const select = new Subject<MinimalNodeEntryEntity[]>();
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');
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -0,0 +1,30 @@
<div class="adf-attach-folder-widget {{field.className}}"
[class.adf-invalid]="!field.isValid"
[class.adf-readonly]="field.readOnly">
<label class="adf-label" [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<div class="adf-attach-folder-widget-container">
<div *ngIf="hasFolder" class="adf-attach-folder-result">
<mat-icon>folder</mat-icon>
<div class="adf-attach-folder-files-row">
<span matLine id="{{'folder-'+field?.id}}"
role="button" tabindex="0" class="adf-folder">{{selectedFolderName}}</span>
<button *ngIf="!field.readOnly" mat-icon-button [id]="'folder-'+field?.id+'-remove'"
(click)="removeFolder();">
<mat-icon class="mat-24">highlight_off</mat-icon>
</button>
</div>
</div>
<div class="button-row" *ngIf="!hasFolder && !field.readOnly">
<button mat-raised-button
color="primary"
(click)="openSelectDialogFromFileSource()"
[id]="'folder-'+field?.id+'-button'">
{{ 'FORM.FIELD.UPLOAD' | translate }}
<mat-icon>cloud_upload</mat-icon>
</button>
</div>
</div>
<error-widget [error]="field.validationSummary"></error-widget>
<error-widget *ngIf="isInvalidFieldRequired()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
</div>

View File

@@ -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;
}
}

View File

@@ -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 = <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<AttachFolderWidgetComponent>;
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();
});
}));
});

View File

@@ -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;
}
}

View File

@@ -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 {}

View File

@@ -16,3 +16,4 @@
*/
export * from './attach-file-widget.component';
export * from './attach-folder-widget.component';

View File

@@ -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) {

View File

@@ -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<UserProcessModel[]>(observer => this.peopleSearchObserver = observer).share();
}