[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 |
| --- | --- | --- | --- |
| 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

View File

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

View File

@@ -16,10 +16,10 @@
*/
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 { NodeEntry, Node, SiteEntry, SitePaging, NodePaging, ResultSetPaging, RequestScope } from '@alfresco/js-api';
import { SitesService, setupTestBed, NodesApiService } from '@alfresco/adf-core';
import { Node, NodeEntry, NodePaging, RequestScope, ResultSetPaging, SiteEntry, SitePaging } from '@alfresco/js-api';
import { FileModel, FileUploadStatus, NodesApiService, setupTestBed, SitesService, UploadService } from '@alfresco/adf-core';
import { of, throwError } from 'rxjs';
import { DropdownBreadcrumbComponent } from '../breadcrumb';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
@@ -65,6 +65,7 @@ describe('ContentNodeSelectorPanelComponent', () => {
const nodeEntryEvent = new NodeEntryEvent(fakeNodeEntry);
let searchQueryBuilderService: SearchQueryBuilderService;
let contentNodeSelectorPanelService: ContentNodeSelectorPanelService;
let uploadService: UploadService;
function typeToSearchBox(searchTerm = 'string-to-search') {
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);
sitesService = TestBed.inject(SitesService);
contentNodeSelectorPanelService = TestBed.inject(ContentNodeSelectorPanelService);
uploadService = TestBed.inject(UploadService);
searchQueryBuilderService = component.queryBuilderService;
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', () => {

View File

@@ -34,7 +34,9 @@ import {
NodesApiService,
SitesService,
UploadService,
FileUploadCompleteEvent
FileUploadCompleteEvent,
FileUploadDeleteEvent,
FileModel
} from '@alfresco/adf-core';
import { FormControl } from '@angular/forms';
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();
target: PaginatedComponent;
preselectNodes: NodeEntry[] = [];
preselectedNodes: NodeEntry[] = [];
searchPanelExpanded: boolean = false;
@@ -320,6 +322,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
this.breadcrumbTransform = this.breadcrumbTransform ? this.breadcrumbTransform : null;
this.isSelectionValid = this.isSelectionValid ? this.isSelectionValid : defaultValidation;
this.onFileUploadEvent();
this.onFileUploadDeletedEvent();
this.resetPagination();
this.setSearchScopeToNodes();
@@ -351,11 +354,30 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
takeUntil(this.onDestroy$)
)
.subscribe((uploadedFiles: FileUploadCompleteEvent[]) => {
this.preselectNodes = this.getPreselectNodesBasedOnSelectionMode(uploadedFiles);
this.preselectedNodes = this.getPreselectNodesBasedOnSelectionMode(uploadedFiles);
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() {
this.nodesApiService.getNode(this.currentFolderId).subscribe((startNodeEntry) => {
this.startSiteGuid = this.sitesService.getSiteNameFromNodePath(startNodeEntry);
@@ -511,7 +533,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
this.showingSearchResults = false;
this.infiniteScroll = false;
this.breadcrumbFolderTitle = null;
this.preselectNodes = [];
this.preselectedNodes = [];
this.clearSearch();
this.navigationChange.emit($event);
}
@@ -573,7 +595,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
*/
onCurrentSelection(nodesEntries: NodeEntry[]): void {
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) {
@@ -589,7 +611,7 @@ export class ContentNodeSelectorPanelComponent implements OnInit, OnDestroy {
}
hasPreselectNodes(): boolean {
return this.preselectNodes && this.preselectNodes.length > 0;
return this.preselectedNodes?.length > 0;
}
isSingleSelectionMode(): boolean {

View File

@@ -8,7 +8,7 @@
mat-align-tabs="start"
(selectedIndexChange)="onTabSelectionChange($event)"
[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
[currentFolderId]="data?.currentFolderId"
[restrictRootToCurrentFolderId]="data?.restrictRootToCurrentFolderId"
@@ -35,6 +35,11 @@
<mat-tab *ngIf="canPerformLocalUpload()"
label="{{ 'NODE_SELECTOR.UPLOAD_FROM_DEVICE' | translate }}"
[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-group>

View File

@@ -9,6 +9,22 @@
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 {

View File

@@ -1532,7 +1532,7 @@ describe('DocumentList', () => {
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');
fixture.detectChanges();
@@ -1548,7 +1548,7 @@ describe('DocumentList', () => {
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';
fixture.detectChanges();
@@ -1560,10 +1560,10 @@ describe('DocumentList', () => {
await fixture.whenStable();
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';
fixture.detectChanges();
@@ -1575,13 +1575,14 @@ describe('DocumentList', () => {
await fixture.whenStable();
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 () => {
const datatableSelectRowSpy = spyOn(documentList.dataTable, 'selectRow');
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.preselectNodes = mockPreselectedNodes;
@@ -1593,7 +1594,7 @@ describe('DocumentList', () => {
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');
fixture.detectChanges();

View File

@@ -487,7 +487,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
if (this.data) {
if (changes.node && changes.node.currentValue) {
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.onDataReady(changes.node.currentValue);
} else if (changes.imageResolver) {
@@ -501,7 +501,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.resetSelection();
if (this.node) {
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.syncPagination();
@@ -694,7 +694,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
onPageLoaded(nodePaging: NodePaging) {
if (nodePaging) {
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.setLoadingState(false);
@@ -800,7 +800,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
}
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', {
detail: {
node: event.row ? event.row.node : null,
@@ -919,13 +919,13 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
this.error.emit(err);
}
getPreselectNodesBasedOnSelectionMode(): NodeEntry[] {
return this.hasPreselectNodes() ? (this.isSingleSelectionMode() ? [this.preselectNodes[0]] : this.preselectNodes) : [];
getPreselectedNodesBasedOnSelectionMode(): NodeEntry[] {
return this.hasPreselectedNodes() ? (this.isSingleSelectionMode() ? [this.preselectNodes[0]] : this.preselectNodes) : [];
}
onPreselectNodes() {
if (this.hasPreselectNodes()) {
const preselectedNodes = [...this.isSingleSelectionMode() ? [this.data.getPreselectRows()[0]] : this.data.getPreselectRows()];
if (this.data.hasPreselectedRows()) {
const preselectedNodes = [...this.isSingleSelectionMode() ? [this.data.getPreselectedRows()[0]] : this.data.getPreselectedRows()];
const selectedNodes = [...this.selection, ...preselectedNodes];
for (const node of preselectedNodes) {
@@ -939,7 +939,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
return this.selectionMode === 'single';
}
hasPreselectNodes(): boolean {
return this.preselectNodes && this.preselectNodes.length > 0;
hasPreselectedNodes(): boolean {
return this.preselectNodes?.length > 0;
}
}

View File

@@ -16,7 +16,7 @@
*/
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 { ShareDataTableAdapter } from './share-datatable-adapter';
import { ContentTestingModule } from '../../testing/content.testing.module';
@@ -484,28 +484,38 @@ describe('ShareDataTableAdapter', () => {
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, []);
adapter.loadPage(mockNodePagingWithPreselectedNodes, null, null, mockPreselectedNodes);
expect(adapter.getPreselectRows().length).toBe(1);
expect(adapter.getPreselectRows()[0].isSelected).toBe(true);
expect(adapter.getPreselectedRows().length).toBe(1);
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, []);
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, []);
mockNode2.id = 'mock-file-id';
const preselectedNode = [ { entry: mockNode2 }];
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[];
selectedRow: DataRow;
allowDropFiles: boolean;
preselectRows: DataRow[] = [];
preselectedRows: DataRow[] = [];
set sortingMode(value: string) {
let newValue = (value || 'client').toLowerCase();
@@ -82,8 +82,8 @@ export class ShareDataTableAdapter implements DataTableAdapter {
this.sort();
}
getPreselectRows(): Array<DataRow> {
return this.preselectRows;
getPreselectedRows(): Array<DataRow> {
return this.preselectedRows;
}
getColumns(): Array<DataColumn> {
@@ -208,7 +208,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
}
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) {
@@ -218,7 +218,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
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') {
options.numeric = true;
@@ -255,9 +255,9 @@ export class ShareDataTableAdapter implements DataTableAdapter {
if (allowDropFiles !== undefined) {
this.allowDropFiles = allowDropFiles;
}
if (nodePaging && nodePaging.list) {
if (nodePaging?.list) {
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,
this.thumbnailService, this.allowDropFiles));
@@ -267,7 +267,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
if (this.sortingMode !== 'server') {
// Sort by first sortable or just first column
if (this.columns && this.columns.length > 0) {
if (this.columns?.length) {
const sorting = this.getSorting();
if (sorting) {
this.sortRows(shareDataRows, sorting);
@@ -302,7 +302,7 @@ export class ShareDataTableAdapter implements DataTableAdapter {
}
selectRowsBasedOnGivenNodes(preselectNodes: NodeEntry[]) {
if (preselectNodes && preselectNodes.length > 0) {
if (preselectNodes?.length) {
this.rows = this.rows.map((row) => {
preselectNodes.map((preselectedNode) => {
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",
"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",
"FILE_SERVER": "File server",
"REPOSITORY": "Repository",
"UPLOAD_FROM_DEVICE": "Upload from your device"
},
"OPERATION": {

View File

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

View File

@@ -60,6 +60,50 @@ describe('FileUploadingDialogComponent', () => {
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', () => {
it('should not render dialog when uploading list is empty', () => {
uploadService.addToQueue();
@@ -322,9 +366,9 @@ describe('FileUploadingDialogComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
const confirmDecription = fixture.nativeElement.querySelector('#confirmationDescription');
expect(confirmDecription).not.toBeNull();
expect(confirmDecription).toBeDefined();
const confirmDescription = fixture.nativeElement.querySelector('#confirmationDescription');
expect(confirmDescription).not.toBeNull();
expect(confirmDescription).toBeDefined();
const confirmTitle = fixture.nativeElement.querySelector('#confirmationTitle');
expect(confirmTitle).toBeDefined();

View File

@@ -15,7 +15,7 @@
* 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 { Subscription, merge, Subject } from 'rxjs';
import { FileUploadingListComponent } from './file-uploading-list.component';
@@ -39,6 +39,10 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
@Input()
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. */
@Output()
error: EventEmitter<any> = new EventEmitter();
@@ -107,7 +111,7 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
this.uploadService.fileUploadDeleted
)
.pipe(takeUntil(this.onDestroy$))
.subscribe(event => {
.subscribe((event: FileUploadCompleteEvent | FileUploadDeleteEvent) => {
this.totalCompleted = event.totalComplete;
this.changeDetector.detectChanges();
});
@@ -201,4 +205,20 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
this.onDestroy$.next(true);
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();
uploadFromLocalTabName = 'Upload from your device';
fileServerTabName = 'File server';
repositoryTabName = 'Repository';
async checkDialogIsDisplayed(): Promise<void> {
await BrowserVisibility.waitUntilElementIsVisible(this.dialog);
@@ -151,7 +151,7 @@ export class ContentNodeSelectorDialogPage {
await BrowserVisibility.waitUntilElementIsPresent(uploadButton);
await uploadButton.sendKeys(fileLocation);
await this.tabPage.clickTabByLabel(this.fileServerTabName);
await this.tabPage.clickTabByLabel(this.repositoryTabName);
await this.dataTable.waitForTableBody();
await this.dataTable.waitTillContentLoaded();

6
package-lock.json generated
View File

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

View File

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