Integration beetwen Viewer and Activiti Content (#1765)

* Provide a FormService event when you click on the uploaded content

* Add a formContentClick event to the Form component

* Provide a way to pass Blob to viewer

* Provide a way to use the viewer using a Blob
 - Fix the pdf viewer
 - Fix the image viewer
 - Fix the media viewer

* Update the viewer documentation

* Use the ContentService provided by the core

* Fix and improve unit test

* Add unit test blob viewer
This commit is contained in:
Maurizio Vitale
2017-03-27 14:33:07 +01:00
committed by Eugenio Romano
parent 8132f79e97
commit fde08bb573
18 changed files with 283 additions and 67 deletions

View File

@@ -210,10 +210,12 @@ platformBrowserDynamic().bootstrapModule(AppModule);
Attribute | Options | Default | Description | Mandatory
--- | --- | --- | --- | ---
`fileNodeId` | *string* | | node Id of the file to load the file |
`urlFile` | *string* | | If you want laod an external file that not comes from the ECM you can use this Url where to load the file |
`urlFile` | *string* | | If you want load an external file that not comes from the ECM you can use this Url where to load the file |
`urlBlob` | *Blob* | | If you want load a Blob File |
`overlayMode` | *boolean* | `false` | if true Show the Viewer full page over the present content otherwise will fit the parent div |
`showViewer` | *boolean* | `true` | Hide or show the viewer |
`showToolbar` | *boolean* | `true` | Hide or show the toolbars |
`displayName` | *string* | | You can specify the name of the file|
#### Supported file formats

View File

@@ -1,8 +1,7 @@
<div class="viewer-image-content">
<div class="viewer-image-row">
<div class="viewer-image-cell">
<img id="viewer-image" src="{{urlFile}}" alt="{{nameFile}}" class="center-element viewer-image"/>
<img id="viewer-image" [src]="urlFile" [alt]="nameFile" class="center-element viewer-image"/>
</div>
</div>
</div>

View File

@@ -17,21 +17,28 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { ImgViewerComponent } from './imgViewer.component';
import { DebugElement } from '@angular/core';
import { DebugElement, SimpleChange } from '@angular/core';
import {
AlfrescoAuthenticationService,
AlfrescoSettingsService,
AlfrescoApiService,
CoreModule
CoreModule,
ContentService
} from 'ng2-alfresco-core';
describe('Test ng2-alfresco-viewer Img viewer component ', () => {
let component: ImgViewerComponent;
let service: ContentService;
let fixture: ComponentFixture<ImgViewerComponent>;
let debug: DebugElement;
let element: HTMLElement;
function createFakeBlob() {
let data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
return new Blob([data], {type: 'image/png'});
}
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
@@ -44,6 +51,7 @@ describe('Test ng2-alfresco-viewer Img viewer component ', () => {
AlfrescoApiService
]
}).compileComponents();
service = TestBed.get(ContentService);
}));
beforeEach(() => {
@@ -55,17 +63,18 @@ describe('Test ng2-alfresco-viewer Img viewer component ', () => {
fixture.detectChanges();
});
it('If no url is passed should thrown an error', () => {
it('If no url or blob are passed should thrown an error', () => {
let change = new SimpleChange(null, null);
expect(() => {
component.ngOnChanges(null);
}).toThrow(new Error('Attribute urlFile is required'));
component.ngOnChanges({ 'blobFile': change });
}).toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('If url is passed should not thrown an error', () => {
component.urlFile = 'fake-url';
expect(() => {
component.ngOnChanges(null);
}).not.toThrow(new Error('Attribute urlFile is required'));
}).not.toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('The file Name should be present in the alt attribute', () => {
@@ -73,4 +82,15 @@ describe('Test ng2-alfresco-viewer Img viewer component ', () => {
fixture.detectChanges();
expect(element.querySelector('#viewer-image').getAttribute('alt')).toEqual('fake-name');
});
it('If blob is passed should not thrown an error', () => {
let blob = createFakeBlob();
spyOn(service, 'createTrustedUrl').and.returnValue('fake-blob-url');
let change = new SimpleChange(null, blob);
expect(() => {
component.ngOnChanges({ 'blobFile': change });
}).not.toThrow(new Error('Attribute urlFile or blobFile is required'));
expect(component.urlFile).toEqual('fake-blob-url');
});
});

View File

@@ -15,7 +15,8 @@
* limitations under the License.
*/
import { Component, Input } from '@angular/core';
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ContentService } from 'ng2-alfresco-core';
@Component({
moduleId: module.id,
@@ -23,17 +24,27 @@ import { Component, Input } from '@angular/core';
templateUrl: './imgViewer.component.html',
styleUrls: ['./imgViewer.component.css']
})
export class ImgViewerComponent {
export class ImgViewerComponent implements OnChanges {
@Input()
urlFile: string;
@Input()
blobFile: any;
@Input()
nameFile: string;
ngOnChanges(changes) {
if (!this.urlFile) {
throw new Error('Attribute urlFile is required');
constructor(private contentService: ContentService) {}
ngOnChanges(changes: SimpleChanges) {
let blobFile = changes['blobFile'];
if (blobFile && blobFile.currentValue) {
this.urlFile = this.contentService.createTrustedUrl(this.blobFile);
return;
}
if (!this.urlFile && !this.blobFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
}
}

View File

@@ -17,7 +17,9 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { MediaPlayerComponent } from './mediaPlayer.component';
import { DebugElement } from '@angular/core';
import { DebugElement, SimpleChange } from '@angular/core';
import { ContentService } from 'ng2-alfresco-core';
import {
AlfrescoAuthenticationService,
AlfrescoSettingsService,
@@ -28,10 +30,16 @@ import {
describe('Test ng2-alfresco-viewer Media player component ', () => {
let component: MediaPlayerComponent;
let service: ContentService;
let fixture: ComponentFixture<MediaPlayerComponent>;
let debug: DebugElement;
let element: HTMLElement;
function createFakeBlob() {
let data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
return new Blob([data], {type: 'image/png'});
}
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
@@ -44,6 +52,7 @@ describe('Test ng2-alfresco-viewer Media player component ', () => {
AlfrescoApiService
]
}).compileComponents();
service = TestBed.get(ContentService);
}));
beforeEach(() => {
@@ -55,23 +64,35 @@ describe('Test ng2-alfresco-viewer Media player component ', () => {
fixture.detectChanges();
});
it('If no url is passed should thrown an error', () => {
it('If no url or no blob are passed should thrown an error', () => {
let change = new SimpleChange(null, null);
expect(() => {
component.ngOnChanges(null);
}).toThrow(new Error('Attribute urlFile is required'));
component.ngOnChanges({ 'blobFile': change });
}).toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('If url is passed should not thrown an error', () => {
component.urlFile = 'fake-url';
expect(() => {
component.ngOnChanges(null);
}).not.toThrow(new Error('Attribute urlFile is required'));
}).not.toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('If url is passed should not thrown an error', () => {
component.urlFile = 'fake-url';
expect(() => {
component.ngOnChanges(null);
}).not.toThrow(new Error('Attribute urlFile is required'));
}).not.toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('If blob is passed should not thrown an error', () => {
let blob = createFakeBlob();
spyOn(service, 'createTrustedUrl').and.returnValue('fake-blob-url');
let change = new SimpleChange(null, blob);
expect(() => {
component.ngOnChanges({ 'blobFile': change });
}).not.toThrow(new Error('Attribute urlFile or blobFile is required'));
expect(component.urlFile).toEqual('fake-blob-url');
});
});

View File

@@ -15,7 +15,8 @@
* limitations under the License.
*/
import { Component, Input } from '@angular/core';
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { ContentService } from 'ng2-alfresco-core';
@Component({
moduleId: module.id,
@@ -23,21 +24,31 @@ import { Component, Input } from '@angular/core';
templateUrl: './mediaPlayer.component.html',
styleUrls: ['./mediaPlayer.component.css']
})
export class MediaPlayerComponent {
export class MediaPlayerComponent implements OnChanges {
@Input()
urlFile: string;
@Input()
blobFile: Blob;
@Input()
mimeType: string;
@Input()
nameFile: string;
ngOnChanges(changes) {
if (!this.urlFile) {
throw new Error('Attribute urlFile is required');
constructor(private contentService: ContentService ) {}
ngOnChanges(changes: SimpleChanges) {
let blobFile = changes['blobFile'];
if (blobFile && blobFile.currentValue) {
this.urlFile = this.contentService.createTrustedUrl(this.blobFile);
return;
}
if (!this.urlFile && !this.blobFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
}
}

View File

@@ -49,6 +49,24 @@ describe('Test ng2-alfresco-viewer PdfViewer component', () => {
}).compileComponents();
}));
function createFakeBlob(): Blob {
let pdfData = atob(
'JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog' +
'IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv' +
'TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K' +
'Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg' +
'L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+' +
'PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u' +
'dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq' +
'Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU' +
'CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu' +
'ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g' +
'CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw' +
'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' +
'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G');
return new Blob([pdfData], {type: 'application/pdf'});
}
beforeEach(() => {
fixture = TestBed.createComponent(PdfViewerComponent);
@@ -57,21 +75,71 @@ describe('Test ng2-alfresco-viewer PdfViewer component', () => {
component = fixture.componentInstance;
component.showToolbar = true;
component.urlFile = 'base/src/assets/fake-test-file.pdf';
fixture.detectChanges();
});
describe('View', () => {
describe('View with url file', () => {
beforeEach(() => {
component.urlFile = 'base/src/assets/fake-test-file.pdf';
fixture.detectChanges();
});
it('If urlfile is not present should not be thrown any error ', () => {
it('If urlfile is not present should thrown an error ', () => {
component.urlFile = undefined;
fixture.detectChanges();
expect(() => {
component.ngOnChanges(null);
}).toThrow();
}).toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('Canvas should be present', () => {
expect(element.querySelector('#viewer-viewerPdf')).not.toBeNull();
expect(element.querySelector('#viewer-pdf-container')).not.toBeNull();
});
it('Loader should be present', () => {
expect(element.querySelector('#loader-container')).not.toBeNull();
});
it('Next an Previous Buttons should be present', () => {
expect(element.querySelector('#viewer-previous-page-button')).not.toBeNull();
expect(element.querySelector('#viewer-next-page-button')).not.toBeNull();
});
it('Input Page elements should be present', () => {
expect(element.querySelector('#viewer-pagenumber-input')).toBeDefined();
expect(element.querySelector('#viewer-total-pages')).toBeDefined();
expect(element.querySelector('#viewer-previous-page-button')).not.toBeNull();
expect(element.querySelector('#viewer-next-page-button')).not.toBeNull();
});
it('Toolbar should be hide if showToolbar is false', () => {
component.showToolbar = false;
fixture.detectChanges();
expect(element.querySelector('#viewer-toolbar-command')).toBeNull();
expect(element.querySelector('#viewer-toolbar-pagination')).toBeNull();
});
});
describe('View with blob file', () => {
beforeEach(() => {
component.urlFile = undefined;
component.blobFile = createFakeBlob();
fixture.detectChanges();
});
it('If blobFile is not present should thrown an error ', () => {
component.blobFile = undefined;
expect(() => {
component.ngOnChanges(null);
}).toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('Canvas should be present', () => {
@@ -109,6 +177,8 @@ describe('Test ng2-alfresco-viewer PdfViewer component', () => {
describe('User interaction', () => {
beforeEach(() => {
component.urlFile = 'base/src/assets/fake-test-file.pdf';
fixture.detectChanges();
component.inputPage('1');
});
@@ -244,6 +314,10 @@ describe('Test ng2-alfresco-viewer PdfViewer component', () => {
});
describe('Resize interaction', () => {
beforeEach(() => {
component.urlFile = 'base/src/assets/fake-test-file.pdf';
component.inputPage('1');
});
it('resize event should trigger setScaleUpdatePages', (done) => {
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
@@ -256,6 +330,10 @@ describe('Test ng2-alfresco-viewer PdfViewer component', () => {
});
describe('scroll interaction', () => {
beforeEach(() => {
component.urlFile = 'base/src/assets/fake-test-file.pdf';
fixture.detectChanges();
});
it('scroll page should return the current page', (done) => {
component.ngOnChanges(null).then(() => {
fixture.detectChanges();

View File

@@ -33,6 +33,9 @@ export class PdfViewerComponent {
@Input()
urlFile: string;
@Input()
blobFile: Blob;
@Input()
nameFile: string;
@@ -58,40 +61,52 @@ export class PdfViewerComponent {
}
ngOnChanges(changes) {
if (!this.urlFile) {
throw new Error('Attribute urlFile is required');
if (!this.urlFile && !this.blobFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
if (this.urlFile) {
return new Promise((resolve, reject) => {
let loadingTask = this.getPDFJS().getDocument(this.urlFile);
loadingTask.onProgress = (progressData) => {
let level = progressData.loaded / progressData.total;
this.laodingPercent = Math.round(level * 100);
this.executePdf(this.urlFile, resolve, reject);
});
} else {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = () => {
this.executePdf(reader.result, resolve, reject);
};
loadingTask.then((pdfDocument) => {
this.currentPdfDocument = pdfDocument;
this.totalPages = pdfDocument.numPages;
this.page = 1;
this.displayPage = 1;
this.initPDFViewer(this.currentPdfDocument);
this.currentPdfDocument.getPage(1).then(() => {
this.scalePage('auto');
resolve();
}, (error) => {
reject(error);
});
}, (error) => {
reject(error);
});
reader.readAsArrayBuffer(this.blobFile);
});
}
}
executePdf(src, resolve, reject) {
let loadingTask = this.getPDFJS().getDocument(src);
loadingTask.onProgress = (progressData) => {
let level = progressData.loaded / progressData.total;
this.laodingPercent = Math.round(level * 100);
};
loadingTask.then((pdfDocument) => {
this.currentPdfDocument = pdfDocument;
this.totalPages = pdfDocument.numPages;
this.page = 1;
this.displayPage = 1;
this.initPDFViewer(this.currentPdfDocument);
this.currentPdfDocument.getPage(1).then(() => {
this.scalePage('auto');
resolve();
}, (error) => {
reject(error);
});
}, (error) => {
reject(error);
});
}
/**
* return the PDFJS global object (exist to facilitate the mock of PDFJS in the test)
*

View File

@@ -41,14 +41,14 @@
<!-- Start View Switch-->
<div *ngIf="isPdf()">
<pdf-viewer [showToolbar]="showToolbar" [urlFile]="urlFileContent"
<pdf-viewer [showToolbar]="showToolbar" [blobFile]="blobFile" [urlFile]="urlFileContent"
[nameFile]="displayName"></pdf-viewer>
</div>
<div class="center-element" *ngIf="isImage()">
<img-viewer [urlFile]="urlFileContent" [nameFile]="displayName"></img-viewer>
<img-viewer [urlFile]="urlFileContent" [nameFile]="displayName" [blobFile]="blobFile"></img-viewer>
</div>
<div class="center-element" *ngIf="isMedia()">
<media-player [urlFile]="urlFileContent" [mimeType]="mimeType"
<media-player [urlFile]="urlFileContent" [mimeType]="mimeType" [blobFile]="blobFile"
[nameFile]="displayName"></media-player>
</div>

View File

@@ -31,6 +31,9 @@ export class ViewerComponent {
@Input()
urlFile: string = '';
@Input()
blobFile: Blob;
@Input()
fileNodeId: string = null;
@@ -43,6 +46,9 @@ export class ViewerComponent {
@Input()
showToolbar: boolean = true;
@Input()
displayName: string;
@Output()
showViewerChange: EventEmitter<boolean> = new EventEmitter<boolean>();
@@ -55,7 +61,6 @@ export class ViewerComponent {
urlFileContent: string;
otherMenu: any;
displayName: string;
extension: string;
mimeType: string;
loaded: boolean = false;
@@ -70,12 +75,16 @@ export class ViewerComponent {
if (this.showViewer) {
this.hideOtherHeaderBar();
this.blockOtherScrollBar();
if (!this.urlFile && !this.fileNodeId) {
throw new Error('Attribute urlFile or fileNodeId is required');
if (!this.urlFile && !this.blobFile && !this.fileNodeId) {
throw new Error('Attribute urlFile or fileNodeId or blobFile is required');
}
return new Promise((resolve, reject) => {
let alfrescoApi = this.apiService.getInstance();
if (this.urlFile) {
if (this.blobFile) {
this.mimeType = this.blobFile.type;
this.extensionChange.emit(this.mimeType);
resolve();
} else if (this.urlFile) {
let filenameFromUrl = this.getFilenameFromUrl(this.urlFile);
this.displayName = filenameFromUrl ? filenameFromUrl : '';
this.extension = this.getFileExtension(filenameFromUrl);