[ADF-1949 ] Allow customization of the dropdown menu on document picker (#2660)

* [ADF-1949] Allow customization of the dropdown menu on document picker

- add as input properties the 'dropdownHideMyFiles' and the 'dropdownSiteList' to the destination picker, so the list of sites from the dropdown can be customized, if wanted
- add navigation on '-mysites-' entries on document-list
- use custom dropdown on the copy/move document picker

* [ADF-1949] do not use a custom dropdown on the copy/move document picker on ADF side, because this can be done only on ACA side

* [ADF-1949] handling the node-dblclick event on content-node-selector.component instead of doing it on the document-list component

- and update the sites-dropdown documentation file

* [ADF-1949] changes requested on code review

* [ADF-1949] fix failing tests
This commit is contained in:
suzanadirla 2017-11-20 14:10:38 +02:00 committed by Eugenio Romano
parent 141bc0f8b4
commit edaa442e18
7 changed files with 103 additions and 43 deletions

View File

@ -29,6 +29,7 @@ Displays a dropdown menu to show and interact with the sites of the current user
| Attribute | Type | Default | Description | | Attribute | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| hideMyFiles | boolean | false | Hide the "My Files" option added to the list by default | | hideMyFiles | boolean | false | Hide the "My Files" option added to the list by default |
| siteList | any[] | null | A custom list of sites to be displayed by the dropdown. If no value is given, the sites of the current user are displayed by default. A list of objects only with properties 'title' and 'guid' is enough to be able to display the dropdown. |
### Events ### Events

View File

@ -28,6 +28,8 @@
<adf-sites-dropdown <adf-sites-dropdown
(change)="siteChanged($event)" (change)="siteChanged($event)"
[hideMyFiles]="dropdownHideMyFiles"
[siteList]="dropdownSiteList"
data-automation-id="content-node-selector-sites-combo"></adf-sites-dropdown> data-automation-id="content-node-selector-sites-combo"></adf-sites-dropdown>
<adf-toolbar> <adf-toolbar>
@ -60,6 +62,7 @@
[allowDropFiles]="false" [allowDropFiles]="false"
(folderChange)="onFolderChange()" (folderChange)="onFolderChange()"
(ready)="onFolderLoaded($event)" (ready)="onFolderLoaded($event)"
(node-dblclick)="onNodeDoubleClick($event)"
data-automation-id="content-node-selector-document-list"> data-automation-id="content-node-selector-document-list">
<empty-folder-content> <empty-folder-content>
<ng-template> <ng-template>

View File

@ -20,7 +20,7 @@ import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { MinimalNodeEntryEntity } from 'alfresco-js-api'; import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { ContentService, TranslationService, SearchService, SiteModel, SitesApiService, UserPreferencesService } from '@alfresco/adf-core'; import { AlfrescoApiService, ContentService, TranslationService, SearchService, SiteModel, SitesApiService, UserPreferencesService } from '@alfresco/adf-core';
import { DataTableModule } from '@alfresco/adf-core'; import { DataTableModule } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
import { MaterialModule } from '../material.module'; import { MaterialModule } from '../material.module';
@ -83,6 +83,7 @@ describe('ContentNodeSelectorComponent', () => {
ContentNodeSelectorComponent ContentNodeSelectorComponent
], ],
providers: [ providers: [
AlfrescoApiService,
ContentService, ContentService,
SitesApiService, SitesApiService,
TranslationService, TranslationService,
@ -173,13 +174,13 @@ describe('ContentNodeSelectorComponent', () => {
}); });
it('should be shown if dialogRef is injected', () => { it('should be shown if dialogRef is injected', () => {
const componentInstance = new ContentNodeSelectorComponent(null, null, fakePreference, data, dummyMdDialogRef); const componentInstance = new ContentNodeSelectorComponent(null, null, null, fakePreference, data, dummyMdDialogRef);
expect(componentInstance.inDialog).toBeTruthy(); expect(componentInstance.inDialog).toBeTruthy();
}); });
it('should should call the close method in the injected dialogRef', () => { it('should should call the close method in the injected dialogRef', () => {
spyOn(dummyMdDialogRef, 'close'); spyOn(dummyMdDialogRef, 'close');
const componentInstance = new ContentNodeSelectorComponent(null, null, fakePreference, data, dummyMdDialogRef); const componentInstance = new ContentNodeSelectorComponent(null, null, null, fakePreference, data, dummyMdDialogRef);
componentInstance.close(); componentInstance.close();

View File

@ -16,7 +16,7 @@
*/ */
import { Component, EventEmitter, Inject, Input, OnInit, Optional, Output, ViewChild, ViewEncapsulation } from '@angular/core'; import { Component, EventEmitter, Inject, Input, OnInit, Optional, Output, ViewChild, ViewEncapsulation } from '@angular/core';
import { ContentService, HighlightDirective, SiteModel, UserPreferencesService } from '@alfresco/adf-core'; import { AlfrescoApiService, ContentService, HighlightDirective, SiteModel, UserPreferencesService } from '@alfresco/adf-core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { MinimalNodeEntryEntity, NodePaging, Pagination } from 'alfresco-js-api'; import { MinimalNodeEntryEntity, NodePaging, Pagination } from 'alfresco-js-api';
import { DocumentListComponent, PaginationStrategy } from '../document-list/components/document-list.component'; import { DocumentListComponent, PaginationStrategy } from '../document-list/components/document-list.component';
@ -28,6 +28,8 @@ import { ContentNodeSelectorService } from './content-node-selector.service';
export interface ContentNodeSelectorComponentData { export interface ContentNodeSelectorComponentData {
title: string; title: string;
currentFolderId?: string; currentFolderId?: string;
dropdownHideMyFiles?: boolean;
dropdownSiteList?: any[];
rowFilter?: RowFilter; rowFilter?: RowFilter;
imageResolver?: ImageResolver; imageResolver?: ImageResolver;
select: EventEmitter<MinimalNodeEntryEntity[]>; select: EventEmitter<MinimalNodeEntryEntity[]>;
@ -60,6 +62,12 @@ export class ContentNodeSelectorComponent implements OnInit {
@Input() @Input()
currentFolderId: string | null = null; currentFolderId: string | null = null;
@Input()
dropdownHideMyFiles: boolean = false;
@Input()
dropdownSiteList: any[] = null;
@Input() @Input()
rowFilter: RowFilter = null; rowFilter: RowFilter = null;
@ -80,6 +88,7 @@ export class ContentNodeSelectorComponent implements OnInit {
constructor(private contentNodeSelectorService: ContentNodeSelectorService, constructor(private contentNodeSelectorService: ContentNodeSelectorService,
private contentService: ContentService, private contentService: ContentService,
private apiService: AlfrescoApiService,
private preferences: UserPreferencesService, private preferences: UserPreferencesService,
@Optional() @Inject(MAT_DIALOG_DATA) data?: ContentNodeSelectorComponentData, @Optional() @Inject(MAT_DIALOG_DATA) data?: ContentNodeSelectorComponentData,
@Optional() private containingDialog?: MatDialogRef<ContentNodeSelectorComponent>) { @Optional() private containingDialog?: MatDialogRef<ContentNodeSelectorComponent>) {
@ -87,6 +96,8 @@ export class ContentNodeSelectorComponent implements OnInit {
this.title = data.title; this.title = data.title;
this.select = data.select; this.select = data.select;
this.currentFolderId = data.currentFolderId; this.currentFolderId = data.currentFolderId;
this.dropdownHideMyFiles = data.dropdownHideMyFiles;
this.dropdownSiteList = data.dropdownSiteList;
this.rowFilter = data.rowFilter; this.rowFilter = data.rowFilter;
this.imageResolver = data.imageResolver; this.imageResolver = data.imageResolver;
} }
@ -295,4 +306,21 @@ export class ContentNodeSelectorComponent implements OnInit {
close(): void { close(): void {
this.containingDialog.close(); this.containingDialog.close();
} }
onNodeDoubleClick(e: CustomEvent) {
const node: any = e.detail.node.entry;
if (node && node.guid) {
const options = {
maxItems: this.pageSize,
skipCount: this.skipCount,
include: ['path', 'properties', 'allowableOperations']
};
this.apiService.nodesApi.getNode(node.guid, options)
.then(documentLibrary => {
this.documentList.performCustomSourceNavigation(documentLibrary);
});
}
}
} }

View File

@ -287,7 +287,7 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
if (this.folderNode) { if (this.folderNode) {
this.loadFolder(merge); this.loadFolder(merge);
} else if (this.currentFolderId) { } else if (this.currentFolderId) {
this.loadFolderByNodeId(this.currentFolderId); this.loadFolderByNodeId(this.currentFolderId, merge);
} else if (this.node) { } else if (this.node) {
this.data.loadPage(this.node); this.data.loadPage(this.node);
this.ready.emit(this.node); this.ready.emit(this.node);
@ -376,15 +376,27 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
performNavigation(node: MinimalNodeEntity): boolean { performNavigation(node: MinimalNodeEntity): boolean {
if (this.canNavigateFolder(node)) { if (this.canNavigateFolder(node)) {
this.updateFolderData(node);
return true;
}
return false;
}
performCustomSourceNavigation(node: MinimalNodeEntity): boolean {
if (this.isCustomSource(this.currentFolderId)) {
this.updateFolderData(node);
return true;
}
return false;
}
updateFolderData(node: MinimalNodeEntity): void {
this.currentFolderId = node.entry.id; this.currentFolderId = node.entry.id;
this.folderNode = node.entry; this.folderNode = node.entry;
this.skipCount = 0; this.skipCount = 0;
this.currentNodeAllowableOperations = node.entry['allowableOperations'] ? node.entry['allowableOperations'] : []; this.currentNodeAllowableOperations = node.entry['allowableOperations'] ? node.entry['allowableOperations'] : [];
this.loadFolder(); this.loadFolder();
this.folderChange.emit(new NodeEntryEvent(node.entry)); this.folderChange.emit(new NodeEntryEvent(node.entry));
return true;
}
return false;
} }
/** /**
@ -418,28 +430,32 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
} }
let nodeId = this.folderNode ? this.folderNode.id : this.currentFolderId; let nodeId = this.folderNode ? this.folderNode.id : this.currentFolderId;
if (!this.hasCustomLayout) {
this.setupDefaultColumns(nodeId);
}
if (nodeId) { if (nodeId) {
this.loadFolderNodesByFolderNodeId(nodeId, this.maxItems, this.skipCount, merge).catch(err => this.error.emit(err)); this.loadFolderNodesByFolderNodeId(nodeId, this.maxItems, this.skipCount, merge).catch(err => this.error.emit(err));
} }
} }
// gets folder node and its content // gets folder node and its content
loadFolderByNodeId(nodeId: string) { loadFolderByNodeId(nodeId: string, merge: boolean = false) {
this.loading = true; this.loading = true;
this.resetSelection(); this.resetSelection();
if (nodeId === '-trashcan-') { if (nodeId === '-trashcan-') {
this.loadTrashcan(); this.loadTrashcan(merge);
} else if (nodeId === '-sharedlinks-') { } else if (nodeId === '-sharedlinks-') {
this.loadSharedLinks(); this.loadSharedLinks(merge);
} else if (nodeId === '-sites-') { } else if (nodeId === '-sites-') {
this.loadSites(); this.loadSites(merge);
} else if (nodeId === '-mysites-') { } else if (nodeId === '-mysites-') {
this.loadMemberSites(); this.loadMemberSites(merge);
} else if (nodeId === '-favorites-') { } else if (nodeId === '-favorites-') {
this.loadFavorites(); this.loadFavorites(merge);
} else if (nodeId === '-recent-') { } else if (nodeId === '-recent-') {
this.loadRecent(); this.loadRecent(merge);
} else { } else {
this.documentListService this.documentListService
.getFolderNode(nodeId) .getFolderNode(nodeId)
@ -448,7 +464,7 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
this.currentFolderId = node.id; this.currentFolderId = node.id;
this.skipCount = 0; this.skipCount = 0;
this.currentNodeAllowableOperations = node['allowableOperations'] ? node['allowableOperations'] : []; this.currentNodeAllowableOperations = node['allowableOperations'] ? node['allowableOperations'] : [];
return this.loadFolderNodesByFolderNodeId(node.id, this.maxItems, this.skipCount); return this.loadFolderNodesByFolderNodeId(node.id, this.maxItems, this.skipCount, merge);
}) })
.catch(err => { .catch(err => {
if (JSON.parse(err.message).error.statusCode === 403) { if (JSON.parse(err.message).error.statusCode === 403) {
@ -502,29 +518,29 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
changePage.maxItems.currentValue !== changePage.maxItems.previousValue; changePage.maxItems.currentValue !== changePage.maxItems.previousValue;
} }
private loadTrashcan(): void { private loadTrashcan(merge: boolean = false): void {
const options = { const options = {
include: ['path', 'properties'], include: ['path', 'properties'],
maxItems: this.maxItems, maxItems: this.maxItems,
skipCount: this.skipCount skipCount: this.skipCount
}; };
this.apiService.nodesApi.getDeletedNodes(options) this.apiService.nodesApi.getDeletedNodes(options)
.then((page: DeletedNodesPaging) => this.onPageLoaded(page)) .then((page: DeletedNodesPaging) => this.onPageLoaded(page, merge))
.catch(error => this.error.emit(error)); .catch(error => this.error.emit(error));
} }
private loadSharedLinks(): void { private loadSharedLinks(merge: boolean = false): void {
const options = { const options = {
include: ['properties', 'allowableOperations', 'path'], include: ['properties', 'allowableOperations', 'path'],
maxItems: this.maxItems, maxItems: this.maxItems,
skipCount: this.skipCount skipCount: this.skipCount
}; };
this.apiService.sharedLinksApi.findSharedLinks(options) this.apiService.sharedLinksApi.findSharedLinks(options)
.then((page: NodePaging) => this.onPageLoaded(page)) .then((page: NodePaging) => this.onPageLoaded(page, merge))
.catch(error => this.error.emit(error)); .catch(error => this.error.emit(error));
} }
private loadSites(): void { private loadSites(merge: boolean = false): void {
const options = { const options = {
include: ['properties'], include: ['properties'],
maxItems: this.maxItems, maxItems: this.maxItems,
@ -532,11 +548,11 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
}; };
this.apiService.sitesApi.getSites(options) this.apiService.sitesApi.getSites(options)
.then((page: NodePaging) => this.onPageLoaded(page)) .then((page: NodePaging) => this.onPageLoaded(page, merge))
.catch(error => this.error.emit(error)); .catch(error => this.error.emit(error));
} }
private loadMemberSites(): void { private loadMemberSites(merge: boolean = false): void {
const options = { const options = {
include: ['properties'], include: ['properties'],
maxItems: this.maxItems, maxItems: this.maxItems,
@ -548,19 +564,22 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
let page: NodePaging = { let page: NodePaging = {
list: { list: {
entries: result.list.entries entries: result.list.entries
.map(({ entry: { site } }: any) => ({ .map(({entry: {site}}: any) => {
site.allowableOperations = site.allowableOperations ? site.allowableOperations : [this.CREATE_PERMISSION];
return {
entry: site entry: site
})), };
}),
pagination: result.list.pagination pagination: result.list.pagination
} }
}; };
this.onPageLoaded(page); this.onPageLoaded(page, merge);
}) })
.catch(error => this.error.emit(error)); .catch(error => this.error.emit(error));
} }
private loadFavorites(): void { private loadFavorites(merge: boolean = false): void {
const options = { const options = {
maxItems: this.maxItems, maxItems: this.maxItems,
skipCount: this.skipCount, skipCount: this.skipCount,
@ -586,12 +605,12 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
pagination: result.list.pagination pagination: result.list.pagination
} }
}; };
this.onPageLoaded(page); this.onPageLoaded(page, merge);
}) })
.catch(error => this.error.emit(error)); .catch(error => this.error.emit(error));
} }
private loadRecent(): void { private loadRecent(merge: boolean = false): void {
this.apiService.peopleApi.getPerson('-me-') this.apiService.peopleApi.getPerson('-me-')
.then((person: PersonEntry) => { .then((person: PersonEntry) => {
const username = person.entry.id; const username = person.entry.id;
@ -619,13 +638,13 @@ export class DocumentListComponent implements OnInit, OnChanges, AfterContentIni
return this.apiService.searchApi.search(query); return this.apiService.searchApi.search(query);
}) })
.then((page: NodePaging) => this.onPageLoaded(page)) .then((page: NodePaging) => this.onPageLoaded(page, merge))
.catch(error => this.error.emit(error)); .catch(error => this.error.emit(error));
} }
private onPageLoaded(page: NodePaging) { private onPageLoaded(page: NodePaging, merge: boolean = false) {
if (page) { if (page) {
this.data.loadPage(page); this.data.loadPage(page, merge);
this.loading = false; this.loading = false;
this.ready.emit(page); this.ready.emit(page);
} }

View File

@ -10,7 +10,7 @@
(ngModelChange)="selectedSite()"> (ngModelChange)="selectedSite()">
<mat-option *ngIf="!hideMyFiles" data-automation-id="site-my-files-option" id="default_site_option" [value]="MY_FILES_VALUE">{{'DROPDOWN.MY_FILES_OPTION' | translate}}</mat-option> <mat-option *ngIf="!hideMyFiles" data-automation-id="site-my-files-option" id="default_site_option" [value]="MY_FILES_VALUE">{{'DROPDOWN.MY_FILES_OPTION' | translate}}</mat-option>
<mat-option *ngFor="let site of siteList" [value]="site.guid"> <mat-option *ngFor="let site of siteList" [value]="site.guid">
{{ site.title }} {{ site.title | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>

View File

@ -28,22 +28,22 @@ export class DropdownSitesComponent implements OnInit {
@Input() @Input()
hideMyFiles: boolean = false; hideMyFiles: boolean = false;
@Input()
siteList: any[] = null;
@Output() @Output()
change: EventEmitter<SiteModel> = new EventEmitter(); change: EventEmitter<SiteModel> = new EventEmitter();
public MY_FILES_VALUE = 'default'; public MY_FILES_VALUE = 'default';
siteList = [];
public siteSelected: string; public siteSelected: string;
constructor(private sitesService: SitesApiService) {} constructor(private sitesService: SitesApiService) {}
ngOnInit() { ngOnInit() {
this.sitesService.getSites().subscribe((result) => { if (!this.siteList) {
this.siteList = result; this.setDefaultSiteList();
}, }
(error) => {});
} }
selectedSite() { selectedSite() {
@ -56,4 +56,12 @@ export class DropdownSitesComponent implements OnInit {
this.change.emit(siteFound); this.change.emit(siteFound);
} }
setDefaultSiteList() {
this.siteList = [];
this.sitesService.getSites().subscribe((result) => {
this.siteList = result;
},
(error) => {});
}
} }