[AAE-4427] Embed upload progress dialog inside the upload from your d… (#6575)

* [AAE-4427] Embed upload progress dialog inside the upload from your device tab in attach file widget

* Fix failing unit tesT

* Add unit tests

* Removed not needed condition

* Make upload from your device tab same size as Repository tab

* Revert renaming causing breaking change

* simplify if conditions

* Update js-api version

* Use typescript ?. operator

* Add unit test for non existing datatable entries
This commit is contained in:
arditdomi
2021-02-04 09:39:54 +01:00
committed by GitHub
parent a13367876b
commit d362153e37
17 changed files with 211 additions and 61 deletions

View File

@@ -22,6 +22,7 @@ Shows a dialog listing all the files uploaded with the Upload Button or Drag Are
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| position | `string` | "right" | Dialog position. Can be 'left' or 'right'. | | position | `string` | "right" | Dialog position. Can be 'left' or 'right'. |
| alwaysVisible | `boolean` | false | Dialog visibility. When true it makes the dialog visible even when there are no uploads. |
### Events ### Events

View File

@@ -70,7 +70,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" [preselectNodes]="preselectedNodes"
[maxItems]="pageSize" [maxItems]="pageSize"
[rowFilter]="_rowFilter" [rowFilter]="_rowFilter"
[imageResolver]="imageResolver" [imageResolver]="imageResolver"

View File

@@ -16,10 +16,10 @@
*/ */
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { tick, ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { NodeEntry, Node, SiteEntry, SitePaging, NodePaging, ResultSetPaging, RequestScope } from '@alfresco/js-api'; import { Node, NodeEntry, NodePaging, RequestScope, ResultSetPaging, SiteEntry, SitePaging } from '@alfresco/js-api';
import { SitesService, setupTestBed, NodesApiService } from '@alfresco/adf-core'; import { FileModel, FileUploadStatus, NodesApiService, setupTestBed, SitesService, UploadService } from '@alfresco/adf-core';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { DropdownBreadcrumbComponent } from '../breadcrumb'; import { DropdownBreadcrumbComponent } from '../breadcrumb';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component'; import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
@@ -65,6 +65,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
const nodeEntryEvent = new NodeEntryEvent(fakeNodeEntry); const nodeEntryEvent = new NodeEntryEvent(fakeNodeEntry);
let searchQueryBuilderService: SearchQueryBuilderService; let searchQueryBuilderService: SearchQueryBuilderService;
let contentNodeSelectorPanelService: ContentNodeSelectorPanelService; let contentNodeSelectorPanelService: ContentNodeSelectorPanelService;
let uploadService: UploadService;
function typeToSearchBox(searchTerm = 'string-to-search') { function typeToSearchBox(searchTerm = 'string-to-search') {
const searchInput = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-input"]')); const searchInput = fixture.debugElement.query(By.css('[data-automation-id="content-node-selector-search-input"]'));
@@ -95,6 +96,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
nodeService = TestBed.inject(NodesApiService); nodeService = TestBed.inject(NodesApiService);
sitesService = TestBed.inject(SitesService); sitesService = TestBed.inject(SitesService);
contentNodeSelectorPanelService = TestBed.inject(ContentNodeSelectorPanelService); contentNodeSelectorPanelService = TestBed.inject(ContentNodeSelectorPanelService);
uploadService = TestBed.inject(UploadService);
searchQueryBuilderService = component.queryBuilderService; searchQueryBuilderService = component.queryBuilderService;
component.queryBuilderService.resetToDefaults(); component.queryBuilderService.resetToDefaults();
@@ -1171,6 +1173,31 @@ describe('ContentNodeSelectorPanelComponent', () => {
}); });
}); });
describe('interaction with upload functionality', () => {
let documentListService: DocumentListService;
beforeEach(() => {
documentListService = TestBed.inject(DocumentListService);
spyOn(documentListService, 'getFolderNode');
spyOn(documentListService, 'getFolder');
});
it('should remove the node from the chosenNodes when an upload gets deleted', () => {
fixture.detectChanges();
const selectSpy = spyOn(component.select, 'next');
const fakeFileModel = new FileModel(<File> { name: 'fake-name', size: 10000000 });
const fakeNodes = [<Node> { id: 'fakeNodeId' }, <Node> { id: 'fakeNodeId2' }];
fakeFileModel.data = { entry: fakeNodes[0] };
fakeFileModel.status = FileUploadStatus.Deleted;
component._chosenNode = [...fakeNodes];
uploadService.cancelUpload(fakeFileModel);
expect(selectSpy).toHaveBeenCalledWith([fakeNodes[1]]);
expect(component._chosenNode).toEqual([fakeNodes[1]]);
});
});
}); });
describe('Search panel', () => { describe('Search panel', () => {

View File

@@ -34,7 +34,9 @@ import {
NodesApiService, NodesApiService,
SitesService, SitesService,
UploadService, UploadService,
FileUploadCompleteEvent FileUploadCompleteEvent,
FileUploadDeleteEvent,
FileModel
} 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, QueryBody, RequestScope } from '@alfresco/js-api'; import { Node, NodePaging, Pagination, SiteEntry, SitePaging, NodeEntry, QueryBody, RequestScope } from '@alfresco/js-api';
@@ -247,7 +249,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
searchInput: FormControl = new FormControl(); searchInput: FormControl = new FormControl();
target: PaginatedComponent; target: PaginatedComponent;
preselectNodes: NodeEntry[] = []; preselectedNodes: NodeEntry[] = [];
searchPanelExpanded: boolean = false; searchPanelExpanded: boolean = false;
@@ -320,6 +322,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(); this.onFileUploadEvent();
this.onFileUploadDeletedEvent();
this.resetPagination(); this.resetPagination();
this.setSearchScopeToNodes(); this.setSearchScopeToNodes();
@@ -351,11 +354,30 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
takeUntil(this.onDestroy$) takeUntil(this.onDestroy$)
) )
.subscribe((uploadedFiles: FileUploadCompleteEvent[]) => { .subscribe((uploadedFiles: FileUploadCompleteEvent[]) => {
this.preselectNodes = this.getPreselectNodesBasedOnSelectionMode(uploadedFiles); this.preselectedNodes = this.getPreselectNodesBasedOnSelectionMode(uploadedFiles);
this.documentList.reload(); this.documentList.reload();
}); });
} }
private onFileUploadDeletedEvent() {
this.uploadService.fileUploadDeleted
.pipe(takeUntil(this.onDestroy$))
.subscribe((deletedFileEvent: FileUploadDeleteEvent) => {
this.removeFromChosenNodes(deletedFileEvent.file);
this.documentList.reload();
});
}
private removeFromChosenNodes(file: FileModel) {
if (this.chosenNode) {
const fileIndex = this.chosenNode.findIndex((chosenNode: Node) => chosenNode.id === file.data.entry.id);
if (fileIndex !== -1) {
this._chosenNode.splice(fileIndex, 1);
this.select.next(this._chosenNode);
}
}
}
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);
@@ -511,7 +533,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.preselectedNodes = [];
this.clearSearch(); this.clearSearch();
this.navigationChange.emit($event); this.navigationChange.emit($event);
} }
@@ -573,7 +595,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
*/ */
onCurrentSelection(nodesEntries: NodeEntry[]): void { onCurrentSelection(nodesEntries: NodeEntry[]): void {
const validNodesEntity = nodesEntries.filter((node) => this.isSelectionValid(node.entry)); const validNodesEntity = nodesEntries.filter((node) => this.isSelectionValid(node.entry));
this.chosenNode = validNodesEntity.map((node) => node.entry ); this.chosenNode = validNodesEntity.map((node) => node.entry);
} }
setTitleIfCustomSite(site: SiteEntry) { setTitleIfCustomSite(site: SiteEntry) {
@@ -589,7 +611,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
} }
hasPreselectNodes(): boolean { hasPreselectNodes(): boolean {
return this.preselectNodes && this.preselectNodes.length > 0; return this.preselectedNodes?.length > 0;
} }
isSingleSelectionMode(): boolean { isSingleSelectionMode(): boolean {

View File

@@ -8,7 +8,7 @@
mat-align-tabs="start" mat-align-tabs="start"
(selectedIndexChange)="onTabSelectionChange($event)" (selectedIndexChange)="onTabSelectionChange($event)"
[class.adf-content-node-selector-headless-tabs]="!canPerformLocalUpload()"> [class.adf-content-node-selector-headless-tabs]="!canPerformLocalUpload()">
<mat-tab label="{{ 'NODE_SELECTOR.FILE_SERVER' | translate }}"> <mat-tab label="{{ 'NODE_SELECTOR.REPOSITORY' | translate }}">
<adf-content-node-selector-panel <adf-content-node-selector-panel
[currentFolderId]="data?.currentFolderId" [currentFolderId]="data?.currentFolderId"
[restrictRootToCurrentFolderId]="data?.restrictRootToCurrentFolderId" [restrictRootToCurrentFolderId]="data?.restrictRootToCurrentFolderId"
@@ -35,6 +35,11 @@
<mat-tab *ngIf="canPerformLocalUpload()" <mat-tab *ngIf="canPerformLocalUpload()"
label="{{ 'NODE_SELECTOR.UPLOAD_FROM_DEVICE' | translate }}" label="{{ 'NODE_SELECTOR.UPLOAD_FROM_DEVICE' | translate }}"
[disabled]="isNotAllowedToUpload()"> [disabled]="isNotAllowedToUpload()">
<adf-upload-drag-area [rootFolderId]="currentDirectoryId">
<div class="adf-upload-dialog-container">
<adf-file-uploading-dialog [alwaysVisible]="true"></adf-file-uploading-dialog>
</div>
</adf-upload-drag-area>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>

View File

@@ -9,6 +9,22 @@
display: none; display: none;
} }
} }
.adf-upload-dialog {
&__content {
max-height: 64%;
}
height: 100%;
width: 100%;
position: unset;
bottom: unset;
}
.adf-upload-dialog-container {
height: 456px;
}
} }
.adf-content-node-selector-dialog { .adf-content-node-selector-dialog {

View File

@@ -1532,7 +1532,7 @@ describe('DocumentList', () => {
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue(`assets/images/ft_ic_created.svg`); spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue(`assets/images/ft_ic_created.svg`);
}); });
it('should able to emit nodeSelected event with preselectNodes on the reload', async () => { it('should able to emit nodeSelected event with preselectedNodes on the reload', async () => {
const nodeSelectedSpy = spyOn(documentList.nodeSelected, 'emit'); const nodeSelectedSpy = spyOn(documentList.nodeSelected, 'emit');
fixture.detectChanges(); fixture.detectChanges();
@@ -1548,7 +1548,7 @@ describe('DocumentList', () => {
expect(nodeSelectedSpy).toHaveBeenCalled(); expect(nodeSelectedSpy).toHaveBeenCalled();
}); });
it('should be able to select first node from the preselectNodes when selectionMode set to single', async () => { it('should be able to select first node from the preselectedNodes when selectionMode set to single', async () => {
documentList.selectionMode = 'single'; documentList.selectionMode = 'single';
fixture.detectChanges(); fixture.detectChanges();
@@ -1560,10 +1560,10 @@ describe('DocumentList', () => {
await fixture.whenStable(); await fixture.whenStable();
expect(documentList.preselectNodes.length).toBe(2); expect(documentList.preselectNodes.length).toBe(2);
expect(documentList.getPreselectNodesBasedOnSelectionMode().length).toBe(1); expect(documentList.getPreselectedNodesBasedOnSelectionMode().length).toBe(1);
}); });
it('should be able to select all preselectNodes when selectionMode set to multiple', async () => { it('should be able to select all preselectedNodes when selectionMode set to multiple', async () => {
documentList.selectionMode = 'multiple'; documentList.selectionMode = 'multiple';
fixture.detectChanges(); fixture.detectChanges();
@@ -1575,13 +1575,14 @@ describe('DocumentList', () => {
await fixture.whenStable(); await fixture.whenStable();
expect(documentList.preselectNodes.length).toBe(2); expect(documentList.preselectNodes.length).toBe(2);
expect(documentList.getPreselectNodesBasedOnSelectionMode().length).toBe(2); expect(documentList.getPreselectedNodesBasedOnSelectionMode().length).toBe(2);
}); });
it('should call the datatable select row method for each preselected node', async () => { it('should call the datatable select row method for each preselected node', async () => {
const datatableSelectRowSpy = spyOn(documentList.dataTable, 'selectRow'); const datatableSelectRowSpy = spyOn(documentList.dataTable, 'selectRow');
const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)]; const fakeDatatableRows = [new ShareDataRow(mockPreselectedNodes[0], contentService, null), new ShareDataRow(mockPreselectedNodes[1], contentService, null)];
spyOn(documentList.data, 'getPreselectRows').and.returnValue(fakeDatatableRows); spyOn(documentList.data, 'hasPreselectedRows').and.returnValue(true);
spyOn(documentList.data, 'getPreselectedRows').and.returnValue(fakeDatatableRows);
documentList.selectionMode = 'multiple'; documentList.selectionMode = 'multiple';
documentList.preselectNodes = mockPreselectedNodes; documentList.preselectNodes = mockPreselectedNodes;
@@ -1593,7 +1594,7 @@ describe('DocumentList', () => {
expect(datatableSelectRowSpy.calls.count()).toEqual(fakeDatatableRows.length); expect(datatableSelectRowSpy.calls.count()).toEqual(fakeDatatableRows.length);
}); });
it('should not emit nodeSelected event when preselectNodes is undefined/empty', async () => { it('should not emit nodeSelected event when preselectedNodes is undefined/empty', async () => {
const nodeSelectedSpy = spyOn(documentList.nodeSelected, 'emit'); const nodeSelectedSpy = spyOn(documentList.nodeSelected, 'emit');
fixture.detectChanges(); fixture.detectChanges();

View File

@@ -487,7 +487,7 @@ 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, null, this.getPreselectNodesBasedOnSelectionMode()); this.data.loadPage(changes.node.currentValue, merge, null, this.getPreselectedNodesBasedOnSelectionMode());
this.onPreselectNodes(); this.onPreselectNodes();
this.onDataReady(changes.node.currentValue); this.onDataReady(changes.node.currentValue);
} else if (changes.imageResolver) { } else if (changes.imageResolver) {
@@ -501,7 +501,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.resetSelection(); this.resetSelection();
if (this.node) { if (this.node) {
if (this.data) { if (this.data) {
this.data.loadPage(this.node, this._pagination.merge, null, this.getPreselectNodesBasedOnSelectionMode()); this.data.loadPage(this.node, this._pagination.merge, null, this.getPreselectedNodesBasedOnSelectionMode());
} }
this.onPreselectNodes(); this.onPreselectNodes();
this.syncPagination(); this.syncPagination();
@@ -694,7 +694,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
onPageLoaded(nodePaging: NodePaging) { onPageLoaded(nodePaging: NodePaging) {
if (nodePaging) { if (nodePaging) {
if (this.data) { if (this.data) {
this.data.loadPage(nodePaging, this._pagination.merge, this.allowDropFiles, this.getPreselectNodesBasedOnSelectionMode()); this.data.loadPage(nodePaging, this._pagination.merge, this.allowDropFiles, this.getPreselectedNodesBasedOnSelectionMode());
} }
this.onPreselectNodes(); this.onPreselectNodes();
this.setLoadingState(false); this.setLoadingState(false);
@@ -800,7 +800,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
} }
onNodeSelect(event: { row: ShareDataRow, selection: Array<ShareDataRow> }) { onNodeSelect(event: { row: ShareDataRow, selection: Array<ShareDataRow> }) {
this.selection = event.selection.map((entry) => entry.node); this.selection = event.selection.filter(entry => entry.node).map((entry) => entry.node);
const domEvent = new CustomEvent('node-select', { const domEvent = new CustomEvent('node-select', {
detail: { detail: {
node: event.row ? event.row.node : null, node: event.row ? event.row.node : null,
@@ -919,13 +919,13 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.error.emit(err); this.error.emit(err);
} }
getPreselectNodesBasedOnSelectionMode(): NodeEntry[] { getPreselectedNodesBasedOnSelectionMode(): NodeEntry[] {
return this.hasPreselectNodes() ? (this.isSingleSelectionMode() ? [this.preselectNodes[0]] : this.preselectNodes) : []; return this.hasPreselectedNodes() ? (this.isSingleSelectionMode() ? [this.preselectNodes[0]] : this.preselectNodes) : [];
} }
onPreselectNodes() { onPreselectNodes() {
if (this.hasPreselectNodes()) { if (this.data.hasPreselectedRows()) {
const preselectedNodes = [...this.isSingleSelectionMode() ? [this.data.getPreselectRows()[0]] : this.data.getPreselectRows()]; const preselectedNodes = [...this.isSingleSelectionMode() ? [this.data.getPreselectedRows()[0]] : this.data.getPreselectedRows()];
const selectedNodes = [...this.selection, ...preselectedNodes]; const selectedNodes = [...this.selection, ...preselectedNodes];
for (const node of preselectedNodes) { for (const node of preselectedNodes) {
@@ -939,7 +939,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
return this.selectionMode === 'single'; return this.selectionMode === 'single';
} }
hasPreselectNodes(): boolean { hasPreselectedNodes(): boolean {
return this.preselectNodes && this.preselectNodes.length > 0; return 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, mockPreselectedNodes, mockNodePagingWithPreselectedNodes, mockNode2, fakeNodePaging } from './../../mock'; import { FileNode, FolderNode, SmartFolderNode, RuleFolderNode, LinkFolderNode, mockPreselectedNodes, mockNodePagingWithPreselectedNodes, mockNode2, fakeNodePaging, mockNode1 } 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';
@@ -484,28 +484,38 @@ describe('ShareDataTableAdapter', () => {
describe('Preselect rows', () => { describe('Preselect rows', () => {
it('should set isSelected to be true for each preselectRow if the preselectNodes are defined', () => { it('should set isSelected to be true for each preselectRow if the preselectedNodes are defined', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []); const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, mockPreselectedNodes); adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, mockPreselectedNodes);
expect(adapter.getPreselectRows().length).toBe(1); expect(adapter.getPreselectedRows().length).toBe(1);
expect(adapter.getPreselectRows()[0].isSelected).toBe(true); expect(adapter.getPreselectedRows()[0].isSelected).toBe(true);
}); });
it('should set preselectRows empty if preselectedNodes are undefined/empty', () => { it('should set preselectedRows empty if preselectedNodes are undefined/empty', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []); const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, []); adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, []);
expect(adapter.getPreselectRows().length).toBe(0); expect(adapter.getPreselectedRows().length).toBe(0);
}); });
it('should set preselectRows empty if preselectedNodes are not found in the list', () => { it('should set preselectedRows empty if preselectedNodes are not found in the list', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []); const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
mockNode2.id = 'mock-file-id'; mockNode2.id = 'mock-file-id';
const preselectedNode = [ { entry: mockNode2 }]; const preselectedNode = [ { entry: mockNode2 }];
adapter.loadPage(fakeNodePaging, null, null, preselectedNode); adapter.loadPage(fakeNodePaging, null, null, preselectedNode);
expect(adapter.getPreselectRows().length).toBe(0); expect(adapter.getPreselectedRows().length).toBe(0);
});
it('should preselected rows contain only the valid rows that exist in the datatable', () => {
const adapter = new ShareDataTableAdapter(thumbnailService, contentService, []);
const nonExistingEntry = {...mockNode1};
nonExistingEntry.id = 'non-existing-entry-id';
const preselectedNodes = [{ entry: nonExistingEntry }, { entry: mockNode1 }, { entry: mockNode2 }];
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, preselectedNodes);
expect(adapter.getPreselectedRows().length).toBe(2);
}); });
}); });
}); });

View File

@@ -45,7 +45,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
permissionsStyle: PermissionStyleModel[]; permissionsStyle: PermissionStyleModel[];
selectedRow: DataRow; selectedRow: DataRow;
allowDropFiles: boolean; allowDropFiles: boolean;
preselectRows: DataRow[] = []; preselectedRows: DataRow[] = [];
set sortingMode(value: string) { set sortingMode(value: string) {
let newValue = (value || 'client').toLowerCase(); let newValue = (value || 'client').toLowerCase();
@@ -82,8 +82,8 @@ export class ShareDataTableAdapter implements DataTableAdapter {
this.sort(); this.sort();
} }
getPreselectRows(): Array<DataRow> { getPreselectedRows(): Array<DataRow> {
return this.preselectRows; return this.preselectedRows;
} }
getColumns(): Array<DataColumn> { getColumns(): Array<DataColumn> {
@@ -208,7 +208,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
} }
private getNodeAspectNames(node: any): any[] { private getNodeAspectNames(node: any): any[] {
return node.entry && node.entry.aspectNames ? node.entry.aspectNames : node.aspectNames ? node.aspectNames : []; return node.entry?.aspectNames ? node.entry.aspectNames : (node.aspectNames ? node.aspectNames : []);
} }
private sortRows(rows: DataRow[], sorting: DataSorting) { private sortRows(rows: DataRow[], sorting: DataSorting) {
@@ -218,7 +218,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
const options: Intl.CollatorOptions = {}; const options: Intl.CollatorOptions = {};
if (sorting && sorting.key && rows && rows.length > 0) { if (sorting?.key && rows?.length) {
if (sorting.key.includes('sizeInBytes') || sorting.key === 'name') { if (sorting.key.includes('sizeInBytes') || sorting.key === 'name') {
options.numeric = true; options.numeric = true;
@@ -255,9 +255,9 @@ export class ShareDataTableAdapter implements DataTableAdapter {
if (allowDropFiles !== undefined) { if (allowDropFiles !== undefined) {
this.allowDropFiles = allowDropFiles; this.allowDropFiles = allowDropFiles;
} }
if (nodePaging && nodePaging.list) { if (nodePaging?.list) {
const nodeEntries: NodeEntry[] = nodePaging.list.entries; const nodeEntries: NodeEntry[] = nodePaging.list.entries;
if (nodeEntries && nodeEntries.length > 0) { if (nodeEntries?.length) {
shareDataRows = nodeEntries.map((item) => new ShareDataRow(item, this.contentService, this.permissionsStyle, shareDataRows = nodeEntries.map((item) => new ShareDataRow(item, this.contentService, this.permissionsStyle,
this.thumbnailService, this.allowDropFiles)); this.thumbnailService, this.allowDropFiles));
@@ -267,7 +267,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
if (this.sortingMode !== 'server') { if (this.sortingMode !== 'server') {
// Sort by first sortable or just first column // Sort by first sortable or just first column
if (this.columns && this.columns.length > 0) { if (this.columns?.length) {
const sorting = this.getSorting(); const sorting = this.getSorting();
if (sorting) { if (sorting) {
this.sortRows(shareDataRows, sorting); this.sortRows(shareDataRows, sorting);
@@ -302,7 +302,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
} }
selectRowsBasedOnGivenNodes(preselectNodes: NodeEntry[]) { selectRowsBasedOnGivenNodes(preselectNodes: NodeEntry[]) {
if (preselectNodes && preselectNodes.length > 0) { if (preselectNodes?.length) {
this.rows = this.rows.map((row) => { this.rows = this.rows.map((row) => {
preselectNodes.map((preselectedNode) => { preselectNodes.map((preselectedNode) => {
if (row.obj.entry.id === preselectedNode.entry.id) { if (row.obj.entry.id === preselectedNode.entry.id) {
@@ -313,7 +313,11 @@ export class ShareDataTableAdapter implements DataTableAdapter {
}); });
} }
this.preselectRows = [...this.rows.filter((res) => res.isSelected)]; this.preselectedRows = [...this.rows.filter((res) => res.isSelected)];
}
hasPreselectedRows(): boolean {
return this.preselectedRows?.length > 0;
} }
} }

View File

@@ -89,7 +89,7 @@
"SELECT_LOCATION": "Select Location", "SELECT_LOCATION": "Select Location",
"UPLOAD_BUTTON_SEARCH_WARNING_MESSAGE": "You can't upload a file whilst a search is still running", "UPLOAD_BUTTON_SEARCH_WARNING_MESSAGE": "You can't upload a file whilst a search is still running",
"UPLOAD_BUTTON_PERMISSION_WARNING_MESSAGE": "User doesn't have permission to upload content to the folder", "UPLOAD_BUTTON_PERMISSION_WARNING_MESSAGE": "User doesn't have permission to upload content to the folder",
"FILE_SERVER": "File server", "REPOSITORY": "Repository",
"UPLOAD_FROM_DEVICE": "Upload from your device" "UPLOAD_FROM_DEVICE": "Upload from your device"
}, },
"OPERATION": { "OPERATION": {

View File

@@ -1,4 +1,4 @@
<div *ngIf="isDialogActive" <div *ngIf="canShowDialog()"
role="dialog" role="dialog"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG'| translate" [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG'| translate"
tabindex="0" tabindex="0"
@@ -89,14 +89,14 @@
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.CANCEL_ALL' | translate" [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.CANCEL_ALL' | translate"
color="primary" color="primary"
mat-button mat-button
*ngIf="!uploadList.isUploadCompleted() && !uploadList.isUploadCancelled()" *ngIf="canShowCancelAll()"
(click)="toggleConfirmation()" (click)="toggleConfirmation()"
>{{ 'ADF_FILE_UPLOAD.BUTTON.CANCEL_ALL' | translate }}</button> >{{ 'ADF_FILE_UPLOAD.BUTTON.CANCEL_ALL' | translate }}</button>
<button <button
id="adf-upload-dialog-close" id="adf-upload-dialog-close"
[attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG_CLOSE' | translate" [attr.aria-label]="'ADF_FILE_UPLOAD.ARIA-LABEL.DIALOG_CLOSE' | translate"
*ngIf="uploadList.isUploadCompleted() || uploadList.isUploadCancelled()" *ngIf="canCloseDialog()"
mat-button mat-button
color="primary" color="primary"
(click)="close()" (click)="close()"

View File

@@ -60,6 +60,50 @@ describe('FileUploadingDialogComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}); });
describe('Dialog and actions visibility', () => {
it('should canShowDialog return true when alwaysVisible is true even when there are no uploads', () => {
component.isDialogActive = false;
component.alwaysVisible = true;
expect(component.canShowDialog()).toBe(true);
});
it('should not be able to close the dialog when alwaysVisible is set to true', () => {
component.alwaysVisible = true;
spyOn(component, 'hasUploadInProgress').and.returnValue(false);
expect(component.canCloseDialog()).toBe(false);
});
it('should not be able to close the dialog when has uploads are in progress', () => {
component.alwaysVisible = false;
spyOn(component, 'hasUploadInProgress').and.returnValue(true);
expect(component.canCloseDialog()).toBe(false);
});
it('should be able to close the dialog when no uploads are in progress', () => {
component.alwaysVisible = false;
spyOn(component, 'hasUploadInProgress').and.returnValue(false);
expect(component.canCloseDialog()).toBe(true);
});
it('should show cancel all when there are uploads in progress', () => {
component.filesUploadingList = fileList;
spyOn(component, 'hasUploadInProgress').and.returnValue(true);
expect(component.canShowCancelAll()).toBe(true);
});
it('should not show cancel all when there are no uploads in progress', () => {
component.filesUploadingList = fileList;
spyOn(component, 'hasUploadInProgress').and.returnValue(false);
expect(component.canShowCancelAll()).toBe(false);
});
});
describe('upload service subscribers', () => { describe('upload service subscribers', () => {
it('should not render dialog when uploading list is empty', () => { it('should not render dialog when uploading list is empty', () => {
uploadService.addToQueue(); uploadService.addToQueue();
@@ -322,9 +366,9 @@ describe('FileUploadingDialogComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
const confirmDecription = fixture.nativeElement.querySelector('#confirmationDescription'); const confirmDescription = fixture.nativeElement.querySelector('#confirmationDescription');
expect(confirmDecription).not.toBeNull(); expect(confirmDescription).not.toBeNull();
expect(confirmDecription).toBeDefined(); expect(confirmDescription).toBeDefined();
const confirmTitle = fixture.nativeElement.querySelector('#confirmationTitle'); const confirmTitle = fixture.nativeElement.querySelector('#confirmationTitle');
expect(confirmTitle).toBeDefined(); expect(confirmTitle).toBeDefined();

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { FileModel, FileUploadStatus, UploadService, UserPreferencesService } from '@alfresco/adf-core'; import { FileModel, FileUploadStatus, UploadService, UserPreferencesService, FileUploadDeleteEvent, FileUploadCompleteEvent } from '@alfresco/adf-core';
import { ChangeDetectorRef, Component, Input, Output, EventEmitter, OnDestroy, OnInit, ViewChild, HostBinding, ElementRef } from '@angular/core'; import { ChangeDetectorRef, Component, Input, Output, EventEmitter, OnDestroy, OnInit, ViewChild, HostBinding, ElementRef } from '@angular/core';
import { Subscription, merge, Subject } from 'rxjs'; import { Subscription, merge, Subject } from 'rxjs';
import { FileUploadingListComponent } from './file-uploading-list.component'; import { FileUploadingListComponent } from './file-uploading-list.component';
@@ -39,6 +39,10 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
@Input() @Input()
position: string = 'right'; position: string = 'right';
/** Makes the dialog always visible even when there are no uploads. */
@Input()
alwaysVisible: boolean = false;
/** Emitted when a file in the list has an error. */ /** Emitted when a file in the list has an error. */
@Output() @Output()
error: EventEmitter<any> = new EventEmitter(); error: EventEmitter<any> = new EventEmitter();
@@ -107,7 +111,7 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
this.uploadService.fileUploadDeleted this.uploadService.fileUploadDeleted
) )
.pipe(takeUntil(this.onDestroy$)) .pipe(takeUntil(this.onDestroy$))
.subscribe(event => { .subscribe((event: FileUploadCompleteEvent | FileUploadDeleteEvent) => {
this.totalCompleted = event.totalComplete; this.totalCompleted = event.totalComplete;
this.changeDetector.detectChanges(); this.changeDetector.detectChanges();
}); });
@@ -201,4 +205,20 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
this.onDestroy$.next(true); this.onDestroy$.next(true);
this.onDestroy$.complete(); this.onDestroy$.complete();
} }
canShowDialog(): boolean {
return this.isDialogActive || this.alwaysVisible;
}
canShowCancelAll(): boolean {
return this.filesUploadingList?.length && this.hasUploadInProgress();
}
canCloseDialog(): boolean {
return !this.hasUploadInProgress() && !this.alwaysVisible;
}
hasUploadInProgress(): boolean {
return (!this.uploadList?.isUploadCompleted() && !this.uploadList?.isUploadCancelled());
}
} }

View File

@@ -40,7 +40,7 @@ export class ContentNodeSelectorDialogPage {
tabPage: TabPage = new TabPage(); tabPage: TabPage = new TabPage();
uploadFromLocalTabName = 'Upload from your device'; uploadFromLocalTabName = 'Upload from your device';
fileServerTabName = 'File server'; repositoryTabName = 'Repository';
async checkDialogIsDisplayed(): Promise<void> { async checkDialogIsDisplayed(): Promise<void> {
await BrowserVisibility.waitUntilElementIsVisible(this.dialog); await BrowserVisibility.waitUntilElementIsVisible(this.dialog);
@@ -151,7 +151,7 @@ export class ContentNodeSelectorDialogPage {
await BrowserVisibility.waitUntilElementIsPresent(uploadButton); await BrowserVisibility.waitUntilElementIsPresent(uploadButton);
await uploadButton.sendKeys(fileLocation); await uploadButton.sendKeys(fileLocation);
await this.tabPage.clickTabByLabel(this.fileServerTabName); await this.tabPage.clickTabByLabel(this.repositoryTabName);
await this.dataTable.waitForTableBody(); await this.dataTable.waitForTableBody();
await this.dataTable.waitTillContentLoaded(); await this.dataTable.waitTillContentLoaded();

6
package-lock.json generated
View File

@@ -5,9 +5,9 @@
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@alfresco/js-api": { "@alfresco/js-api": {
"version": "4.3.0-f117dba26d7c31cb56a6cf40eb84dff89a174971", "version": "4.3.0-2e84309af123ae96cc85bd9683cb1ab9a7119c33",
"resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-4.3.0-f117dba26d7c31cb56a6cf40eb84dff89a174971.tgz", "resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-4.3.0-2e84309af123ae96cc85bd9683cb1ab9a7119c33.tgz",
"integrity": "sha512-aNDqBYpfRVem1UivNWW0VAO+e8JZIo7ATTsf+MrIWWkM+2JiKKc+ByG8uj5HjGt0KekwlZh/tXbAwbNHvpo+PA==", "integrity": "sha512-D655f7pIzecQ+dN2hN8g/MrI0ToRuqO2EddOSs5g6d1fRwA4I32rpH62R2+5oXsdvgkLCuPJ8xoLamz3QVcajQ==",
"requires": { "requires": {
"event-emitter": "^0.3.5", "event-emitter": "^0.3.5",
"minimatch": "3.0.4", "minimatch": "3.0.4",

View File

@@ -71,7 +71,7 @@
"process services-cloud" "process services-cloud"
], ],
"dependencies": { "dependencies": {
"@alfresco/js-api": "^4.3.0-f117dba26d7c31cb56a6cf40eb84dff89a174971", "@alfresco/js-api": "4.3.0-2e84309af123ae96cc85bd9683cb1ab9a7119c33",
"@angular/animations": "^10.0.4", "@angular/animations": "^10.0.4",
"@angular/cdk": "10.1.3", "@angular/cdk": "10.1.3",
"@angular/common": "^10.0.4", "@angular/common": "^10.0.4",