[ADF-793] Ability to create PDF renditions in case of non supported formats (#1994)

* Style changes and button

* Convert to PDF button

* Convert to PDF button part II.

* Convert within the Not Supported Format component

* Rendition loading skeleton

* Conversion is working.

* Convert button behaviour tests

* Rebasing fix.
This commit is contained in:
Popovics András
2017-06-20 21:02:22 +01:00
committed by Eugenio Romano
parent 0ff4ff5f24
commit b457024cab
11 changed files with 331 additions and 49 deletions

View File

@@ -70,6 +70,7 @@ describe('RenditionsService', () => {
it('Create redition service should call the server with the ID passed and the asked encoding', (done) => { it('Create redition service should call the server with the ID passed and the asked encoding', (done) => {
service.createRendition('fake-node-id', 'pdf').subscribe((res) => { service.createRendition('fake-node-id', 'pdf').subscribe((res) => {
expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST');
expect(jasmine.Ajax.requests.mostRecent().url).toBe('http://localhost:3000/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/fake-node-id/renditions'); expect(jasmine.Ajax.requests.mostRecent().url).toBe('http://localhost:3000/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/fake-node-id/renditions');
done(); done();
}); });
@@ -81,6 +82,17 @@ describe('RenditionsService', () => {
}); });
}); });
describe('convert', () => {
it('should call the server with the ID passed and the asked encoding for creation', (done) => {
service.convert('fake-node-id', 'pdf');
expect(jasmine.Ajax.requests.mostRecent().method).toBe('POST');
expect(jasmine.Ajax.requests.mostRecent().url).toBe('http://localhost:3000/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/fake-node-id/renditions');
done();
});
});
it('Get redition service should catch the error', (done) => { it('Get redition service should catch the error', (done) => {
service.getRenditionsListByNodeId('fake-node-id').subscribe((res) => { service.getRenditionsListByNodeId('fake-node-id').subscribe((res) => {
}, (res) => { }, (res) => {

View File

@@ -76,6 +76,19 @@ export class RenditionsService {
.catch(err => this.handleError(err)); .catch(err => this.handleError(err));
} }
convert(nodeId: string, encoding: string, pollingInterval: number|undefined) {
return this.createRendition(nodeId, encoding)
.concatMap(() => this.pollRendition(nodeId, encoding, pollingInterval));
}
private pollRendition(nodeId: string, encoding: string, interval: number = 1000) {
return Observable.interval(interval)
.switchMap(() => this.getRendition(nodeId, encoding))
.takeWhile((data) => {
return (data.entry.status !== 'CREATED');
});
}
private handleError(error: any): Observable<any> { private handleError(error: any): Observable<any> {
this.logService.error(error); this.logService.error(error);
return Observable.throw(error || 'Server error'); return Observable.throw(error || 'Server error');

View File

@@ -35,6 +35,7 @@ import { NotSupportedFormat } from './src/components/notSupportedFormat.componen
import { PdfViewerComponent } from './src/components/pdfViewer.component'; import { PdfViewerComponent } from './src/components/pdfViewer.component';
import { TxtViewerComponent } from './src/components/txtViewer.component'; import { TxtViewerComponent } from './src/components/txtViewer.component';
import { ExtensionViewerDirective } from './src/directives/extension-viewer.directive'; import { ExtensionViewerDirective } from './src/directives/extension-viewer.directive';
import { MdIconModule, MdButtonModule, MdProgressSpinnerModule } from '@angular/material';
export * from './src/components/viewer.component'; export * from './src/components/viewer.component';
export * from './src/services/rendering-queue.services'; export * from './src/services/rendering-queue.services';
@@ -60,7 +61,10 @@ export const VIEWER_PROVIDERS: any[] = [
@NgModule({ @NgModule({
imports: [ imports: [
CoreModule CoreModule,
MdIconModule,
MdButtonModule,
MdProgressSpinnerModule
], ],
declarations: [ declarations: [
...VIEWER_DIRECTIVES ...VIEWER_DIRECTIVES

View File

@@ -10,7 +10,7 @@
"test": "karma start karma.conf.js --reporters mocha,coverage --single-run --mode coverage", "test": "karma start karma.conf.js --reporters mocha,coverage --single-run --mode coverage",
"test-browser": "karma start karma.conf.js --reporters kjhtml --component", "test-browser": "karma start karma.conf.js --reporters kjhtml --component",
"coverage": "npm run test && wsrv -o -p 9875 ./coverage/report", "coverage": "npm run test && wsrv -o -p 9875 ./coverage/report",
"prepublish" : "npm run build" "prepublish": "npm run build"
}, },
"main": "bundles/ng2-alfresco-viewer.js", "main": "bundles/ng2-alfresco-viewer.js",
"repository": { "repository": {
@@ -45,7 +45,6 @@
"@angular/platform-browser": "~4.0.0", "@angular/platform-browser": "~4.0.0",
"@angular/platform-browser-dynamic": "~4.0.0", "@angular/platform-browser-dynamic": "~4.0.0",
"@angular/router": "~4.0.0", "@angular/router": "~4.0.0",
"@angular/material": "2.0.0-beta.6", "@angular/material": "2.0.0-beta.6",
"alfresco-js-api": "~1.5.0", "alfresco-js-api": "~1.5.0",
"core-js": "2.4.1", "core-js": "2.4.1",

View File

@@ -1,23 +1,48 @@
.viewer-download-text { .unsupported-container {
text-align: center; width: 600px;
word-wrap: break-word;
}
.viewer-margin-cloud-download{
margin-right: 20px;
} }
.viewer-margin { .viewer-margin {
margin: auto !important; margin: auto !important;
} padding: 16px;
.center-element {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.full_width{ .viewer-download-text {
width :95% !important; text-align: center;
word-wrap: break-word;
min-height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
.viewer-download-text h4 {
margin: 0;
}
.adf-conversion-spinner {
margin: 16px 0;
}
.viewer-margin >>> .adf-conversion-spinner.mat-spinner path {
stroke: #00BFD4;
}
.button-container {
display: flex;
align-items: center;
justify-content: space-around;
}
.button-container button {
line-height: 40px;
}
.viewer-button-icon {
margin-right: 10px;
margin-top: -5px;
} }

View File

@@ -1,12 +1,21 @@
<section class="section--center mdl-grid mdl-grid--no-spacing"> <section *ngIf="!isConversionFinished" class="unsupported-container">
<div class="viewer-margin mdl-card mdl-cell mdl-cell--9-col-desktop mdl-cell--6-col-tablet mdl-cell--4-col-phone mdl-shadow--2dp"> <div class="viewer-margin mdl-card mdl-cell mdl-cell--9-col-desktop mdl-cell--6-col-tablet mdl-cell--4-col-phone mdl-shadow--2dp">
<div class="viewer-download-text mdl-card__supporting-text viewer-margin"> <div *ngIf="!isConversionStarted" class="viewer-download-text mdl-card__supporting-text">
<h4>File '<span>{{nameFile}}</span>' is of an unsupported format</h4> <h4>File '<span>{{nameFile}}</span>' is of an unsupported format</h4>
</div> </div>
<div class="center-element mdl-card__actions"> <md-spinner *ngIf="isConversionStarted" id="conversion-spinner" class="adf-conversion-spinner" color="primary"></md-spinner>
<button id="viewer-download-button" aria-label="Download" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--accent" (click)="download()"> <div class="button-container mdl-card__actions">
<i class="viewer-margin-cloud-download material-icons">cloud_download</i> Download <button md-button id="viewer-download-button" aria-label="Download" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--accent" (click)="download()">
<md-icon class="viewer-button-icon">cloud_download</md-icon> Download
</button>
<button md-button *ngIf="convertible" [disabled]="isConversionStarted" id="viewer-convert-button" aria-label="Convert to PDF" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--primary" (click)="convertToPdf()">
<md-icon class="viewer-button-icon">insert_drive_file</md-icon> Convert to PDF
</button>
<button md-button *ngIf="displayable" id="viewer-display-button" aria-label="Show PDF" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--primary" (click)="showPDF()">
<md-icon class="viewer-button-icon">insert_drive_file</md-icon> Show PDF
</button> </button>
</div> </div>
</div> </div>
</section> </section>
<pdf-viewer *ngIf="isConversionFinished" id="pdf-rendition-viewer" [showToolbar]="showToolbar" [urlFile]="renditionUrl" [nameFile]="nameFile"></pdf-viewer>

View File

@@ -17,34 +17,59 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { NotSupportedFormat } from './notSupportedFormat.component'; import { NotSupportedFormat } from './notSupportedFormat.component';
import { PdfViewerComponent } from './pdfViewer.component';
import { DebugElement } from '@angular/core'; import { DebugElement } from '@angular/core';
import { MdIconModule, MdButtonModule, MdProgressSpinnerModule } from '@angular/material';
import { Subject } from 'rxjs';
import { import {
AlfrescoAuthenticationService, AlfrescoAuthenticationService,
AlfrescoSettingsService, AlfrescoSettingsService,
AlfrescoApiService,
CoreModule, CoreModule,
ContentService ContentService,
AlfrescoApiService,
LogService,
RenditionsService
} from 'ng2-alfresco-core'; } from 'ng2-alfresco-core';
type RenditionResponse = {
entry: {
status: string
}
};
describe('Test ng2-alfresco-viewer Not Supported Format View component', () => { describe('Test ng2-alfresco-viewer Not Supported Format View component', () => {
const nodeId = 'not-supported-node-id';
let component: NotSupportedFormat; let component: NotSupportedFormat;
let service: ContentService; let service: ContentService;
let fixture: ComponentFixture<NotSupportedFormat>; let fixture: ComponentFixture<NotSupportedFormat>;
let debug: DebugElement; let debug: DebugElement;
let element: HTMLElement; let element: HTMLElement;
let renditionsService: RenditionsService;
let renditionSubject: Subject<RenditionResponse>,
conversionSubject: Subject<any>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
CoreModule CoreModule,
MdIconModule,
MdButtonModule,
MdProgressSpinnerModule
],
declarations: [
NotSupportedFormat,
PdfViewerComponent
], ],
declarations: [NotSupportedFormat],
providers: [ providers: [
AlfrescoSettingsService, AlfrescoSettingsService,
AlfrescoAuthenticationService, AlfrescoAuthenticationService,
AlfrescoApiService, AlfrescoApiService,
ContentService ContentService,
RenditionsService,
LogService
] ]
}).compileComponents(); }).compileComponents();
})); }));
@@ -55,11 +80,21 @@ describe('Test ng2-alfresco-viewer Not Supported Format View component', () => {
debug = fixture.debugElement; debug = fixture.debugElement;
element = fixture.nativeElement; element = fixture.nativeElement;
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); component.nodeId = nodeId;
renditionSubject = new Subject<RenditionResponse>();
conversionSubject = new Subject<any>();
renditionsService = TestBed.get(RenditionsService);
spyOn(renditionsService, 'getRendition').and.returnValue(renditionSubject);
spyOn(renditionsService, 'convert').and.returnValue(conversionSubject);
}); });
describe('View', () => { describe('View', () => {
beforeEach(() => {
fixture.detectChanges();
});
it('should be present Download button', () => { it('should be present Download button', () => {
expect(element.querySelector('#viewer-download-button')).not.toBeNull(); expect(element.querySelector('#viewer-download-button')).not.toBeNull();
}); });
@@ -69,9 +104,69 @@ describe('Test ng2-alfresco-viewer Not Supported Format View component', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(element.querySelector('h4 span').innerHTML).toEqual('Example Content.xls'); expect(element.querySelector('h4 span').innerHTML).toEqual('Example Content.xls');
}); });
it('should NOT show loading spinner by default', () => {
expect(element.querySelector('#conversion-spinner')).toBeNull('Conversion spinner should NOT be shown by default');
});
});
describe('Convertibility to pdf', () => {
it('should not show the "Convert to PDF" button by default', () => {
fixture.detectChanges();
expect(element.querySelector('#viewer-convert-button')).toBeNull();
});
it('should be checked on ngInit', () => {
fixture.detectChanges();
expect(renditionsService.getRendition).toHaveBeenCalledWith(nodeId, 'pdf');
});
it('should NOT be checked on ngInit if nodeId is not set', () => {
component.nodeId = null;
fixture.detectChanges();
expect(renditionsService.getRendition).not.toHaveBeenCalled();
});
it('should show the "Convert to PDF" button if the node is convertible', async(() => {
fixture.detectChanges();
renditionSubject.next({ entry: { status: 'NOT_CREATED' } });
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#viewer-convert-button')).not.toBeNull();
});
}));
it('should NOT show the "Convert to PDF" button if the node is NOT convertible', async(() => {
component.convertible = true;
fixture.detectChanges();
renditionSubject.error(new Error('Mocked error'));
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#viewer-convert-button')).toBeNull();
});
}));
it('should NOT show the "Convert to PDF" button if the node is already converted', async(() => {
renditionSubject.next({ entry: { status: 'CREATED' } });
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#viewer-convert-button')).toBeNull();
});
}));
}); });
describe('User Interaction', () => { describe('User Interaction', () => {
beforeEach(() => {
fixture.detectChanges();
});
describe('Download', () => {
it('should call download method if Click on Download button', () => { it('should call download method if Click on Download button', () => {
spyOn(window, 'open'); spyOn(window, 'open');
component.urlFile = 'test'; component.urlFile = 'test';
@@ -93,4 +188,47 @@ describe('Test ng2-alfresco-viewer Not Supported Format View component', () => {
expect(service.downloadBlob).toHaveBeenCalled(); expect(service.downloadBlob).toHaveBeenCalled();
}); });
}); });
describe('Conversion', () => {
function clickOnConvertButton() {
renditionSubject.next({ entry: { status: 'NOT_CREATED' } });
fixture.detectChanges();
let convertButton: any = element.querySelector('#viewer-convert-button');
convertButton.click();
fixture.detectChanges();
}
it('should show loading spinner and disable the "Convert to PDF button" after the button was clicked', () => {
clickOnConvertButton();
let convertButton: any = element.querySelector('#viewer-convert-button');
expect(element.querySelector('#conversion-spinner')).not.toBeNull('Conversion spinner should be shown');
expect(convertButton.disabled).toBe(true);
});
it('should re-enable the "Convert to PDF button" and hide spinner after unsuccessful conversion and hide loading spinner', () => {
clickOnConvertButton();
conversionSubject.error(new Error());
fixture.detectChanges();
let convertButton: any = element.querySelector('#viewer-convert-button');
expect(element.querySelector('#conversion-spinner')).toBeNull('Conversion spinner should be shown');
expect(convertButton.disabled).toBe(false);
});
it('should show the pdf rendition after successful conversion', () => {
clickOnConvertButton();
conversionSubject.next();
conversionSubject.complete();
fixture.detectChanges();
fixture.detectChanges();
expect(element.querySelector('#pdf-rendition-viewer')).not.toBeNull('Pdf rendition should be shown.');
});
});
});
}); });

View File

@@ -15,15 +15,18 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { ContentService } from 'ng2-alfresco-core'; import { ContentService, RenditionsService } from 'ng2-alfresco-core';
import { AlfrescoApiService } from 'ng2-alfresco-core';
const DEFAULT_CONVERSION_ENCODING = 'pdf';
@Component({ @Component({
selector: 'not-supported-format', selector: 'not-supported-format',
templateUrl: './notSupportedFormat.component.html', templateUrl: './notSupportedFormat.component.html',
styleUrls: ['./notSupportedFormat.component.css'] styleUrls: ['./notSupportedFormat.component.css']
}) })
export class NotSupportedFormat { export class NotSupportedFormat implements OnInit {
@Input() @Input()
nameFile: string; nameFile: string;
@@ -34,9 +37,23 @@ export class NotSupportedFormat {
@Input() @Input()
blobFile: Blob; blobFile: Blob;
constructor(private contentService: ContentService) { @Input()
nodeId: string|null = null;
} @Input()
showToolbar: boolean = true;
convertible: boolean = false;
displayable: boolean = false;
isConversionStarted: boolean = false;
isConversionFinished: boolean = false;
renditionUrl: string|null = null;
constructor(
private contentService: ContentService,
private renditionsService: RenditionsService,
private apiService: AlfrescoApiService
) {}
/** /**
* Download file opening it in a new window * Download file opening it in a new window
@@ -48,4 +65,56 @@ export class NotSupportedFormat {
this.contentService.downloadBlob(this.blobFile, this.nameFile); this.contentService.downloadBlob(this.blobFile, this.nameFile);
} }
} }
ngOnInit() {
if (this.nodeId) {
this.checkRendition();
}
}
/**
* Update component's button according to the given rendition's availability
*
* @param {string} encoding - the rendition id
*/
checkRendition(encoding: string = DEFAULT_CONVERSION_ENCODING): void {
this.renditionsService.getRendition(this.nodeId, encoding)
.subscribe(
(response: any) => {
if (response.entry.status === 'NOT_CREATED') {
this.convertible = true;
this.displayable = false;
} else if (response.entry.status === 'CREATED') {
this.convertible = false;
this.displayable = true;
}
},
() => {
this.convertible = false;
this.displayable = false;
}
);
}
/**
* Set the component to loading state and send the conversion starting signal to parent component
*/
convertToPdf(): void {
this.isConversionStarted = true;
this.renditionsService.convert(this.nodeId, DEFAULT_CONVERSION_ENCODING)
.subscribe({
error: (error) => { this.isConversionStarted = false; },
complete: () => { this.showPDF(); }
});
}
/**
* Show the PDF rendition of the node
*/
showPDF(): void {
this.renditionUrl = this.apiService.getInstance().content.getRenditionUrl(this.nodeId, DEFAULT_CONVERSION_ENCODING);
this.isConversionStarted = false;
this.isConversionFinished = true;
}
} }

View File

@@ -64,7 +64,13 @@
</span> </span>
<div *ngIf="!supportedExtension()"> <div *ngIf="!supportedExtension()">
<not-supported-format *ngIf="!extensionTemplate" [urlFile]="urlFileContent" [blobFile]="blobFile" [nameFile]="displayName"></not-supported-format> <not-supported-format *ngIf="!extensionTemplate"
[urlFile]="urlFileContent"
[blobFile]="blobFile"
[nameFile]="displayName"
[showToolbar]="showToolbar"
[nodeId]="fileNodeId">
</not-supported-format>
</div> </div>
<!-- End View Switch --> <!-- End View Switch -->

View File

@@ -29,7 +29,8 @@ import {
AlfrescoAuthenticationService, AlfrescoAuthenticationService,
AlfrescoSettingsService, AlfrescoSettingsService,
AlfrescoApiService, AlfrescoApiService,
CoreModule CoreModule,
RenditionsService
} from 'ng2-alfresco-core'; } from 'ng2-alfresco-core';
declare let jasmine: any; declare let jasmine: any;
@@ -58,7 +59,8 @@ describe('Test ng2-alfresco-viewer ViewerComponent', () => {
AlfrescoSettingsService, AlfrescoSettingsService,
AlfrescoAuthenticationService, AlfrescoAuthenticationService,
AlfrescoApiService, AlfrescoApiService,
RenderingQueueServices RenderingQueueServices,
RenditionsService
] ]
}).compileComponents(); }).compileComponents();
})); }));
@@ -74,6 +76,8 @@ describe('Test ng2-alfresco-viewer ViewerComponent', () => {
component.showToolbar = true; component.showToolbar = true;
component.urlFile = 'base/src/assets/fake-test-file.pdf'; component.urlFile = 'base/src/assets/fake-test-file.pdf';
component.mimeType = 'application/pdf';
component.ngOnChanges(null);
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -196,7 +200,7 @@ describe('Test ng2-alfresco-viewer ViewerComponent', () => {
}); });
}); });
describe('Extension Type Test', () => { describe('Exteznsion Type Test', () => {
it('should extension file pdf be loaded', (done) => { it('should extension file pdf be loaded', (done) => {
component.urlFile = 'base/src/assets/fake-test-file.pdf'; component.urlFile = 'base/src/assets/fake-test-file.pdf';
@@ -259,6 +263,7 @@ describe('Test ng2-alfresco-viewer ViewerComponent', () => {
it('should the not supported div be loaded if the file is a not supported extension', (done) => { it('should the not supported div be loaded if the file is a not supported extension', (done) => {
component.urlFile = 'fake-url-file.unsupported'; component.urlFile = 'fake-url-file.unsupported';
component.mimeType = '';
component.ngOnChanges(null).then(() => { component.ngOnChanges(null).then(() => {
fixture.detectChanges(); fixture.detectChanges();

View File

@@ -143,7 +143,9 @@
"wsrv": "^0.1.7", "wsrv": "^0.1.7",
"node-sass": "4.5.3", "node-sass": "4.5.3",
"sass-loader": "6.0.5", "sass-loader": "6.0.5",
"markdown-toc": "1.1.0" "license-check": "1.1.5",
"markdown-toc": "1.1.0",
"happypack": "3.0.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"module": "./index.js", "module": "./index.js",