[AAE-3321] Select uploaded local files by default (#6079)

* [AAE-3111] Select uploaded files by default

* [AAE-3321] Select uploaded local files by default

* * After rebase

* *  Renamed method/property names

* * Fixed comments
* Added a private method to bubble up preselected nodes

* * Added unit tests

* * Fixed typo* added doc* Preselect based on the selection mode

* * Added way to test in demo shell
This commit is contained in:
siva kumar 2020-09-14 13:37:41 +05:30 committed by GitHub
parent a64e13bec5
commit 6fa02548ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 429 additions and 34 deletions

View File

@ -13,6 +13,7 @@
[currentFolderId]="'-recent-'" [currentFolderId]="'-recent-'"
locationFormat="/files" locationFormat="/files"
[display]="'gallery'" [display]="'gallery'"
[preselectNodes]="selectedNodes"
[showHeader]="false" [showHeader]="false"
[maxItems]="5" [maxItems]="5"
(preview)="showFile($event)" (preview)="showFile($event)"
@ -221,6 +222,7 @@
[contentActions]="true" [contentActions]="true"
[allowDropFiles]="allowDropFiles" [allowDropFiles]="allowDropFiles"
[selectionMode]="selectionMode" [selectionMode]="selectionMode"
[preselectNodes]="selectedNodes"
[multiselect]="multiselect" [multiselect]="multiselect"
[display]="displayMode" [display]="displayMode"
[node]="nodeResult" [node]="nodeResult"
@ -550,7 +552,7 @@
</section> </section>
<section> <section>
<mat-slide-toggle id="adf-multiple-upload-switch" [color]="'primary'" [(ngModel)]="multipleFileUpload"> <mat-slide-toggle id="adf-multiple-upload-switch" [color]="'primary'" (change)="onMultipleFilesUpload()" [(ngModel)]="multipleFileUpload" >
{{'DOCUMENT_LIST.MULTIPLE_FILE_UPLOAD' | translate}} {{'DOCUMENT_LIST.MULTIPLE_FILE_UPLOAD' | translate}}
</mat-slide-toggle> </mat-slide-toggle>
</section> </section>
@ -643,6 +645,21 @@
</mat-slide-toggle> </mat-slide-toggle>
</section> </section>
<section>
<mat-slide-toggle
color="primary" [(ngModel)]="preselectNodes" id="preselectNodes">
Preselect Nodes
</mat-slide-toggle>
</section>
<form class="example-form">
<mat-form-field *ngIf="preselectNodes" class="adf-preselect-nodes-input">
<input matInput
(input)="setPreselectNodes($event.target?.value)"
placeholder="NodeEntry[] => [{ entry: { isFile: true, id: 'node-id' }}, { entry: { isFile: true, id: 'node-id' }} ]">
</mat-form-field>
</form>
<h5>Upload</h5> <h5>Upload</h5>
<section *ngIf="acceptedFilesTypeShow"> <section *ngIf="acceptedFilesTypeShow">
<mat-form-field floatPlaceholder="float"> <mat-form-field floatPlaceholder="float">

View File

@ -88,4 +88,8 @@
justify-content: center; justify-content: center;
} }
} }
.adf-preselect-nodes-input {
width: 100%;
}
} }

View File

@ -23,13 +23,14 @@ import { Location } from '@angular/common';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Params, Router } from '@angular/router';
import { MinimalNodeEntity, NodePaging, Pagination, MinimalNodeEntryEntity, SiteEntry, SearchEntry } from '@alfresco/js-api'; import { MinimalNodeEntity, NodePaging, Pagination, MinimalNodeEntryEntity, SiteEntry, SearchEntry, NodeEntry } from '@alfresco/js-api';
import { import {
AlfrescoApiService, AuthenticationService, AppConfigService, AppConfigValues, ContentService, TranslationService, FolderCreatedEvent, LogService, NotificationService, AlfrescoApiService, AuthenticationService, AppConfigService, AppConfigValues, ContentService, TranslationService, FolderCreatedEvent, LogService, NotificationService,
UploadService, DataRow, UserPreferencesService, UploadService, DataRow, UserPreferencesService,
PaginationComponent, FormValues, DisplayMode, ShowHeaderMode, InfinitePaginationComponent, HighlightDirective, PaginationComponent, FormValues, DisplayMode, ShowHeaderMode, InfinitePaginationComponent, HighlightDirective,
SharedLinksApiService, SharedLinksApiService,
FormRenderingService FormRenderingService,
FileUploadEvent
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { import {
@ -47,7 +48,7 @@ import { VersionManagerDialogAdapterComponent } from './version-manager-dialog-a
import { MetadataDialogAdapterComponent } from './metadata-dialog-adapter.component'; import { MetadataDialogAdapterComponent } from './metadata-dialog-adapter.component';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { PreviewService } from '../../services/preview.service'; import { PreviewService } from '../../services/preview.service';
import { takeUntil } from 'rxjs/operators'; import { takeUntil, debounceTime, scan } from 'rxjs/operators';
const DEFAULT_FOLDER_TO_SHOW = '-my-'; const DEFAULT_FOLDER_TO_SHOW = '-my-';
@ -208,6 +209,7 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
permissionsStyle: PermissionStyleModel[] = []; permissionsStyle: PermissionStyleModel[] = [];
infiniteScrolling: boolean; infiniteScrolling: boolean;
stickyHeader: boolean; stickyHeader: boolean;
preselectNodes: boolean;
warnOnMultipleUploads = false; warnOnMultipleUploads = false;
thumbnails = false; thumbnails = false;
@ -216,6 +218,8 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
displayEmptyMetadata = false; displayEmptyMetadata = false;
hyperlinkNavigation = false; hyperlinkNavigation = false;
selectedNodes = [];
constructor(private notificationService: NotificationService, constructor(private notificationService: NotificationService,
private uploadService: UploadService, private uploadService: UploadService,
private contentService: ContentService, private contentService: ContentService,
@ -277,6 +281,33 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
}); });
} }
this.uploadService.fileUploadComplete
.pipe(
debounceTime(300),
scan((files, currentFile) => [...files, currentFile], []),
takeUntil(this.onDestroy$)
)
.subscribe((value: any[]) => {
let selectedNodes: NodeEntry[] = [];
if (this.preselectNodes) {
if (value && value.length > 0 ) {
if (this.selectionMode === 'single') {
selectedNodes = [...[value[value.length - 1]].map((uploadedFile) => uploadedFile.data)];
} else {
selectedNodes = [...value.map((uploadedFile) => uploadedFile.data)];
}
this.selectedNodes = [...selectedNodes];
}
}
this.onFileUploadEvent(value[0]);
});
this.uploadService.fileUploadDeleted
.pipe(takeUntil(this.onDestroy$))
.subscribe(value => this.onFileUploadEvent(value));
this.contentService.folderCreated this.contentService.folderCreated
.pipe(takeUntil(this.onDestroy$)) .pipe(takeUntil(this.onDestroy$))
.subscribe(value => this.onFolderCreated(value)); .subscribe(value => this.onFolderCreated(value));
@ -302,6 +333,12 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
}); });
} }
onFileUploadEvent(event: FileUploadEvent) {
if (event && event.file.options.parentId === this.documentList.currentFolderId) {
this.documentList.reload();
}
}
ngOnDestroy() { ngOnDestroy() {
this.onDestroy$.next(true); this.onDestroy$.next(true);
this.onDestroy$.complete(); this.onDestroy$.complete();
@ -669,4 +706,30 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
this.router.navigate([], { relativeTo: this.route, queryParams: objectFromMap }); this.router.navigate([], { relativeTo: this.route, queryParams: objectFromMap });
} }
setPreselectNodes(nodes: string) {
this.selectedNodes = this.getArrayFromString(nodes);
this.documentList.reload();
}
isStringArray(str: string): boolean {
try {
const result = JSON.parse(str);
return Array.isArray(result);
} catch (e) {
return false;
}
}
private getArrayFromString<T = any>(value: string): T[] {
if (this.isStringArray(value)) {
return JSON.parse(value);
} else {
return [];
}
}
onMultipleFilesUpload() {
this.selectedNodes = [];
}
} }

View File

@ -73,6 +73,7 @@ Displays the documents from a repository.
| navigationMode | `string` | | [User](../../../lib/core/pipes/user-initial.pipe.ts) interaction for folder navigation or file preview. Valid values are "click" and "dblclick". Default value: "dblclick" | | navigationMode | `string` | | [User](../../../lib/core/pipes/user-initial.pipe.ts) interaction for folder navigation or file preview. Valid values are "click" and "dblclick". Default value: "dblclick" |
| node | [`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md) | null | The Document list will show all the nodes contained in the [NodePaging](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md) entity | | node | [`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md) | null | The Document list will show all the nodes contained in the [NodePaging](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md) entity |
| permissionsStyle | [`PermissionStyleModel`](../../../lib/content-services/src/lib/document-list/models/permissions-style.model.ts)`[]` | \[] | Define a set of CSS styles to apply depending on the permission of the user on that node. See the [Permission Style model](../../../lib/content-services/src/lib/document-list/models/permissions-style.model.ts) page for further details and examples. | | permissionsStyle | [`PermissionStyleModel`](../../../lib/content-services/src/lib/document-list/models/permissions-style.model.ts)`[]` | \[] | Define a set of CSS styles to apply depending on the permission of the user on that node. See the [Permission Style model](../../../lib/content-services/src/lib/document-list/models/permissions-style.model.ts) page for further details and examples. |
| preselectNodes | [`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md) `[]` | \[] | Array of nodes to preselect . |
| rowStyle | `string` | | The inline style to apply to every row. See the Angular NgStyle docs for more details and usage examples. | | rowStyle | `string` | | The inline style to apply to every row. See the Angular NgStyle docs for more details and usage examples. |
| rowStyleClass | `string` | | The CSS class to apply to every row | | rowStyleClass | `string` | | The CSS class to apply to every row |
| selectionMode | `string` | "single" | Row selection mode. Can be null, `single` or `multiple`. For `multiple` mode, you can use Cmd (macOS) or Ctrl (Win) modifier key to toggle selection for multiple rows. | | selectionMode | `string` | "single" | Row selection mode. Can be null, `single` or `multiple`. For `multiple` mode, you can use Cmd (macOS) or Ctrl (Win) modifier key to toggle selection for multiple rows. |

View File

@ -61,6 +61,7 @@
adf-highlight-selector=".adf-name-location-cell-name" adf-highlight-selector=".adf-name-location-cell-name"
[showHeader]="false" [showHeader]="false"
[node]="nodePaging" [node]="nodePaging"
[preselectNodes]="preselectNodes"
[maxItems]="pageSize" [maxItems]="pageSize"
[rowFilter]="_rowFilter" [rowFilter]="_rowFilter"
[imageResolver]="imageResolver" [imageResolver]="imageResolver"

View File

@ -23,7 +23,9 @@ import {
UserPreferenceValues, UserPreferenceValues,
InfinitePaginationComponent, PaginatedComponent, InfinitePaginationComponent, PaginatedComponent,
NodesApiService, NodesApiService,
SitesService SitesService,
UploadService,
FileUploadCompleteEvent
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { Node, NodePaging, Pagination, SiteEntry, SitePaging, NodeEntry } from '@alfresco/js-api'; import { Node, NodePaging, Pagination, SiteEntry, SitePaging, NodeEntry } from '@alfresco/js-api';
@ -31,7 +33,7 @@ import { DocumentListComponent } from '../document-list/components/document-list
import { RowFilter } from '../document-list/data/row-filter.model'; import { RowFilter } from '../document-list/data/row-filter.model';
import { ImageResolver } from '../document-list/data/image-resolver.model'; import { ImageResolver } from '../document-list/data/image-resolver.model';
import { ContentNodeSelectorService } from './content-node-selector.service'; import { ContentNodeSelectorService } from './content-node-selector.service';
import { debounceTime, takeUntil } from 'rxjs/operators'; import { debounceTime, takeUntil, scan } from 'rxjs/operators';
import { CustomResourcesService } from '../document-list/services/custom-resources.service'; import { CustomResourcesService } from '../document-list/services/custom-resources.service';
import { NodeEntryEvent, ShareDataRow } from '../document-list'; import { NodeEntryEvent, ShareDataRow } from '../document-list';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -222,6 +224,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
searchInput: FormControl = new FormControl(); searchInput: FormControl = new FormControl();
target: PaginatedComponent; target: PaginatedComponent;
preselectNodes: NodeEntry[] = [];
private onDestroy$ = new Subject<boolean>(); private onDestroy$ = new Subject<boolean>();
@ -229,6 +232,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
private customResourcesService: CustomResourcesService, private customResourcesService: CustomResourcesService,
private userPreferencesService: UserPreferencesService, private userPreferencesService: UserPreferencesService,
private nodesApiService: NodesApiService, private nodesApiService: NodesApiService,
private uploadService: UploadService,
private sitesService: SitesService) { private sitesService: SitesService) {
} }
@ -267,6 +271,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
this.breadcrumbTransform = this.breadcrumbTransform ? this.breadcrumbTransform : null; this.breadcrumbTransform = this.breadcrumbTransform ? this.breadcrumbTransform : null;
this.isSelectionValid = this.isSelectionValid ? this.isSelectionValid : defaultValidation; this.isSelectionValid = this.isSelectionValid ? this.isSelectionValid : defaultValidation;
this.onFileUploadEvent();
} }
ngOnDestroy() { ngOnDestroy() {
@ -274,6 +279,19 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
this.onDestroy$.complete(); this.onDestroy$.complete();
} }
private onFileUploadEvent() {
this.uploadService.fileUploadComplete
.pipe(
debounceTime(300),
scan((files, currentFile) => [...files, currentFile], []),
takeUntil(this.onDestroy$)
)
.subscribe((uploadedFiles: FileUploadCompleteEvent[]) => {
this.preselectNodes = this.getPreselectNodesBasedOnSelectionMode(uploadedFiles);
this.documentList.reload();
});
}
private getStartSite() { private getStartSite() {
this.nodesApiService.getNode(this.currentFolderId).subscribe((startNodeEntry) => { this.nodesApiService.getNode(this.currentFolderId).subscribe((startNodeEntry) => {
this.startSiteGuid = this.sitesService.getSiteNameFromNodePath(startNodeEntry); this.startSiteGuid = this.sitesService.getSiteNameFromNodePath(startNodeEntry);
@ -363,7 +381,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
this.searchTerm = ''; this.searchTerm = '';
this.nodePaging = null; this.nodePaging = null;
this.pagination.maxItems = this.pageSize; this.pagination.maxItems = this.pageSize;
this.chosenNode = null; this.resetChosenNode();
this.showingSearchResults = false; this.showingSearchResults = false;
} }
@ -434,6 +452,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
this.showingSearchResults = false; this.showingSearchResults = false;
this.infiniteScroll = false; this.infiniteScroll = false;
this.breadcrumbFolderTitle = null; this.breadcrumbFolderTitle = null;
this.preselectNodes = [];
this.clearSearch(); this.clearSearch();
this.navigationChange.emit($event); this.navigationChange.emit($event);
} }
@ -476,8 +495,6 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
private attemptNodeSelection(entry: Node): void { private attemptNodeSelection(entry: Node): void {
if (entry && this.isSelectionValid(entry)) { if (entry && this.isSelectionValid(entry)) {
this.chosenNode = [entry]; this.chosenNode = [entry];
} else {
this.resetChosenNode();
} }
} }
@ -510,4 +527,26 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
this.breadcrumbFolderTitle = null; this.breadcrumbFolderTitle = null;
} }
} }
hasPreselectNodes(): boolean {
return this.preselectNodes && this.preselectNodes.length > 0;
}
isSingleSelectionMode(): boolean {
return this.selectionMode === 'single';
}
private getPreselectNodesBasedOnSelectionMode(uploadedFiles: FileUploadCompleteEvent[]): NodeEntry[] {
let selectedNodes: NodeEntry[] = [];
if (uploadedFiles && uploadedFiles.length > 0 ) {
if (this.isSingleSelectionMode()) {
selectedNodes = [...[uploadedFiles[uploadedFiles.length - 1]].map((uploadedFile) => uploadedFile.data)];
} else {
selectedNodes = [...uploadedFiles.map((uploadedFile) => uploadedFile.data)];
}
}
return selectedNodes;
}
} }

View File

@ -26,7 +26,8 @@ import {
DataTableComponent, DataTableComponent,
DataTableModule, DataTableModule,
ObjectDataTableAdapter, ObjectDataTableAdapter,
ShowHeaderMode ShowHeaderMode,
ThumbnailService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { Subject, of, throwError } from 'rxjs'; import { Subject, of, throwError } from 'rxjs';
import { import {
@ -34,7 +35,9 @@ import {
fakeNodeAnswerWithNOEntries, fakeNodeAnswerWithNOEntries,
fakeNodeWithNoPermission, fakeNodeWithNoPermission,
fakeGetSitesAnswer, fakeGetSitesAnswer,
fakeGetSiteMembership fakeGetSiteMembership,
mockPreselectedNodes,
mockNodePagingWithPreselectedNodes
} from '../../mock'; } from '../../mock';
import { ContentActionModel } from '../models/content-action.model'; import { ContentActionModel } from '../models/content-action.model';
import { NodeMinimal, NodeMinimalEntry, NodePaging } from '../models/document-library.model'; import { NodeMinimal, NodeMinimalEntry, NodePaging } from '../models/document-library.model';
@ -56,6 +59,7 @@ describe('DocumentList', () => {
let documentListService: DocumentListService; let documentListService: DocumentListService;
let apiService: AlfrescoApiService; let apiService: AlfrescoApiService;
let customResourcesService: CustomResourcesService; let customResourcesService: CustomResourcesService;
let thumbnailService: ThumbnailService;
let fixture: ComponentFixture<DocumentListComponent>; let fixture: ComponentFixture<DocumentListComponent>;
let element: HTMLElement; let element: HTMLElement;
let eventMock: any; let eventMock: any;
@ -86,6 +90,7 @@ describe('DocumentList', () => {
documentListService = TestBed.inject(DocumentListService); documentListService = TestBed.inject(DocumentListService);
apiService = TestBed.inject(AlfrescoApiService); apiService = TestBed.inject(AlfrescoApiService);
customResourcesService = TestBed.inject(CustomResourcesService); customResourcesService = TestBed.inject(CustomResourcesService);
thumbnailService = TestBed.inject(ThumbnailService);
spyFolder = spyOn(documentListService, 'getFolder').and.callFake(() => { spyFolder = spyOn(documentListService, 'getFolder').and.callFake(() => {
return Promise.resolve({ list: {} }); return Promise.resolve({ list: {} });
@ -1515,6 +1520,74 @@ describe('DocumentList', () => {
where: undefined where: undefined
}), undefined); }), undefined);
}); });
describe('Preselect nodes', () => {
beforeEach(() => {
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue(`assets/images/ft_ic_created.svg`);
});
it('should able to emit nodeSelected event with preselectNodes on the reload', async () => {
const nodeSelectedSpy = spyOn(documentList.nodeSelected, 'emit');
fixture.detectChanges();
documentList.node = mockNodePagingWithPreselectedNodes;
documentList.preselectNodes = mockPreselectedNodes;
documentList.reload();
fixture.detectChanges();
await fixture.whenStable();
expect(documentList.preselectNodes.length).toBe(2);
expect(nodeSelectedSpy).toHaveBeenCalled();
});
it('should able to select first node from the preselectNodes when selectionMode set to single', async () => {
documentList.selectionMode = 'single';
fixture.detectChanges();
documentList.node = mockNodePagingWithPreselectedNodes;
documentList.preselectNodes = mockPreselectedNodes;
documentList.reload();
fixture.detectChanges();
await fixture.whenStable();
expect(documentList.preselectNodes.length).toBe(2);
expect(documentList.getPreselectNodesBasedOnSelectionMode().length).toBe(1);
});
it('should able to select all preselectNodes when selectionMode set to multiple', async () => {
documentList.selectionMode = 'multiple';
fixture.detectChanges();
documentList.node = mockNodePagingWithPreselectedNodes;
documentList.preselectNodes = mockPreselectedNodes;
documentList.reload();
fixture.detectChanges();
await fixture.whenStable();
expect(documentList.preselectNodes.length).toBe(2);
expect(documentList.getPreselectNodesBasedOnSelectionMode().length).toBe(2);
});
it('should not emit nodeSelected event when preselectNodes undefined/empty', async () => {
const nodeSelectedSpy = spyOn(documentList.nodeSelected, 'emit');
fixture.detectChanges();
documentList.node = mockNodePagingWithPreselectedNodes;
documentList.preselectNodes = [];
documentList.reload();
fixture.detectChanges();
await fixture.whenStable();
expect(nodeSelectedSpy).not.toHaveBeenCalled();
});
});
}); });
@Component({ @Component({

View File

@ -46,7 +46,7 @@ import {
AlfrescoApiService, AlfrescoApiService,
UserPreferenceValues, UserPreferenceValues,
LockService, LockService,
UploadService DataRow
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { Node, NodeEntry, NodePaging, Pagination } from '@alfresco/js-api'; import { Node, NodeEntry, NodePaging, Pagination } from '@alfresco/js-api';
@ -61,7 +61,7 @@ import { NavigableComponentInterface } from '../../breadcrumb/navigable-componen
import { RowFilter } from '../data/row-filter.model'; import { RowFilter } from '../data/row-filter.model';
import { DocumentListService } from '../services/document-list.service'; import { DocumentListService } from '../services/document-list.service';
import { DocumentLoaderNode } from '../models/document-folder.model'; import { DocumentLoaderNode } from '../models/document-folder.model';
import { debounceTime, takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@Component({ @Component({
selector: 'adf-document-list', selector: 'adf-document-list',
@ -259,6 +259,13 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
@Input() @Input()
currentFolderId: string = null; currentFolderId: string = null;
/** Array of nodes to be pre-selected. All nodes in the
* array are pre-selected in multi selection mode, but only the first node
* is pre-selected in single selection mode.
*/
@Input()
preselectNodes: NodeEntry[] = [];
/** The Document list will show all the nodes contained in the NodePaging entity */ /** The Document list will show all the nodes contained in the NodePaging entity */
@Input() @Input()
node: NodePaging = null; node: NodePaging = null;
@ -327,7 +334,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
private appConfig: AppConfigService, private appConfig: AppConfigService,
private userPreferencesService: UserPreferencesService, private userPreferencesService: UserPreferencesService,
private contentService: ContentService, private contentService: ContentService,
private uploadService: UploadService,
private thumbnailService: ThumbnailService, private thumbnailService: ThumbnailService,
private alfrescoApiService: AlfrescoApiService, private alfrescoApiService: AlfrescoApiService,
private lockService: LockService) { private lockService: LockService) {
@ -337,18 +343,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
.subscribe(pagSize => { .subscribe(pagSize => {
this.maxItems = this._pagination.maxItems = pagSize; this.maxItems = this._pagination.maxItems = pagSize;
}); });
this.uploadService.fileUploadComplete
.pipe(
debounceTime(300),
takeUntil(this.onDestroy$))
.subscribe(() => this.reload());
this.uploadService.fileUploadDeleted
.pipe(
debounceTime(300),
takeUntil(this.onDestroy$))
.subscribe(() => this.reload());
} }
getContextActions(node: NodeEntry) { getContextActions(node: NodeEntry) {
@ -469,7 +463,8 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
if (changes['currentFolderId']?.currentValue !== changes['currentFolderId']?.previousValue) { if (changes['currentFolderId']?.currentValue !== changes['currentFolderId']?.previousValue) {
if (this.data) { if (this.data) {
this.data.loadPage(null, false); this.data.loadPage(null, false, null, this.getPreselectNodesBasedOnSelectionMode());
this.onPreselectNodes();
this.resetNewFolderPagination(); this.resetNewFolderPagination();
} }
@ -483,7 +478,8 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
if (this.data) { if (this.data) {
if (changes.node && changes.node.currentValue) { if (changes.node && changes.node.currentValue) {
const merge = this._pagination ? this._pagination.merge : false; const merge = this._pagination ? this._pagination.merge : false;
this.data.loadPage(changes.node.currentValue, merge); this.data.loadPage(changes.node.currentValue, merge, null, this.getPreselectNodesBasedOnSelectionMode());
this.onPreselectNodes();
this.onDataReady(changes.node.currentValue); this.onDataReady(changes.node.currentValue);
} else if (changes.imageResolver) { } else if (changes.imageResolver) {
this.data.setImageResolver(changes.imageResolver.currentValue); this.data.setImageResolver(changes.imageResolver.currentValue);
@ -495,7 +491,8 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.ngZone.run(() => { this.ngZone.run(() => {
this.resetSelection(); this.resetSelection();
if (this.node) { if (this.node) {
this.data.loadPage(this.node, this._pagination.merge); this.data.loadPage(this.node, this._pagination.merge, null, this.getPreselectNodesBasedOnSelectionMode());
this.onPreselectNodes();
this.onDataReady(this.node); this.onDataReady(this.node);
} else { } else {
this.loadFolder(); this.loadFolder();
@ -685,7 +682,8 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
onPageLoaded(nodePaging: NodePaging) { onPageLoaded(nodePaging: NodePaging) {
if (nodePaging) { if (nodePaging) {
this.data.loadPage(nodePaging, this._pagination.merge, this.allowDropFiles); this.data.loadPage(nodePaging, this._pagination.merge, this.allowDropFiles, this.getPreselectNodesBasedOnSelectionMode());
this.onPreselectNodes();
this.setLoadingState(false); this.setLoadingState(false);
this.onDataReady(nodePaging); this.onDataReady(nodePaging);
} }
@ -787,7 +785,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.selection = event.selection.map((entry) => entry.node); this.selection = event.selection.map((entry) => entry.node);
const domEvent = new CustomEvent('node-select', { const domEvent = new CustomEvent('node-select', {
detail: { detail: {
node: event.row.node, node: event.row ? event.row.node : null,
selection: this.selection selection: this.selection
}, },
bubbles: true bubbles: true
@ -898,4 +896,40 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.setLoadingState(false); this.setLoadingState(false);
this.error.emit(err); this.error.emit(err);
} }
getPreselectNodesBasedOnSelectionMode(): NodeEntry[] {
let selectedNodes: NodeEntry[] = [];
if (this.hasPreselectNodes()) {
if (this.isSingleSelectionMode()) {
selectedNodes = [this.preselectNodes[0]];
} else {
selectedNodes = this.preselectNodes;
}
}
return selectedNodes;
}
private onPreselectNodes() {
if (this.hasPreselectNodes()) {
let selectedNodes: DataRow[] = [];
if (this.isSingleSelectionMode()) {
selectedNodes = [this.data.getPreselectRows()[0]];
} else {
selectedNodes = this.data.getPreselectRows();
}
this.onNodeSelect({ row: undefined, selection: <ShareDataRow[]> selectedNodes });
}
}
isSingleSelectionMode(): boolean {
return this.selectionMode === 'single';
}
hasPreselectNodes(): boolean {
return this.preselectNodes && this.preselectNodes.length > 0;
}
} }

View File

@ -16,7 +16,7 @@
*/ */
import { DataColumn, DataRow, DataSorting, ContentService, ThumbnailService, setupTestBed } from '@alfresco/adf-core'; import { DataColumn, DataRow, DataSorting, ContentService, ThumbnailService, setupTestBed } from '@alfresco/adf-core';
import { FileNode, FolderNode, SmartFolderNode, RuleFolderNode, LinkFolderNode } from './../../mock'; import { FileNode, FolderNode, SmartFolderNode, RuleFolderNode, LinkFolderNode, mockPreselectedNodes, mockNodePagingWithPreselectedNodes, mockNode2, fakeNodePaging } from './../../mock';
import { ShareDataRow } from './share-data-row.model'; import { ShareDataRow } from './share-data-row.model';
import { ShareDataTableAdapter } from './share-datatable-adapter'; import { ShareDataTableAdapter } from './share-datatable-adapter';
import { ContentTestingModule } from '../../testing/content.testing.module'; import { ContentTestingModule } from '../../testing/content.testing.module';
@ -481,4 +481,31 @@ describe('ShareDataTableAdapter', () => {
expect(row.isDropTarget).toBeFalsy(); expect(row.isDropTarget).toBeFalsy();
}); });
}); });
describe('Preselect rows', () => {
it('should set isSelected to be true for each preselectRow if the preselectNodes are defined', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, mockPreselectedNodes);
expect(adapter.getPreselectRows().length).toBe(1);
expect(adapter.getPreselectRows()[0].isSelected).toBe(true);
});
it('should set preselectRows empty if preselectedNodes are undefined/empty', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, []);
expect(adapter.getPreselectRows().length).toBe(0);
});
it('should set preselectRows empty if preselectedNodes are not found in the list', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
mockNode2.id = 'mock-file-id';
const preselectedNode = [ { entry: mockNode2 }];
adapter.loadPage(fakeNodePaging, null, null, preselectedNode);
expect(adapter.getPreselectRows().length).toBe(0);
});
});
}); });

View File

@ -45,6 +45,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
permissionsStyle: PermissionStyleModel[]; permissionsStyle: PermissionStyleModel[];
selectedRow: DataRow; selectedRow: DataRow;
allowDropFiles: boolean; allowDropFiles: boolean;
preselectRows: DataRow[] = [];
set sortingMode(value: string) { set sortingMode(value: string) {
let newValue = (value || 'client').toLowerCase(); let newValue = (value || 'client').toLowerCase();
@ -81,6 +82,10 @@ export class ShareDataTableAdapter implements DataTableAdapter {
this.sort(); this.sort();
} }
getPreselectRows(): Array<DataRow> {
return this.preselectRows;
}
getColumns(): Array<DataColumn> { getColumns(): Array<DataColumn> {
return this.columns; return this.columns;
} }
@ -245,7 +250,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
} }
} }
public loadPage(nodePaging: NodePaging, merge: boolean = false, allowDropFiles?: boolean) { public loadPage(nodePaging: NodePaging, merge: boolean = false, allowDropFiles?: boolean, preselectNodes: NodeEntry[] = []) {
let shareDataRows: ShareDataRow[] = []; let shareDataRows: ShareDataRow[] = [];
if (allowDropFiles !== undefined) { if (allowDropFiles !== undefined) {
this.allowDropFiles = allowDropFiles; this.allowDropFiles = allowDropFiles;
@ -292,6 +297,23 @@ export class ShareDataTableAdapter implements DataTableAdapter {
} else { } else {
this.rows = shareDataRows; this.rows = shareDataRows;
} }
this.selectRowsBasedOnGivenNodes(preselectNodes);
}
selectRowsBasedOnGivenNodes(preselectNodes: NodeEntry[]) {
if (preselectNodes && preselectNodes.length > 0) {
this.rows = this.rows.map((row) => {
preselectNodes.map((preselectedNode) => {
if (row.obj.entry.id === preselectedNode.entry.id) {
row.isSelected = true;
}
});
return row;
});
}
this.preselectRows = [...this.rows.filter((res) => res.isSelected)];
} }
} }

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Node, NodePaging } from '@alfresco/js-api'; import { Node, NodePaging, NodeEntry } from '@alfresco/js-api';
export const fakeNodeWithCreatePermission = new Node({ export const fakeNodeWithCreatePermission = new Node({
isFile: false, isFile: false,
@ -215,3 +215,117 @@ export const fakeNodePaging: NodePaging = {
}] }]
} }
}; };
export const mockNode1 = new Node({
'isFile': true,
'createdByUser': { 'id': 'admin', 'displayName': 'Administrator' },
'modifiedAt': '2017-05-24T15:08:55.640Z',
'nodeType': 'cm:content',
'content': {
'mimeType': 'application/rtf',
'mimeTypeName': 'Rich Text Format',
'sizeInBytes': 14530,
'encoding': 'UTF-8'
},
'parentId': 'd124de26-6ba0-4f40-8d98-4907da2d337a',
'createdAt': '2017-05-24T15:08:55.640Z',
'path': {
'name': '/Company Home/Guest Home',
'isComplete': true,
'elements': [{
'id': '94acfc73-7014-4475-9bd9-93a2162f0f8c',
'name': 'Company Home'
}, { 'id': 'd124de26-6ba0-4f40-8d98-4907da2d337a', 'name': 'Guest Home' }]
},
'isFolder': false,
'modifiedByUser': { 'id': 'admin', 'displayName': 'Administrator' },
'name': 'b_txt_file.rtf',
'id': '70e1cc6a-6918-468a-b84a-1048093b06fd',
'properties': { 'cm:versionLabel': '1.0', 'cm:versionType': 'MAJOR' },
'allowableOperations': ['delete', 'update']
});
export const mockNode2 = new Node({
'isFile': true,
'createdByUser': { 'id': 'admin', 'displayName': 'Administrator' },
'modifiedAt': '2017-05-24T15:08:55.640Z',
'nodeType': 'cm:content',
'content': {
'mimeType': 'application/rtf',
'mimeTypeName': 'Rich Text Format',
'sizeInBytes': 14530,
'encoding': 'UTF-8'
},
'parentId': 'd124de26-6ba0-4f40-8d98-4907da2d337a',
'createdAt': '2017-05-24T15:08:55.640Z',
'path': {
'name': '/Company Home/Guest Home',
'isComplete': true,
'elements': [{
'id': '94acfc73-7014-4475-9bd9-93a2162f0f8c',
'name': 'Company Home'
}, { 'id': 'd124de26-6ba0-4f40-8d98-4907da2d337a', 'name': 'Guest Home' }]
},
'isFolder': false,
'modifiedByUser': { 'id': 'admin', 'displayName': 'Administrator' },
'name': 'b_txt_file.rtf',
'id': '67b80f77-dbca-4f58-be6c-71b9dd61e554',
'properties': { 'cm:versionLabel': '1.0', 'cm:versionType': 'MAJOR' },
'allowableOperations': ['delete', 'update']
});
export const mockNode3 = new Node({
'isFile': true,
'createdByUser': { 'id': 'admin', 'displayName': 'Administrator' },
'modifiedAt': '2017-05-24T15:08:55.640Z',
'nodeType': 'cm:content',
'content': {
'mimeType': 'application/rtf',
'mimeTypeName': 'Rich Text Format',
'sizeInBytes': 14530,
'encoding': 'UTF-8'
},
'parentId': 'd124de26-6ba0-4f40-8d98-4907da2d337a',
'createdAt': '2017-05-24T15:08:55.640Z',
'path': {
'name': '/Company Home/Guest Home',
'isComplete': true,
'elements': [{
'id': '94acfc73-7014-4475-9bd9-93a2162f0f8c',
'name': 'Company Home'
}, { 'id': 'd124de26-6ba0-4f40-8d98-4907da2d337a', 'name': 'Guest Home' }]
},
'isFolder': false,
'modifiedByUser': { 'id': 'admin', 'displayName': 'Administrator' },
'name': 'c_txt_file.rtf',
'id': '67b80f77-dbca-4f58-be6c-71b9dd61e555',
'properties': { 'cm:versionLabel': '1.0', 'cm:versionType': 'MAJOR' },
'allowableOperations': ['delete', 'update']
});
export const mockPreselectedNodes: NodeEntry[] = [
{
entry: mockNode1
},
{
entry: mockNode1
}
];
export const mockNodePagingWithPreselectedNodes: NodePaging = {
list: {
pagination: {
count: 5,
hasMoreItems: false,
totalItems: 5,
skipCount: 0,
maxItems: 100
}, entries: [{
entry: mockNode1
}, {
entry: mockNode2
}, {
entry: mockNode3
}]
}
};