[ADF-1443] Refactor Download directive (#4028)

* [ADF-1443] Refactor Download directive

* [ADF-1443] Node Download Directive now accepts single node and an array as input

* [ADF-1443] Fix Unit tests

* [ADF-1443] Fix unit test related to viewer component
This commit is contained in:
davidcanonieto
2018-12-05 16:39:21 +00:00
committed by Eugenio Romano
parent b99f6d57dc
commit 7197e1e13a
24 changed files with 386 additions and 111 deletions

View File

@@ -45,6 +45,7 @@ import { TemplateModule } from './templates/template.module';
import { ClipboardModule } from './clipboard/clipboard.module';
import { DirectiveModule } from './directives/directive.module';
import { DialogModule } from './dialogs/dialog.module';
import { PipeModule } from './pipes/pipe.module';
import { AlfrescoApiService } from './services/alfresco-api.service';
@@ -66,6 +67,7 @@ export function createTranslateLoader(http: HttpClient) {
PipeModule,
CommonModule,
DirectiveModule,
DialogModule,
ClipboardModule,
FormsModule,
ReactiveFormsModule,
@@ -103,6 +105,7 @@ export function createTranslateLoader(http: HttpClient) {
PipeModule,
CommonModule,
DirectiveModule,
DialogModule,
ClipboardModule,
FormsModule,
ReactiveFormsModule,
@@ -139,6 +142,7 @@ export class CoreModuleLazy {
PipeModule,
CommonModule,
DirectiveModule,
DialogModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
@@ -175,6 +179,7 @@ export class CoreModuleLazy {
PipeModule,
CommonModule,
DirectiveModule,
DialogModule,
ClipboardModule,
FormsModule,
ReactiveFormsModule,

View File

@@ -0,0 +1,43 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MaterialModule } from '../material.module';
import { DownloadZipDialogComponent } from './download-zip.dialog';
import { TranslateModule } from '@ngx-translate/core';
import { PipeModule } from '../pipes/pipe.module';
@NgModule({
imports: [
CommonModule,
MaterialModule,
TranslateModule.forChild(),
PipeModule
],
declarations: [
DownloadZipDialogComponent
],
exports: [
DownloadZipDialogComponent
],
entryComponents: [
DownloadZipDialogComponent
]
})
export class DialogModule {}

View File

@@ -0,0 +1,10 @@
<h1 matDialogTitle>{{ 'CORE.DIALOG.DOWNLOAD_ZIP.TITLE' | translate }}</h1>
<div mat-dialog-content>
<mat-progress-bar color="primary" mode="indeterminate"></mat-progress-bar>
</div>
<div mat-dialog-actions>
<span class="adf-spacer"></span>
<button mat-button color="primary" id="cancel-button" (click)="cancelDownload()">
{{ 'CORE.DIALOG.DOWNLOAD_ZIP.ACTIONS.CANCEL' | translate }}
</button>
</div>

View File

@@ -0,0 +1,5 @@
.adf-spacer { flex: 1 1 auto; }
.adf-download-zip-dialog .mat-dialog-actions .mat-button-wrapper {
text-transform: uppercase;
}

View File

@@ -0,0 +1,149 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TestBed } from '@angular/core/testing';
import { ComponentFixture } from '@angular/core/testing';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { DownloadZipDialogComponent } from './download-zip.dialog';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module';
import { DownloadZipService } from '../services/download-zip.service';
import { of } from 'rxjs';
describe('DownloadZipDialogComponent', () => {
let fixture: ComponentFixture<DownloadZipDialogComponent>;
let component: DownloadZipDialogComponent;
let element: HTMLElement;
let downloadZipService: DownloadZipService;
let dialogRef = {
close: jasmine.createSpy('close')
};
let dataMock = {
nodeIds: [
'123'
]
};
let pendingDownloadEntry = {
entry: {
bytesAdded: 0,
filesAdded: 0,
id: '5bfb0907',
status: 'PENDING',
totalBytes: 0,
totalFiles: 0
}
};
setupTestBed({
imports: [CoreTestingModule],
providers: [
{ provide: MatDialogRef, useValue: dialogRef },
{ provide: MAT_DIALOG_DATA, useValue: dataMock }
]
});
beforeEach(() => {
dialogRef.close.calls.reset();
fixture = TestBed.createComponent(DownloadZipDialogComponent);
downloadZipService = TestBed.get(DownloadZipService);
component = fixture.componentInstance;
element = fixture.nativeElement;
});
afterEach(() => {
fixture.destroy();
});
it('should call downloadZip when it is not cancelled', () => {
component.cancelled = false;
spyOn(component, 'downloadZip');
component.ngOnInit();
expect(component.downloadZip).toHaveBeenCalledWith(['123']);
});
it('should not call downloadZip when it is cancelled', () => {
component.cancelled = true;
spyOn(component, 'downloadZip');
component.ngOnInit();
expect(component.downloadZip).not.toHaveBeenCalled();
});
it('should not call downloadZip when it contains zero nodeIds', () => {
component.data = {
nodeIds: []
};
spyOn(component, 'downloadZip');
component.ngOnInit();
expect(component.downloadZip).not.toHaveBeenCalled();
});
it('should call cancelDownload when CANCEL button is clicked', () => {
fixture.detectChanges();
spyOn(component, 'cancelDownload').and.callThrough();
const cancelButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#cancel-button');
cancelButton.click();
expect(component.cancelDownload).toHaveBeenCalled();
});
it('should call createDownload when component is initialize', () => {
const createDownloadSpy = spyOn(downloadZipService, 'createDownload').and.returnValue(of(pendingDownloadEntry));
fixture.detectChanges();
expect(createDownloadSpy).toHaveBeenCalled();
});
it('should close dialog when download is completed', () => {
component.download('fakeUrl', 'fileName');
spyOn(downloadZipService, 'cancelDownload');
fixture.detectChanges();
expect(dialogRef.close).toHaveBeenCalled();
});
it('should close dialog when download is cancelled', () => {
fixture.detectChanges();
component.download('url', 'filename');
spyOn(downloadZipService, 'cancelDownload');
component.cancelDownload();
expect(dialogRef.close).toHaveBeenCalled();
});
it('should interrupt download when cancel button is clicked', () => {
spyOn(component, 'downloadZip');
spyOn(component, 'download');
spyOn(component, 'cancelDownload').and.callThrough();
fixture.detectChanges();
expect(component.downloadZip).toHaveBeenCalled();
const cancelButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#cancel-button');
cancelButton.click();
expect(component.cancelDownload).toHaveBeenCalled();
expect(component.download).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,110 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { DownloadEntry, MinimalNodeEntity } from 'alfresco-js-api';
import { LogService } from '../services/log.service';
import { DownloadZipService } from '../services/download-zip.service';
@Component({
selector: 'adf-download-zip-dialog',
templateUrl: './download-zip.dialog.html',
styleUrls: ['./download-zip.dialog.scss'],
host: { 'class': 'adf-download-zip-dialog' },
encapsulation: ViewEncapsulation.None
})
export class DownloadZipDialogComponent implements OnInit {
// flag for async threads
cancelled = false;
downloadId: string;
constructor(private dialogRef: MatDialogRef<DownloadZipDialogComponent>,
@Inject(MAT_DIALOG_DATA)
public data: any,
private logService: LogService,
private downloadZipService: DownloadZipService) {
}
ngOnInit() {
if (this.data && this.data.nodeIds && this.data.nodeIds.length > 0) {
if (!this.cancelled) {
this.downloadZip(this.data.nodeIds);
} else {
this.logService.log('Cancelled');
}
}
}
cancelDownload() {
this.cancelled = true;
this.downloadZipService.cancelDownload(this.downloadId);
this.dialogRef.close(false);
}
downloadZip(nodeIds: string[]) {
if (nodeIds && nodeIds.length > 0) {
this.downloadZipService.createDownload({ nodeIds }).subscribe((data: DownloadEntry) => {
if (data && data.entry && data.entry.id) {
const url = this.downloadZipService.getContentUrl(data.entry.id, true);
this.downloadZipService.getNode(data.entry.id).subscribe((downloadNode: MinimalNodeEntity) => {
this.logService.log(downloadNode);
const fileName = downloadNode.entry.name;
this.downloadId = data.entry.id;
this.waitAndDownload(data.entry.id, url, fileName);
});
}
});
}
}
waitAndDownload(downloadId: string, url: string, fileName: string) {
if (this.cancelled) {
return;
}
this.downloadZipService.getDownload(downloadId).subscribe((downloadEntry: DownloadEntry) => {
if (downloadEntry.entry) {
if (downloadEntry.entry.status === 'DONE') {
this.download(url, fileName);
} else {
setTimeout(() => {
this.waitAndDownload(downloadId, url, fileName);
}, 1000);
}
}
});
}
download(url: string, fileName: string) {
if (url && fileName) {
const link = document.createElement('a');
link.style.display = 'none';
link.download = fileName;
link.href = url;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
this.dialogRef.close(true);
}
}

18
lib/core/dialogs/index.ts Executable file
View File

@@ -0,0 +1,18 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './public-api';

20
lib/core/dialogs/public-api.ts Executable file
View File

@@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './download-zip.dialog';
export * from './dialog.module';

View File

@@ -26,6 +26,7 @@ import { NodeFavoriteDirective } from './node-favorite.directive';
import { NodePermissionDirective } from './node-permission.directive';
import { NodeRestoreDirective } from './node-restore.directive';
import { UploadDirective } from './upload.directive';
import { NodeDownloadDirective } from './node-download.directive';
@NgModule({
imports: [
@@ -39,6 +40,7 @@ import { UploadDirective } from './upload.directive';
NodeFavoriteDirective,
NodePermissionDirective,
NodeRestoreDirective,
NodeDownloadDirective,
UploadDirective
],
exports: [
@@ -48,6 +50,7 @@ import { UploadDirective } from './upload.directive';
NodeFavoriteDirective,
NodePermissionDirective,
NodeRestoreDirective,
NodeDownloadDirective,
UploadDirective
]
})

View File

@@ -0,0 +1,167 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { MatDialog } from '@angular/material';
import { Component, DebugElement } from '@angular/core';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreModule } from '../core.module';
import { DialogModule } from '../dialogs/dialog.module';
import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
import { AlfrescoApiService } from '../services/alfresco-api.service';
import { NodeDownloadDirective } from './node-download.directive';
@Component({
template: '<div [adfNodeDownload]="selection"></div>'
})
class TestComponent {
selection;
}
describe('NodeDownloadDirective', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
let element: DebugElement;
let dialog: MatDialog;
let apiService: AlfrescoApiService;
let contentService;
let dialogSpy;
setupTestBed({
imports: [
CoreModule.forRoot(),
DialogModule
],
providers: [
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }
],
declarations: [
TestComponent
]
});
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
element = fixture.debugElement.query(By.directive(NodeDownloadDirective));
dialog = TestBed.get(MatDialog);
apiService = TestBed.get(AlfrescoApiService);
contentService = apiService.getInstance().content;
dialogSpy = spyOn(dialog, 'open');
});
it('should not download node when selection is empty', () => {
spyOn(apiService, 'getInstance');
component.selection = [];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(apiService.getInstance).not.toHaveBeenCalled();
});
it('should not download zip when selection has no nodes', () => {
component.selection = [];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(dialogSpy).not.toHaveBeenCalled();
});
it('should download selected node as file', () => {
spyOn(contentService, 'getContentUrl');
const node = { entry: { id: 'node-id', isFile: true } };
component.selection = [node];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(contentService.getContentUrl).toHaveBeenCalledWith(node.entry.id, true);
});
it('should download selected shared node as file', () => {
spyOn(contentService, 'getContentUrl');
const node = { entry: { nodeId: 'shared-node-id', isFile: true } };
component.selection = [node];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(contentService.getContentUrl).toHaveBeenCalledWith(node.entry.nodeId, true);
});
it('should download selected files nodes as zip', () => {
const node1 = { entry: { id: 'node-1' } };
const node2 = { entry: { id: 'node-2' } };
component.selection = [node1, node2];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(dialogSpy.calls.argsFor(0)[1].data).toEqual({ nodeIds: [ 'node-1', 'node-2' ] });
});
it('should download selected shared files nodes as zip', () => {
const node1 = { entry: { nodeId: 'shared-node-1' } };
const node2 = { entry: { nodeId: 'shared-node-2' } };
component.selection = [node1, node2];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(dialogSpy.calls.argsFor(0)[1].data).toEqual({ nodeIds: [ 'shared-node-1', 'shared-node-2' ] });
});
it('should download selected folder node as zip', () => {
const node = { entry: { isFolder: true, id: 'node-id' } };
component.selection = [node];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(dialogSpy.calls.argsFor(0)[1].data).toEqual({ nodeIds: [ 'node-id' ] });
});
it('should create link element to download file node', () => {
const dummyLinkElement = {
download: null,
href: null,
click: () => null,
style: {
display: null
}
};
const node = { entry: { name: 'dummy', isFile: true, id: 'node-id' } };
spyOn(contentService, 'getContentUrl').and.returnValue('somewhere-over-the-rainbow');
spyOn(document, 'createElement').and.returnValue(dummyLinkElement);
spyOn(document.body, 'appendChild').and.stub();
spyOn(document.body, 'removeChild').and.stub();
component.selection = [node];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(document.createElement).toHaveBeenCalled();
expect(dummyLinkElement.download).toBe('dummy');
expect(dummyLinkElement.href).toContain('somewhere-over-the-rainbow');
});
});

View File

@@ -0,0 +1,134 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Directive, Input, HostListener } from '@angular/core';
import { MatDialog } from '@angular/material';
import { AlfrescoApiService } from '../services/alfresco-api.service';
import { DownloadZipDialogComponent } from '../dialogs/download-zip.dialog';
import { MinimalNodeEntity } from 'alfresco-js-api';
@Directive({
selector: '[adfNodeDownload]'
})
export class NodeDownloadDirective {
/** Nodes to download. */
// tslint:disable-next-line:no-input-rename
@Input('adfNodeDownload')
nodes: MinimalNodeEntity | MinimalNodeEntity[];
@HostListener('click')
onClick() {
this.downloadNodes(this.nodes);
}
constructor(
private apiService: AlfrescoApiService,
private dialog: MatDialog) {
}
/**
* Downloads multiple selected nodes.
* Packs result into a .ZIP archive if there is more than one node selected.
* @param selection Multiple selected nodes to download
*/
downloadNodes(selection: MinimalNodeEntity | Array<MinimalNodeEntity>) {
if (!this.isSelectionValid(selection)) {
return;
}
if (selection instanceof Array) {
if (selection.length === 1) {
this.downloadNode(selection[0]);
} else {
this.downloadZip(selection);
}
} else {
this.downloadNode(selection);
}
}
/**
* Downloads a single node.
* Packs result into a .ZIP archive is the node is a Folder.
* @param node Node to download
*/
downloadNode(node: MinimalNodeEntity) {
if (node && node.entry) {
const entry = node.entry;
if (entry.isFile) {
this.downloadFile(node);
}
if (entry.isFolder) {
this.downloadZip([node]);
}
// Check if there's nodeId for Shared Files
if (!entry.isFile && !entry.isFolder && (<any> entry).nodeId) {
this.downloadFile(node);
}
}
}
private isSelectionValid(selection: MinimalNodeEntity | Array<MinimalNodeEntity>) {
return selection || (selection instanceof Array && selection.length > 0);
}
private downloadFile(node: MinimalNodeEntity) {
if (node && node.entry) {
const contentApi = this.apiService.getInstance().content;
// nodeId for Shared node
const id = (<any> node.entry).nodeId || node.entry.id;
const url = contentApi.getContentUrl(id, true);
const fileName = node.entry.name;
this.download(url, fileName);
}
}
private downloadZip(selection: Array<MinimalNodeEntity>) {
if (selection && selection.length > 0) {
// nodeId for Shared node
const nodeIds = selection.map((node: any) => (node.entry.nodeId || node.entry.id));
this.dialog.open(DownloadZipDialogComponent, {
width: '600px',
disableClose: true,
data: {
nodeIds
}
});
}
}
private download(url: string, fileName: string) {
if (url && fileName) {
const link = document.createElement('a');
link.style.display = 'none';
link.download = fileName;
link.href = url;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}

View File

@@ -21,6 +21,7 @@ export * from './node-delete.directive';
export * from './node-favorite.directive';
export * from './node-permission.directive';
export * from './node-restore.directive';
export * from './node-download.directive';
export * from './upload.directive';
export * from './directive.module';

View File

@@ -39,6 +39,7 @@ export * from './pipes/index';
export * from './services/index';
export * from './directives/index';
export * from './clipboard/index';
export * from './dialogs/index';
export * from './utils/index';
export * from './interface/index';

View File

@@ -0,0 +1,60 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NodeEntry, DownloadEntry, DownloadBodyCreate } from 'alfresco-js-api';
import { Injectable } from '@angular/core';
import { Observable, from, throwError } from 'rxjs';
import { LogService } from './log.service';
import { AlfrescoApiService } from './alfresco-api.service';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DownloadZipService {
constructor(private apiService: AlfrescoApiService,
private logService: LogService) {
}
createDownload(payload: DownloadBodyCreate): Observable<DownloadEntry> {
return from(this.apiService.getInstance().core.downloadsApi.createDownload(payload)).pipe(
catchError((err) => this.handleError(err))
);
}
getContentUrl(nodeId: string, attachment?: boolean): string {
return this.apiService.getInstance().content.getContentUrl(nodeId, attachment);
}
getNode(nodeId: string): Observable<NodeEntry> {
return from(this.apiService.getInstance().core.nodesApi.getNode(nodeId));
}
getDownload(downloadId: string): Observable<DownloadEntry> {
return from(this.apiService.getInstance().core.downloadsApi.getDownload(downloadId));
}
cancelDownload(downloadId: string) {
this.apiService.getInstance().core.downloadsApi.cancelDownload(downloadId);
}
private handleError(error: any) {
this.logService.error(error);
return throwError(error || 'Server error');
}
}

View File

@@ -51,3 +51,4 @@ export * from './comment-content.service';
export * from './login-dialog.service';
export * from './external-alfresco-api.service';
export * from './jwt-helper.service';
export * from './download-zip.service';

View File

@@ -76,7 +76,7 @@
mat-icon-button
title="{{ 'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate }}"
data-automation-id="adf-toolbar-download"
(click)="downloadContent()">
[adfNodeDownload]="node">
<mat-icon>file_download</mat-icon>
</button>

View File

@@ -345,7 +345,10 @@ describe('ViewerComponent', () => {
const nodeDetails = { name: displayName, id: '12' };
const contentUrl = '/content/url/path';
const alfrescoApiInstanceMock = {
nodes: { getNodeInfo: () => Promise.resolve(nodeDetails) },
nodes: {
getNodeInfo: () => Promise.resolve(nodeDetails),
getNode: () => Promise.resolve({ id: 'fake-node' })
},
content: { getContentUrl: () => contentUrl }
};
@@ -370,6 +373,8 @@ describe('ViewerComponent', () => {
Promise.resolve({ name: 'file2', content: {} })
);
spyOn(alfrescoApiService.nodesApi, 'getNode').and.returnValue(Promise.resolve({ id: 'fake-node' }));
component.urlFile = null;
component.displayName = null;
component.blobFile = null;
@@ -549,35 +554,6 @@ describe('ViewerComponent', () => {
});
});
it('should invoke download action with the toolbar button', (done) => {
component.allowDownload = true;
spyOn(component, 'downloadContent').and.stub();
fixture.detectChanges();
const button: HTMLButtonElement = element.querySelector('[data-automation-id="adf-toolbar-download"]') as HTMLButtonElement;
button.click();
fixture.whenStable().then(() => {
expect(component.downloadContent).toHaveBeenCalled();
done();
});
});
it('should raise download event with the toolbar button', (done) => {
component.allowDownload = true;
component.downloadUrl = 'URL';
component.fileName = 'fileName';
fixture.detectChanges();
component.download.subscribe((e) => {
expect(e).not.toBeNull();
done();
});
const button: HTMLButtonElement = element.querySelector('[data-automation-id="adf-toolbar-download"]') as HTMLButtonElement;
button.click();
});
it('should render default print button', (done) => {
component.allowPrint = true;
fixture.detectChanges();
@@ -674,6 +650,30 @@ describe('ViewerComponent', () => {
button.click();
});
it('should get and assign node for download', (done) => {
const node = { id: 'fake-node' };
component.fileNodeId = '12';
component.urlFile = '';
const displayName = 'the-name';
const nodeDetails = { name: displayName, id: '12', content: { mimeType: 'txt' } };
const contentUrl = '/content/url/path';
const alfrescoApiInstanceMock = {
nodes: {
getNodeInfo: () => Promise.resolve(nodeDetails),
getNode: () => Promise.resolve(node)
},
content: { getContentUrl: () => contentUrl }
};
spyOn(alfrescoApiService, 'getInstance').and.returnValue(alfrescoApiInstanceMock);
component.ngOnChanges(null);
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.node).toBe(node);
done();
});
});
});
describe('View', () => {
@@ -923,7 +923,10 @@ describe('ViewerComponent', () => {
const nodeDetails = { name: displayName, id: '12', content: { mimeType: 'txt' } };
const contentUrl = '/content/url/path';
const alfrescoApiInstanceMock = {
nodes: { getNodeInfo: () => Promise.resolve(nodeDetails) },
nodes: {
getNodeInfo: () => Promise.resolve(nodeDetails),
getNode: () => Promise.resolve({ id: 'fake-node' })
},
content: { getContentUrl: () => contentUrl }
};
@@ -998,7 +1001,6 @@ describe('ViewerComponent', () => {
component.enterFullScreen();
expect(domElement.msRequestFullscreen).toHaveBeenCalled();
});
});
});

View File

@@ -21,7 +21,7 @@ import {
Input, OnChanges, Output, SimpleChanges, TemplateRef,
ViewEncapsulation, OnInit, OnDestroy
} from '@angular/core';
import { MinimalNodeEntryEntity, RenditionEntry } from 'alfresco-js-api';
import { MinimalNodeEntryEntity, RenditionEntry, MinimalNodeEntity } from 'alfresco-js-api';
import { BaseEvent } from '../../events';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service';
@@ -196,10 +196,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
@Input()
fileName: string;
/** URL to download. */
@Input()
downloadUrl: string = null;
/** Number of times the Viewer will retry fetching content Rendition.
* There is a delay of at least one second between attempts.
*/
@@ -210,10 +206,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
@Output()
goBack = new EventEmitter<BaseEvent<any>>();
/** Emitted when user clicks the 'Download' button. */
@Output()
download = new EventEmitter<BaseEvent<any>>();
/** Emitted when user clicks the 'Print' button. */
@Output()
print = new EventEmitter<BaseEvent<any>>();
@@ -244,7 +236,7 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
viewerType = 'unknown';
isLoading = false;
node: MinimalNodeEntryEntity;
node: MinimalNodeEntity;
extensionTemplates: { template: TemplateRef<any>, isVisible: boolean }[] = [];
externalExtensions: string[] = [];
@@ -332,6 +324,12 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.logService.error('This node does not exist');
}
);
this.apiService.nodesApi.getNode(this.nodeId).then(
(node) => {
this.node = node;
}
);
} else if (this.sharedLinkId) {
this.apiService.sharedLinksApi.getSharedLink(this.sharedLinkId).then(
@@ -366,7 +364,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.extension = this.getFileExtension(filenameFromUrl);
this.urlFileContent = this.urlFile;
this.downloadUrl = this.urlFile;
this.fileName = this.displayName;
this.viewerType = this.urlFileViewer || this.getViewerTypeByExtension(this.extension);
@@ -393,7 +390,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.extension = this.getFileExtension(data.name);
this.fileName = data.name;
this.downloadUrl = this.apiService.contentApi.getContentUrl(data.id, true);
this.viewerType = this.getViewerTypeByExtension(this.extension);
if (this.viewerType === 'unknown') {
@@ -419,7 +415,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.fileName = details.entry.name;
this.urlFileContent = this.apiService.contentApi.getSharedLinkContentUrl(this.sharedLinkId, false);
this.downloadUrl = this.apiService.contentApi.getSharedLinkContentUrl(this.sharedLinkId, true);
this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
if (this.viewerType === 'unknown') {
@@ -608,25 +603,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
}
}
downloadContent() {
if (this.allowDownload && this.downloadUrl && this.fileName) {
const args = new BaseEvent();
this.download.next(args);
if (!args.defaultPrevented) {
const link = document.createElement('a');
link.style.display = 'none';
link.download = this.fileName;
link.href = this.downloadUrl;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}
printContent() {
if (this.allowPrint) {
const args = new BaseEvent();
@@ -767,5 +743,4 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
private generateCacheBusterNumber() {
this.cacheBusterNumber = Date.now();
}
}

View File

@@ -39,6 +39,7 @@ import { ViewerToolbarComponent } from './components/viewer-toolbar.component';
import { ViewerComponent } from './components/viewer.component';
import { ViewerExtensionDirective } from './directives/viewer-extension.directive';
import { ViewerToolbarActionsComponent } from './components/viewer-toolbar-actions.component';
import { DirectiveModule } from '../directives/directive.module';
@NgModule({
imports: [
@@ -49,7 +50,8 @@ import { ViewerToolbarActionsComponent } from './components/viewer-toolbar-actio
ReactiveFormsModule,
ToolbarModule,
PipeModule,
FlexLayoutModule
FlexLayoutModule,
DirectiveModule
],
declarations: [
PdfPasswordDialogComponent,