From b457024cab8fcb13f4f92cdeddb9684d1798b439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Popovics=20Andr=C3=A1s?= Date: Tue, 20 Jun 2017 21:02:22 +0100 Subject: [PATCH] [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. --- .../src/services/renditions.service.spec.ts | 12 ++ .../src/services/renditions.service.ts | 13 ++ ng2-components/ng2-alfresco-viewer/index.ts | 6 +- .../ng2-alfresco-viewer/package.json | 3 +- .../notSupportedFormat.component.css | 49 +++-- .../notSupportedFormat.component.html | 19 +- .../notSupportedFormat.component.spec.ts | 176 ++++++++++++++++-- .../notSupportedFormat.component.ts | 79 +++++++- .../src/components/viewer.component.html | 8 +- .../src/components/viewer.component.spec.ts | 11 +- ng2-components/package.json | 4 +- 11 files changed, 331 insertions(+), 49 deletions(-) diff --git a/ng2-components/ng2-alfresco-core/src/services/renditions.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/renditions.service.spec.ts index 370b61bde0..7e3d922c41 100644 --- a/ng2-components/ng2-alfresco-core/src/services/renditions.service.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/services/renditions.service.spec.ts @@ -70,6 +70,7 @@ describe('RenditionsService', () => { 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) => { + 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(); }); @@ -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) => { service.getRenditionsListByNodeId('fake-node-id').subscribe((res) => { }, (res) => { diff --git a/ng2-components/ng2-alfresco-core/src/services/renditions.service.ts b/ng2-components/ng2-alfresco-core/src/services/renditions.service.ts index 2d4ce2bb1e..a30882faab 100644 --- a/ng2-components/ng2-alfresco-core/src/services/renditions.service.ts +++ b/ng2-components/ng2-alfresco-core/src/services/renditions.service.ts @@ -76,6 +76,19 @@ export class RenditionsService { .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 { this.logService.error(error); return Observable.throw(error || 'Server error'); diff --git a/ng2-components/ng2-alfresco-viewer/index.ts b/ng2-components/ng2-alfresco-viewer/index.ts index 4addc22125..9bc1cbdb1e 100644 --- a/ng2-components/ng2-alfresco-viewer/index.ts +++ b/ng2-components/ng2-alfresco-viewer/index.ts @@ -35,6 +35,7 @@ import { NotSupportedFormat } from './src/components/notSupportedFormat.componen import { PdfViewerComponent } from './src/components/pdfViewer.component'; import { TxtViewerComponent } from './src/components/txtViewer.component'; import { ExtensionViewerDirective } from './src/directives/extension-viewer.directive'; +import { MdIconModule, MdButtonModule, MdProgressSpinnerModule } from '@angular/material'; export * from './src/components/viewer.component'; export * from './src/services/rendering-queue.services'; @@ -60,7 +61,10 @@ export const VIEWER_PROVIDERS: any[] = [ @NgModule({ imports: [ - CoreModule + CoreModule, + MdIconModule, + MdButtonModule, + MdProgressSpinnerModule ], declarations: [ ...VIEWER_DIRECTIVES diff --git a/ng2-components/ng2-alfresco-viewer/package.json b/ng2-components/ng2-alfresco-viewer/package.json index 1ac270a02d..e4f4c9311e 100644 --- a/ng2-components/ng2-alfresco-viewer/package.json +++ b/ng2-components/ng2-alfresco-viewer/package.json @@ -10,7 +10,7 @@ "test": "karma start karma.conf.js --reporters mocha,coverage --single-run --mode coverage", "test-browser": "karma start karma.conf.js --reporters kjhtml --component", "coverage": "npm run test && wsrv -o -p 9875 ./coverage/report", - "prepublish" : "npm run build" + "prepublish": "npm run build" }, "main": "bundles/ng2-alfresco-viewer.js", "repository": { @@ -45,7 +45,6 @@ "@angular/platform-browser": "~4.0.0", "@angular/platform-browser-dynamic": "~4.0.0", "@angular/router": "~4.0.0", - "@angular/material": "2.0.0-beta.6", "alfresco-js-api": "~1.5.0", "core-js": "2.4.1", diff --git a/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.css b/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.css index 4dd06b5211..3f4bb8d0d4 100644 --- a/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.css +++ b/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.css @@ -1,23 +1,48 @@ -.viewer-download-text { - text-align: center; - word-wrap: break-word; -} - -.viewer-margin-cloud-download{ - margin-right: 20px; +.unsupported-container { + width: 600px; } .viewer-margin { margin: auto !important; -} - -.center-element { + padding: 16px; display: flex; align-items: center; justify-content: center; } -.full_width{ - width :95% !important; +.viewer-download-text { + 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; } diff --git a/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.html b/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.html index e99f9e1d42..7372b57320 100644 --- a/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.html +++ b/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.html @@ -1,12 +1,21 @@ -
+
-
+

File '{{nameFile}}' is of an unsupported format

-
- + +
+ + diff --git a/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.spec.ts b/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.spec.ts index d0b62c27b5..872fa8967b 100644 --- a/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.spec.ts +++ b/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.spec.ts @@ -17,34 +17,59 @@ import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { NotSupportedFormat } from './notSupportedFormat.component'; +import { PdfViewerComponent } from './pdfViewer.component'; import { DebugElement } from '@angular/core'; +import { MdIconModule, MdButtonModule, MdProgressSpinnerModule } from '@angular/material'; +import { Subject } from 'rxjs'; import { AlfrescoAuthenticationService, AlfrescoSettingsService, - AlfrescoApiService, CoreModule, - ContentService + ContentService, + AlfrescoApiService, + LogService, + RenditionsService } from 'ng2-alfresco-core'; +type RenditionResponse = { + entry: { + status: string + } +}; + describe('Test ng2-alfresco-viewer Not Supported Format View component', () => { + const nodeId = 'not-supported-node-id'; + let component: NotSupportedFormat; let service: ContentService; let fixture: ComponentFixture; let debug: DebugElement; let element: HTMLElement; + let renditionsService: RenditionsService; + + let renditionSubject: Subject, + conversionSubject: Subject; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ - CoreModule + CoreModule, + MdIconModule, + MdButtonModule, + MdProgressSpinnerModule + ], + declarations: [ + NotSupportedFormat, + PdfViewerComponent ], - declarations: [NotSupportedFormat], providers: [ AlfrescoSettingsService, AlfrescoAuthenticationService, AlfrescoApiService, - ContentService + ContentService, + RenditionsService, + LogService ] }).compileComponents(); })); @@ -55,11 +80,21 @@ describe('Test ng2-alfresco-viewer Not Supported Format View component', () => { debug = fixture.debugElement; element = fixture.nativeElement; component = fixture.componentInstance; - fixture.detectChanges(); + component.nodeId = nodeId; + + renditionSubject = new Subject(); + conversionSubject = new Subject(); + renditionsService = TestBed.get(RenditionsService); + spyOn(renditionsService, 'getRendition').and.returnValue(renditionSubject); + spyOn(renditionsService, 'convert').and.returnValue(conversionSubject); }); describe('View', () => { + beforeEach(() => { + fixture.detectChanges(); + }); + it('should be present Download button', () => { expect(element.querySelector('#viewer-download-button')).not.toBeNull(); }); @@ -69,28 +104,131 @@ describe('Test ng2-alfresco-viewer Not Supported Format View component', () => { fixture.detectChanges(); 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', () => { - it('should call download method if Click on Download button', () => { - spyOn(window, 'open'); - component.urlFile = 'test'; - let downloadButton: any = element.querySelector('#viewer-download-button'); - downloadButton.click(); - - expect(window.open).toHaveBeenCalled(); + beforeEach(() => { + fixture.detectChanges(); }); - it('should call content service download method if Click on Download button', () => { - spyOn(service, 'downloadBlob'); + describe('Download', () => { - component.blobFile = new Blob(); + it('should call download method if Click on Download button', () => { + spyOn(window, 'open'); + component.urlFile = 'test'; - let downloadButton: any = element.querySelector('#viewer-download-button'); - downloadButton.click(); + let downloadButton: any = element.querySelector('#viewer-download-button'); + downloadButton.click(); - expect(service.downloadBlob).toHaveBeenCalled(); + expect(window.open).toHaveBeenCalled(); + }); + + it('should call content service download method if Click on Download button', () => { + spyOn(service, 'downloadBlob'); + + component.blobFile = new Blob(); + + let downloadButton: any = element.querySelector('#viewer-download-button'); + downloadButton.click(); + + 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.'); + }); }); }); }); diff --git a/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.ts b/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.ts index a00bd16440..17be963e2a 100644 --- a/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.ts +++ b/ng2-components/ng2-alfresco-viewer/src/components/notSupportedFormat.component.ts @@ -15,15 +15,18 @@ * limitations under the License. */ -import { Component, Input } from '@angular/core'; -import { ContentService } from 'ng2-alfresco-core'; +import { Component, Input, OnInit } from '@angular/core'; +import { ContentService, RenditionsService } from 'ng2-alfresco-core'; +import { AlfrescoApiService } from 'ng2-alfresco-core'; + +const DEFAULT_CONVERSION_ENCODING = 'pdf'; @Component({ selector: 'not-supported-format', templateUrl: './notSupportedFormat.component.html', styleUrls: ['./notSupportedFormat.component.css'] }) -export class NotSupportedFormat { +export class NotSupportedFormat implements OnInit { @Input() nameFile: string; @@ -34,9 +37,23 @@ export class NotSupportedFormat { @Input() 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 @@ -48,4 +65,56 @@ export class NotSupportedFormat { 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; + } } diff --git a/ng2-components/ng2-alfresco-viewer/src/components/viewer.component.html b/ng2-components/ng2-alfresco-viewer/src/components/viewer.component.html index 68b2d03ef6..66a84c5072 100644 --- a/ng2-components/ng2-alfresco-viewer/src/components/viewer.component.html +++ b/ng2-components/ng2-alfresco-viewer/src/components/viewer.component.html @@ -64,7 +64,13 @@
- + +
diff --git a/ng2-components/ng2-alfresco-viewer/src/components/viewer.component.spec.ts b/ng2-components/ng2-alfresco-viewer/src/components/viewer.component.spec.ts index b6b3364f7d..4fa4e4e2dc 100644 --- a/ng2-components/ng2-alfresco-viewer/src/components/viewer.component.spec.ts +++ b/ng2-components/ng2-alfresco-viewer/src/components/viewer.component.spec.ts @@ -29,7 +29,8 @@ import { AlfrescoAuthenticationService, AlfrescoSettingsService, AlfrescoApiService, - CoreModule + CoreModule, + RenditionsService } from 'ng2-alfresco-core'; declare let jasmine: any; @@ -58,7 +59,8 @@ describe('Test ng2-alfresco-viewer ViewerComponent', () => { AlfrescoSettingsService, AlfrescoAuthenticationService, AlfrescoApiService, - RenderingQueueServices + RenderingQueueServices, + RenditionsService ] }).compileComponents(); })); @@ -74,6 +76,8 @@ describe('Test ng2-alfresco-viewer ViewerComponent', () => { component.showToolbar = true; component.urlFile = 'base/src/assets/fake-test-file.pdf'; + component.mimeType = 'application/pdf'; + component.ngOnChanges(null); 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) => { 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) => { component.urlFile = 'fake-url-file.unsupported'; + component.mimeType = ''; component.ngOnChanges(null).then(() => { fixture.detectChanges(); diff --git a/ng2-components/package.json b/ng2-components/package.json index 9b7a54d799..b9600708fa 100644 --- a/ng2-components/package.json +++ b/ng2-components/package.json @@ -143,7 +143,9 @@ "wsrv": "^0.1.7", "node-sass": "4.5.3", "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", "module": "./index.js",