New packages org (#2639)

New packages org
This commit is contained in:
Eugenio Romano
2017-11-16 14:12:52 +00:00
committed by GitHub
parent 6a24c6ef75
commit a52bb5600a
1984 changed files with 17179 additions and 40423 deletions

View File

@@ -0,0 +1,3 @@
<div class="image-container">
<img id="viewer-image" [src]="urlFile" [alt]="nameFile" />
</div>

View File

@@ -0,0 +1,14 @@
.adf-img-viewer {
.image-container {
display: flex;
flex: 1;
text-align: center;
flex-direction: row;
justify-content: center;
height: 90vh;
img {
width: 100%;
object-fit: contain;
}
}
}

View File

@@ -0,0 +1,93 @@
/*!
* @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 { DebugElement, SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {
AlfrescoApiService,
AuthenticationService,
ContentService,
SettingsService
} from '../../services';
import { ImgViewerComponent } from './imgViewer.component';
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({
declarations: [ImgViewerComponent],
providers: [
SettingsService,
AuthenticationService,
AlfrescoApiService
]
}).compileComponents();
service = TestBed.get(ContentService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(ImgViewerComponent);
debug = fixture.debugElement;
element = fixture.nativeElement;
component = fixture.componentInstance;
fixture.detectChanges();
});
it('If no url or blob are passed should thrown an error', () => {
let change = new SimpleChange(null, null, true);
expect(() => {
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 or blobFile is required'));
});
it('The file Name should be present in the alt attribute', () => {
component.nameFile = 'fake-name';
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, true);
expect(() => {
component.ngOnChanges({ 'blobFile': change });
}).not.toThrow(new Error('Attribute urlFile or blobFile is required'));
expect(component.urlFile).toEqual('fake-blob-url');
});
});

View File

@@ -0,0 +1,51 @@
/*!
* @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, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { ContentService } from '../../services';
@Component({
selector: 'adf-img-viewer',
templateUrl: './imgViewer.component.html',
styleUrls: ['./imgViewer.component.scss'],
host: { 'class': 'adf-img-viewer' },
encapsulation: ViewEncapsulation.None
})
export class ImgViewerComponent implements OnChanges {
@Input()
urlFile: string;
@Input()
blobFile: Blob;
@Input()
nameFile: string;
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

@@ -0,0 +1,3 @@
<video controls>
<source [src]="urlFile" [type]="mimeType" />
</video>

View File

@@ -0,0 +1,7 @@
.adf-media-player {
video {
display: flex;
flex: 1;
max-height: 90vh;
}
}

View File

@@ -0,0 +1,95 @@
/*!
* @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 { DebugElement, SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MediaPlayerComponent } from './mediaPlayer.component';
import {
AlfrescoApiService,
AuthenticationService,
ContentService,
SettingsService
} from '../../services';
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({
declarations: [MediaPlayerComponent],
providers: [
SettingsService,
AuthenticationService,
AlfrescoApiService
]
}).compileComponents();
service = TestBed.get(ContentService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(MediaPlayerComponent);
debug = fixture.debugElement;
element = fixture.nativeElement;
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should thrown an error If no url or no blob are passed', () => {
let change = new SimpleChange(null, null, true);
expect(() => {
component.ngOnChanges({ 'blobFile': change });
}).toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('should not thrown an error If url is passed', () => {
component.urlFile = 'fake-url';
expect(() => {
component.ngOnChanges(null);
}).not.toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('should not thrown an error If url is passed', () => {
component.urlFile = 'fake-url';
expect(() => {
component.ngOnChanges(null);
}).not.toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('should not thrown an error If blob is passed', () => {
let blob = createFakeBlob();
spyOn(service, 'createTrustedUrl').and.returnValue('fake-blob-url');
let change = new SimpleChange(null, blob, true);
expect(() => {
component.ngOnChanges({ 'blobFile': change });
}).not.toThrow(new Error('Attribute urlFile or blobFile is required'));
expect(component.urlFile).toEqual('fake-blob-url');
});
});

View File

@@ -0,0 +1,55 @@
/*!
* @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, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { ContentService } from '../../services';
@Component({
selector: 'adf-media-player',
templateUrl: './mediaPlayer.component.html',
styleUrls: ['./mediaPlayer.component.scss'],
host: { 'class': 'adf-media-player' },
encapsulation: ViewEncapsulation.None
})
export class MediaPlayerComponent implements OnChanges {
@Input()
urlFile: string;
@Input()
blobFile: Blob;
@Input()
mimeType: string;
@Input()
nameFile: string;
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

@@ -0,0 +1,75 @@
<div id="viewer-pdf-container" class="viewer-pdf-container" (window:resize)="onResize()">
<div id="viewer-viewerPdf" class="pdfViewer">
<div id="loader-container" class="loader-container">
<div class="loader-item">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div >
</div>
</div>
</div>
<div class="adf-pdf-viewer__toolbar" *ngIf="showToolbar">
<adf-toolbar>
<ng-container *ngIf="allowThumbnails">
<button mat-icon-button>
<mat-icon>dashboard</mat-icon>
</button>
<adf-toolbar-divider></adf-toolbar-divider>
</ng-container>
<button
id="viewer-previous-page-button"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.PREVIOUS_PAGE' | translate }}"
mat-icon-button
(click)="previousPage()">
<mat-icon>keyboard_arrow_up</mat-icon>
</button>
<button
id="viewer-next-page-button"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.NEXT_PAGE' | translate }}"
mat-icon-button
(click)="nextPage()">
<mat-icon>keyboard_arrow_down</mat-icon>
</button>
<div>
{{ 'ADF_VIEWER.PAGE_LABEL.SHOWING' | translate }}
<input #page
class="adf-pdf-viewer__toolbar-page-selector"
type="text"
pattern="-?[0-9]*(\.[0-9]+)?"
value="{{ displayPage }}"
(keyup.enter)="inputPage(page.value)">
{{ 'ADF_VIEWER.PAGE_LABEL.OF' | translate }} {{ totalPages }}
</div>
<adf-toolbar-divider></adf-toolbar-divider>
<button
id="viewer-zoom-in-button"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.ZOOM_IN' | translate }}"
mat-icon-button
(click)="zoomIn()">
<mat-icon>zoom_in</mat-icon>
</button>
<button
id="viewer-zoom-out-button"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.ZOOM_OUT' | translate }}"
mat-icon-button
(click)="zoomOut()">
<mat-icon>zoom_out</mat-icon>
</button>
<button
id="viewer-scale-page-button"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.FIT_PAGE' | translate }}"
mat-icon-button
(click)="pageFit()">
<mat-icon>zoom_out_map</mat-icon>
</button>
</adf-toolbar>
</div>

View File

@@ -0,0 +1,38 @@
.adf-pdf-viewer {
.loader-container {
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
display: -webkit-flex; /* NEW - Chrome */
display: flex; /* NEW, Spec - Opera 12.1, Firefox 20+ */
-webkit-box-flex-direction: row;
-moz-box-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
height:100%;
}
.loader-item {
margin: auto;
max-height:100px;
max-width:300px;
}
&__toolbar {
position: absolute;
bottom: 60px;
left: 50%;
transform: translateX(-50%);
&-page-selector {
font-size: 16px;
padding: 4px 0;
text-align: right;
width: 33px;
margin-right: 4px;
height: 20px;
outline-width: 1px;
outline-color: gray;
}
}
}

View File

@@ -0,0 +1,387 @@
/*!
* @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 { DebugElement } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {
AlfrescoApiService,
AuthenticationService,
SettingsService
} from '../../services';
import { MaterialModule } from '../../material.module';
import { ToolbarModule } from '../../toolbar';
import { EventMock } from '../../mock/event.mock';
import { RenderingQueueServices } from '../services/rendering-queue.services';
import { PdfViewerComponent } from './pdfViewer.component';
declare var require: any;
describe('Test ng2-alfresco-viewer PdfViewer component', () => {
let component: PdfViewerComponent;
let fixture: ComponentFixture<PdfViewerComponent>;
let debug: DebugElement;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ToolbarModule,
MaterialModule
],
declarations: [PdfViewerComponent],
providers: [
SettingsService,
AuthenticationService,
AlfrescoApiService,
RenderingQueueServices
]
}).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);
debug = fixture.debugElement;
element = fixture.nativeElement;
component = fixture.componentInstance;
component.showToolbar = true;
});
describe('View with url file', () => {
beforeEach(() => {
component.urlFile = require('../assets/fake-test-file.pdf');
fixture.detectChanges();
});
it('should thrown an error If urlfile is not present', () => {
component.urlFile = undefined;
fixture.detectChanges();
expect(() => {
component.ngOnChanges(null);
}).toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('should Canvas be present', () => {
expect(element.querySelector('.pdfViewer')).not.toBeNull();
expect(element.querySelector('.viewer-pdf-container')).not.toBeNull();
});
it('should Loader be present', () => {
expect(element.querySelector('.loader-container')).not.toBeNull();
});
it('should Next an Previous Buttons be present', () => {
expect(element.querySelector('#viewer-previous-page-button')).not.toBeNull();
expect(element.querySelector('#viewer-next-page-button')).not.toBeNull();
});
it('should Input Page elements 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('should Toolbar 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('should If blobFile is not present thrown an error ', () => {
component.blobFile = undefined;
expect(() => {
component.ngOnChanges(null);
}).toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('should Canvas be present', () => {
expect(element.querySelector('.pdfViewer')).not.toBeNull();
expect(element.querySelector('.viewer-pdf-container')).not.toBeNull();
});
it('should Loader be present', () => {
expect(element.querySelector('.loader-container')).not.toBeNull();
});
it('should Next an Previous Buttons be present', () => {
expect(element.querySelector('#viewer-previous-page-button')).not.toBeNull();
expect(element.querySelector('#viewer-next-page-button')).not.toBeNull();
});
it('should Input Page elements 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('should Toolbar 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('User interaction', () => {
beforeEach(() => {
component.urlFile = require('../assets/fake-test-file.pdf');
fixture.detectChanges();
component.inputPage('1');
});
it('should Total number of pages be loaded', (done) => {
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.totalPages).toEqual(6);
done();
});
});
}, 5000);
it('should right arrow move to the next page', (done) => {
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.displayPage).toBe(1);
EventMock.keyDown(39);
fixture.detectChanges();
expect(component.displayPage).toBe(2);
done();
});
});
}, 5000);
it('should nextPage move to the next page', (done) => {
let nextPageButton: any = element.querySelector('#viewer-next-page-button');
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.displayPage).toBe(1);
nextPageButton.click();
fixture.detectChanges();
expect(component.displayPage).toBe(2);
done();
});
});
});
it('should left arrow move to the previous page', (done) => {
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.displayPage).toBe(1);
EventMock.keyDown(39);
EventMock.keyDown(39);
EventMock.keyDown(37);
fixture.detectChanges();
expect(component.displayPage).toBe(2);
done();
});
});
});
it('should previous page move to the previous page', (done) => {
let previousPageButton: any = element.querySelector('#viewer-previous-page-button');
let nextPageButton: any = element.querySelector('#viewer-next-page-button');
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.displayPage).toBe(1);
nextPageButton.click();
nextPageButton.click();
previousPageButton.click();
fixture.detectChanges();
expect(component.displayPage).toBe(2);
done();
});
});
});
it('should previous page not move to the previous page if is page 1', (done) => {
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.displayPage).toBe(1);
component.previousPage();
fixture.detectChanges();
expect(component.displayPage).toBe(1);
done();
});
});
});
it('should Input page move to the inserted page', (done) => {
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.displayPage).toBe(1);
component.inputPage('2');
fixture.detectChanges();
expect(component.displayPage).toBe(2);
done();
});
});
});
describe('Zoom', () => {
beforeEach(() => {
component.currentScale = 1;
fixture.detectChanges();
});
it('should zoom in increment the scale value', (done) => {
let zoomInButton: any = element.querySelector('#viewer-zoom-in-button');
component.ngOnChanges(null).then(() => {
let zoomBefore = component.currentScale;
zoomInButton.click();
expect(component.currentScaleMode).toBe('auto');
let currentZoom = component.currentScale;
expect(zoomBefore < currentZoom).toBe(true);
done();
});
});
it('should zoom out decrement the scale value', (done) => {
let zoomOutButton: any = element.querySelector('#viewer-zoom-out-button');
component.ngOnChanges(null).then(() => {
let zoomBefore = component.currentScale;
zoomOutButton.click();
expect(component.currentScaleMode).toBe('auto');
let currentZoom = component.currentScale;
expect(zoomBefore > currentZoom).toBe(true);
done();
});
});
it('should fit-in button toggle page-fit and auto scale mode', (done) => {
let fitPage: any = element.querySelector('#viewer-scale-page-button');
component.ngOnChanges(null).then(() => {
expect(component.currentScaleMode).toBe('auto');
fitPage.click();
expect(component.currentScaleMode).toBe('page-fit');
fitPage.click();
expect(component.currentScaleMode).toBe('auto');
done();
});
}, 5000);
});
});
describe('Resize interaction', () => {
beforeEach(() => {
component.urlFile = require('../assets/fake-test-file.pdf');
fixture.detectChanges();
component.inputPage('1');
});
it('should resize event trigger setScaleUpdatePages', (done) => {
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
spyOn(component, 'onResize');
EventMock.resizeMobileView();
expect(component.onResize).toHaveBeenCalled();
done();
});
});
});
});
describe('scroll interaction', () => {
beforeEach(() => {
component.urlFile = require('../assets/fake-test-file.pdf');
fixture.detectChanges();
});
it('should scroll page return the current page', (done) => {
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.displayPage).toBe(1);
component.inputPage('2');
fixture.detectChanges();
expect(component.displayPage).toBe(2);
let documentContainer = element.querySelector('.viewer-pdf-container');
documentContainer.scrollTop = 100000;
component.watchScroll(documentContainer);
fixture.detectChanges();
expect(component.displayPage).toBe(6);
done();
});
});
});
});
});

View File

@@ -0,0 +1,413 @@
/*!
* @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, HostListener, Input, OnChanges, OnDestroy, ViewEncapsulation } from '@angular/core';
import { LogService } from '../../services';
import { RenderingQueueServices } from '../services/rendering-queue.services';
declare let PDFJS: any;
@Component({
selector: 'adf-pdf-viewer',
templateUrl: './pdfViewer.component.html',
styleUrls: [
'./pdfViewer.component.scss',
'./pdfViewerHost.component.scss'
],
providers: [ RenderingQueueServices ],
host: { 'class': 'adf-pdf-viewer' },
encapsulation: ViewEncapsulation.None
})
export class PdfViewerComponent implements OnChanges, OnDestroy {
@Input()
urlFile: string;
@Input()
blobFile: Blob;
@Input()
nameFile: string;
@Input()
showToolbar: boolean = true;
@Input()
allowThumbnails = false;
currentPdfDocument: any;
page: number;
displayPage: number;
totalPages: number;
loadingPercent: number;
pdfViewer: any;
currentScaleMode: string = 'auto';
currentScale: number;
MAX_AUTO_SCALE: number = 1.25;
DEFAULT_SCALE_DELTA: number = 1.1;
MIN_SCALE: number = 0.25;
MAX_SCALE: number = 10.0;
constructor(private renderingQueueServices: RenderingQueueServices,
private logService: LogService) {
// needed to preserve "this" context when setting as a global document event listener
this.onDocumentScroll = this.onDocumentScroll.bind(this);
}
ngOnChanges(changes) {
if (!this.urlFile && !this.blobFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
if (this.urlFile) {
return new Promise((resolve, reject) => {
this.executePdf(this.urlFile, resolve, reject);
});
} else {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = () => {
this.executePdf(reader.result, resolve, reject);
};
reader.readAsArrayBuffer(this.blobFile);
});
}
}
executePdf(src, resolve, reject) {
let loadingTask = this.getPDFJS().getDocument(src);
loadingTask.onProgress = (progressData) => {
let level = progressData.loaded / progressData.total;
this.loadingPercent = 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)
*
* @returns {PDFJS}
*/
getPDFJS() {
return PDFJS;
}
initPDFViewer(pdfDocument: any) {
PDFJS.verbosity = 1;
PDFJS.disableWorker = false;
let documentContainer = document.getElementById('viewer-pdf-container');
let viewer: any = document.getElementById('viewer-viewerPdf');
window.document.addEventListener('scroll', this.onDocumentScroll, true);
this.pdfViewer = new PDFJS.PDFViewer({
container: documentContainer,
viewer: viewer,
renderingQueue: this.renderingQueueServices
});
this.renderingQueueServices.setViewer(this.pdfViewer);
this.pdfViewer.setDocument(pdfDocument);
}
ngOnDestroy() {
window.document.removeEventListener('scroll', this.onDocumentScroll, true);
}
/**
* Method to scale the page current support implementation
*
* @param {string} scaleMode - new scale mode
*/
scalePage(scaleMode) {
this.currentScaleMode = scaleMode;
if (this.pdfViewer) {
let viewerContainer = document.getElementById('viewer-main-container');
let documentContainer = document.getElementById('viewer-pdf-container');
let widthContainer;
let heigthContainer;
if (viewerContainer && viewerContainer.clientWidth <= documentContainer.clientWidth) {
widthContainer = viewerContainer.clientWidth;
heigthContainer = viewerContainer.clientHeight;
} else {
widthContainer = documentContainer.clientWidth;
heigthContainer = documentContainer.clientHeight;
}
let currentPage = this.pdfViewer._pages[this.pdfViewer._currentPageNumber - 1];
let padding = 20;
let pageWidthScale = (widthContainer - padding) / currentPage.width * currentPage.scale;
let pageHeightScale = (heigthContainer - padding) / currentPage.width * currentPage.scale;
let scale;
switch (this.currentScaleMode) {
case 'page-actual':
scale = 1;
break;
case 'page-width':
scale = pageWidthScale;
break;
case 'page-height':
scale = pageHeightScale;
break;
case 'page-fit':
scale = Math.min(pageWidthScale, pageHeightScale);
break;
case 'auto':
let horizontalScale;
if (this.isLandscape) {
horizontalScale = Math.min(pageHeightScale, pageWidthScale);
} else {
horizontalScale = pageWidthScale;
}
scale = Math.min(this.MAX_AUTO_SCALE, horizontalScale);
break;
default:
this.logService.error('pdfViewSetScale: \'' + scaleMode + '\' is an unknown zoom value.');
return;
}
this.setScaleUpdatePages(scale);
}
}
/**
* Update all the pages with the newScale scale
*
* @param {number} newScale - new scale page
*/
setScaleUpdatePages(newScale: number) {
if (!this.isSameScale(this.currentScale, newScale)) {
this.currentScale = newScale;
this.pdfViewer._pages.forEach(function (currentPage) {
currentPage.update(newScale);
});
this.pdfViewer.update();
}
}
/**
* method to check if the request scale of the page is the same for avoid unuseful re-rendering
*
* @param {number} oldScale - old scale page
* @param {number} newScale - new scale page
*
* @returns {boolean}
*/
isSameScale(oldScale: number, newScale: number) {
return (newScale === oldScale);
}
/**
* method to check if is a land scape view
*
* @param {number} width
* @param {number} height
*
* @returns {boolean}
*/
isLandscape(width: number, height: number) {
return (width > height);
}
/**
* Method triggered when the page is resized
*/
onResize() {
this.scalePage(this.currentScaleMode);
}
/**
* toggle the fit page pdf
*/
pageFit() {
if (this.currentScaleMode !== 'page-fit') {
this.scalePage('page-fit');
} else {
this.scalePage('auto');
}
}
/**
* zoom in page pdf
*
* @param {number} ticks
*/
zoomIn(ticks: number) {
let newScale: any = this.currentScale;
do {
newScale = (newScale * this.DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.ceil(newScale * 10) / 10;
newScale = Math.min(this.MAX_SCALE, newScale);
} while (--ticks > 0 && newScale < this.MAX_SCALE);
this.currentScaleMode = 'auto';
this.setScaleUpdatePages(newScale);
}
/**
* zoom out page pdf
*
* @param {number} ticks
*/
zoomOut(ticks: number) {
let newScale: any = this.currentScale;
do {
newScale = (newScale / this.DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.floor(newScale * 10) / 10;
newScale = Math.max(this.MIN_SCALE, newScale);
} while (--ticks > 0 && newScale > this.MIN_SCALE);
this.currentScaleMode = 'auto';
this.setScaleUpdatePages(newScale);
}
/**
* load the previous page
*/
previousPage() {
if (this.pdfViewer && this.page > 1) {
this.page--;
this.displayPage = this.page;
this.pdfViewer.currentPageNumber = this.page;
}
}
/**
* load the next page
*/
nextPage() {
if (this.pdfViewer && this.page < this.totalPages) {
this.page++;
this.displayPage = this.page;
this.pdfViewer.currentPageNumber = this.page;
}
}
/**
* load the page in input
*
* @param {string} page - page to load
*/
inputPage(page: string) {
let pageInput = parseInt(page, 10);
if (!isNaN(pageInput) && pageInput > 0 && pageInput <= this.totalPages) {
this.page = pageInput;
this.displayPage = this.page;
this.pdfViewer.currentPageNumber = this.page;
} else {
this.displayPage = this.page;
}
}
/**
* Litener Scroll Event
*
* @param {any} target
*/
watchScroll(target) {
let outputPage = this.getVisibleElement(target);
if (outputPage) {
this.page = outputPage.id;
this.displayPage = this.page;
}
}
/**
* find out what elements are visible within a scroll pane
*
* @param {any} target
*
* @returns {Object} page
*/
getVisibleElement(target) {
return this.pdfViewer._pages.find((page) => {
return this.isOnScreen(page, target);
});
}
/**
* check if a page is visible
*
* @param {any} page
* @param {any} target
*
* @returns {boolean}
*/
isOnScreen(page: any, target: any) {
let viewport: any = {};
viewport.top = target.scrollTop;
viewport.bottom = viewport.top + target.scrollHeight;
let bounds: any = {};
bounds.top = page.div.offsetTop;
bounds.bottom = bounds.top + page.viewport.height;
return ((bounds.top <= viewport.bottom) && (bounds.bottom >= viewport.top));
}
/**
* Litener Keyboard Event
* @param {KeyboardEvent} event
*/
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
let key = event.keyCode;
if (key === 39) { // right arrow
this.nextPage();
} else if (key === 37) {// left arrow
this.previousPage();
}
}
onDocumentScroll(event: Event) {
if (event && event.target) {
this.watchScroll(event.target);
}
}
}

View File

@@ -0,0 +1,251 @@
.adf-pdf-viewer {
.textLayer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0.2;
line-height: 1.0;
border: 1px solid gray;
& > div {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
-webkit-transform-origin: 0% 0%;
-moz-transform-origin: 0% 0%;
-o-transform-origin: 0% 0%;
-ms-transform-origin: 0% 0%;
transform-origin: 0% 0%;
}
.highlight {
margin: -1px;
padding: 1px;
background-color: rgb(180, 0, 170);
border-radius: 4px;
&.begin {
border-radius: 4px 0px 0px 4px;
}
&.end {
border-radius: 0px 4px 4px 0px;
}
&.middle {
border-radius: 0px;
}
&.selected {
background-color: rgb(0, 100, 0);
}
}
&::selection { background: rgb(0,0,255); }
&::-moz-selection { background: rgb(0,0,255); }
.endOfContent {
display: block;
position: absolute;
left: 0px;
top: 100%;
right: 0px;
bottom: 0px;
z-index: -1;
cursor: default;
user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
&.active {
top: 0px;
}
}
}
.annotationLayer {
section {
position: absolute;
}
.linkAnnotation {
& > a {
position: absolute;
font-size: 1em;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0 repeat;
&:hover {
opacity: 0.2;
background: #ff0;
box-shadow: 0px 2px 10px #ff0;
}
}
}
.textAnnotation {
img {
position: absolute;
cursor: pointer;
}
}
.popupWrapper {
position: absolute;
width: 20em;
}
.popup {
position: absolute;
z-index: 200;
max-width: 20em;
background-color: #FFFF99;
box-shadow: 0px 2px 5px #333;
border-radius: 2px;
padding: 0.6em;
margin-left: 5px;
cursor: pointer;
word-wrap: break-word;
h1 {
font-size: 1em;
border-bottom: 1px solid #000000;
padding-bottom: 0.2em;
}
p {
padding-top: 0.2em;
}
}
.highlightAnnotation,
.underlineAnnotation,
.squigglyAnnotation,
.strikeoutAnnotation,
.fileAttachmentAnnotation {
cursor: pointer;
}
}
.pdfViewer {
canvasWrapper {
overflow: hidden;
}
.page {
direction: ltr;
width: 816px;
height: 1056px;
margin: 1px auto -8px auto;
position: relative;
overflow: visible;
border: 9px solid transparent;
background-clip: content-box;
background-color: white;
canvas {
margin: 0;
display: block;
}
.loadingIcon {
position: absolute;
display: block;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
}
&.removePageBorders {
.page {
margin: 0px auto 10px auto;
border: none;
}
}
.loadingIcon {
width: 100px;
height: 100px;
left: 50% !important;
top: 50% !important;
margin-top: -50px;
margin-left: -50px;
font-size: 5px;
text-indent: -9999em;
border-top: 1.1em solid rgba(3,0,2, 0.2);
border-right: 1.1em solid rgba(3,0,2, 0.2);
border-bottom: 1.1em solid rgba(3,0,2, 0.2);
border-left: 1.1em solid #030002;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation: load8 1.1s infinite linear;
animation: load8 1.1s infinite linear;
border-radius: 50%;
&:after {
border-radius: 50%;
}
}
}
* {
padding: 0;
margin: 0;
}
.hidden, [hidden] {
display: none !important;
}
}
@-webkit-keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.viewer-pdf-container {
overflow: auto;
-webkit-overflow-scrolling: touch;
position: absolute;
top: 32px;
right: 0;
bottom: 0;
left: 0;
outline: none;
}
html[dir='ltr'] .viewer-pdf-container {
box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05);
}
html[dir='rtl'] .viewer-pdf-container {
box-shadow: inset -1px 0 0 hsla(0,0%,100%,.05);
}

View File

@@ -0,0 +1,3 @@
<pre class="adf-txt-viewer-content">
{{content}}
</pre>

View File

@@ -0,0 +1,7 @@
.adf-txt-viewer {
background-color: white;
width: 100vw;
overflow: hidden;
overflow-y: scroll;
margin-bottom: 42px;
}

View File

@@ -0,0 +1,86 @@
/*!
* @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 { DebugElement, SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {
AlfrescoApiService,
AuthenticationService,
SettingsService
} from '../../services';
import { TxtViewerComponent } from './txtViewer.component';
declare var require: any;
describe('Test ng2-alfresco-viewer Text View component', () => {
let component: TxtViewerComponent;
let fixture: ComponentFixture<TxtViewerComponent>;
let debug: DebugElement;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TxtViewerComponent],
providers: [
SettingsService,
AuthenticationService,
AlfrescoApiService
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TxtViewerComponent);
debug = fixture.debugElement;
element = fixture.nativeElement;
component = fixture.componentInstance;
});
describe('View', () => {
it('Should text container be present with urlfile', (done) => {
fixture.detectChanges();
let urlFile = require('../assets/fake-test-file.txt');
let change = new SimpleChange(null, urlFile, true);
component.ngOnChanges({ 'urlFile': change }).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(element.querySelector('.adf-txt-viewer-content').textContent).toContain('example');
done();
});
});
});
it('Should text container be present with Blob file', (done) => {
let blobFile = new Blob(['text example'], {type: 'text/txt'});
let change = new SimpleChange(null, blobFile, true);
component.ngOnChanges({ 'blobFile': change }).then(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(element.querySelector('.adf-txt-viewer-content').textContent).toContain('example');
done();
});
});
});
});
});

View File

@@ -0,0 +1,87 @@
/*!
* @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 { HttpClient } from '@angular/common/http';
import { Component, Input, OnChanges, ViewEncapsulation } from '@angular/core';
import { SimpleChanges } from '@angular/core';
import 'rxjs/add/operator/toPromise';
@Component({
selector: 'adf-txt-viewer',
templateUrl: './txtViewer.component.html',
styleUrls: ['./txtViewer.component.scss'],
host: { 'class': 'adf-txt-viewer' },
encapsulation: ViewEncapsulation.None
})
export class TxtViewerComponent implements OnChanges {
@Input()
urlFile: any;
@Input()
blobFile: Blob;
content: string;
constructor(private http: HttpClient) {
}
ngOnChanges(changes: SimpleChanges): Promise<any> {
let blobFile = changes['blobFile'];
if (blobFile && blobFile.currentValue) {
return this.readBlob(blobFile.currentValue);
}
let urlFile = changes['urlFile'];
if (urlFile && urlFile.currentValue) {
return this.getUrlContent(urlFile.currentValue);
}
if (!this.urlFile && !this.blobFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
}
private getUrlContent(url: string): Promise<any> {
return new Promise((resolve, reject) => {
this.http.get(url, { responseType: 'text' }).subscribe(res => {
this.content = res;
resolve();
}, (event) => {
reject(event);
});
});
}
private readBlob(blob: Blob): Promise<any> {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = () => {
this.content = reader.result;
resolve();
};
reader.onerror = (error: ErrorEvent) => {
reject(error);
};
reader.readAsText(blob);
});
}
}

View File

@@ -0,0 +1,6 @@
<div class="adf-viewer__unknown-format-view">
<div>
<mat-icon class="icon">wifi_tethering</mat-icon>
<div class="label">{{ 'ADF_VIEWER.UNKNOWN_FORMAT' | translate }}</div>
</div>
</div>

View File

@@ -0,0 +1,8 @@
.adf-viewer__unknown-format-view {
height: 90vh;
text-align: center;
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
}

View File

@@ -0,0 +1,26 @@
/*!
* @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 } from '@angular/core';
@Component({
selector: 'adf-viewer-unknown-format',
templateUrl: 'unknown-format.component.html',
styleUrls: ['unknown-format.component.scss']
})
export class UnknownFormatComponent {
}

View File

@@ -0,0 +1,28 @@
/*!
* @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 { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'adf-viewer-more-actions',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'adf-viewer-more-actions' },
template: `<ng-content></ng-content>`
})
export class ViewerMoreActionsComponent {
}

View File

@@ -0,0 +1,28 @@
/*!
* @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 { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'adf-viewer-open-with',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'adf-viewer-open-with' },
template: `<ng-content></ng-content>`
})
export class ViewerOpenWithComponent {
}

View File

@@ -0,0 +1,28 @@
/*!
* @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 { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'adf-viewer-sidebar',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'adf-viewer-sidebar' },
template: `<ng-content></ng-content>`
})
export class ViewerSidebarComponent {
}

View File

@@ -0,0 +1,28 @@
/*!
* @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 { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'adf-viewer-toolbar',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'adf-viewer-toolbar' },
template: `<ng-content></ng-content>`
})
export class ViewerToolbarComponent {
}

View File

@@ -0,0 +1,161 @@
<div *ngIf="showViewer"
class="adf-viewer-container"
[class.adf-viewer-overlay-container]="overlayMode"
[class.adf-viewer-inline-container]="!overlayMode">
<div class="adf-viewer-content">
<ng-content select="adf-viewer-toolbar"></ng-content>
<ng-container *ngIf="showToolbar && !toolbar">
<adf-toolbar color="default" class="adf-viewer-toolbar">
<adf-toolbar-title>
<button *ngIf="allowGoBack"
class="adf-viewer-close-button"
data-automation-id="toolbar-back"
mat-icon-button
matTooltip="{{ 'ADF_VIEWER.ACTIONS.BACK' | translate }}"
(click)="onBackButtonClick()">
<mat-icon>arrow_back</mat-icon>
</button>
<img class="adf-viewer__mimeicon" [src]="mimeType | adfMimeTypeIcon">
<span id="adf-viewer-display-name">{{ displayName }}</span>
</adf-toolbar-title>
<ng-container *ngIf="mnuOpenWith">
<button
mat-button
[matMenuTriggerFor]="mnuOpenWith"
data-automation-id="toolbar-open-with">
<span>{{ 'ADF_VIEWER.ACTIONS.OPEN_WITH' | translate }}</span>
<mat-icon>arrow_drop_down</mat-icon>
</button>
<mat-menu #mnuOpenWith="matMenu" [overlapTrigger]="false">
<ng-content select="adf-viewer-open-with"></ng-content>
</mat-menu>
</ng-container>
<adf-toolbar-divider></adf-toolbar-divider>
<button
*ngIf="allowDownload"
mat-icon-button
matTooltip="{{ 'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate }}"
data-automation-id="toolbar-download"
(click)="downloadContent()">
<mat-icon>file_download</mat-icon>
</button>
<button
*ngIf="allowPrint"
mat-icon-button
matTooltip="{{ 'ADF_VIEWER.ACTIONS.PRINT' | translate }}"
data-automation-id="toolbar-print"
(click)="printContent()">
<mat-icon>print</mat-icon>
</button>
<button
*ngIf="allowShare"
mat-icon-button
matTooltip="{{ 'ADF_VIEWER.ACTIONS.SHARE' | translate }}"
data-automation-id="toolbar-share"
(click)="shareContent()">
<mat-icon>share</mat-icon>
</button>
<ng-container *ngIf="mnuMoreActions">
<button
mat-icon-button
[matMenuTriggerFor]="mnuMoreActions"
matTooltip="{{ 'ADF_VIEWER.ACTIONS.MORE_ACTIONS' | translate }}"
data-automation-id="toolbar-more-actions">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #mnuMoreActions="matMenu" [overlapTrigger]="false">
<ng-content select="adf-viewer-more-actions"></ng-content>
</mat-menu>
</ng-container>
<ng-container *ngIf="allowSidebar">
<adf-toolbar-divider></adf-toolbar-divider>
<button
mat-icon-button
matTooltip="{{ 'ADF_VIEWER.ACTIONS.INFO' | translate }}"
data-automation-id="toolbar-sidebar"
[color]="showSidebar ? 'accent' : 'default'"
(click)="showSidebar = !showSidebar">
<mat-icon>info_outline</mat-icon>
</button>
</ng-container>
</adf-toolbar>
</ng-container>
<ng-container *ngIf="isLoading">
<div class="adf-viewer__loading-screen">
<h2>{{ 'ADF_VIEWER.LOADING' | translate }}</h2>
<div>
<mat-spinner></mat-spinner>
</div>
</div>
</ng-container>
<div *ngIf="!isLoading" class="adf-viewer-layout">
<ng-container *ngIf="showSidebar && sidebarPosition === 'left'">
<div class="adf-viewer__sidebar adf-viewer__sidebar-left">
<ng-content select="adf-viewer-sidebar"></ng-content>
<ng-container *ngIf="!sidebar">
<!-- todo: default info drawer -->
</ng-container>
</div>
</ng-container>
<div class="adf-viewer-layout-content">
<div class="adf-viewer-content-container" [ngSwitch]="viewerType">
<ng-container *ngSwitchCase="'pdf'">
<adf-pdf-viewer [blobFile]="blobFile" [urlFile]="urlFileContent" [nameFile]="displayName"></adf-pdf-viewer>
</ng-container>
<ng-container *ngSwitchCase="'image'">
<adf-img-viewer [urlFile]="urlFileContent" [nameFile]="displayName" [blobFile]="blobFile"></adf-img-viewer>
</ng-container>
<ng-container *ngSwitchCase="'media'">
<adf-media-player [urlFile]="urlFileContent" [mimeType]="mimeType" [blobFile]="blobFile" [nameFile]="displayName"></adf-media-player>
</ng-container>
<ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFileContent" [blobFile]="blobFile"></adf-txt-viewer>
</ng-container>
<ng-container *ngSwitchCase="'custom'">
<span *ngFor="let extensionTemplate of extensionTemplates">
<ng-template
*ngIf="extensionTemplate.isVisible"
[ngTemplateOutlet]="extensionTemplate.template"
[ngTemplateOutletContext]="{ urlFileContent: urlFileContent, extension:extension }">
</ng-template>
</span>
</ng-container>
<ng-container *ngSwitchDefault>
<adf-viewer-unknown-format></adf-viewer-unknown-format>
</ng-container>
</div>
</div>
<ng-container *ngIf="showSidebar && sidebarPosition !== 'left'">
<div class="adf-viewer__sidebar adf-viewer__sidebar-right">
<ng-content select="adf-viewer-sidebar"></ng-content>
<ng-container *ngIf="!sidebar">
<!-- todo: default info drawer -->
</ng-container>
</div>
</ng-container>
</div>
</div>
</div>

View File

@@ -0,0 +1,95 @@
$adf-viewer-background-color: #f5f5f5;
@mixin full-screen() {
width: 100%;
height: 100%;
background-color: $adf-viewer-background-color;
}
.adf-viewer {
&__mimeicon {
vertical-align: middle;
}
&-container {
.adf-viewer-layout-content {
@include full-screen();
position: relative;
overflow-y: auto;
overflow-x: hidden;
z-index: 1;
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex: 1;
& > div {
display: flex;
flex-flow: row wrap;
margin: 0 auto;
align-items: stretch;
}
}
.adf-viewer-layout {
@include full-screen();
display: flex;
flex-direction: row;
overflow-y: auto;
overflow-x: hidden;
position: relative;
}
.adf-viewer-content {
@include full-screen();
flex: 1;
}
}
&-overlay-container {
.adf-viewer-content {
position: fixed;
top: 0px;
left: 0px;
z-index: 1000;
}
}
&-inline-container {
@include full-screen();
}
&-content-container {
display: flex;
justify-content: center;
}
&-unknown-content {
align-items: center;
display: flex;
}
&__loading-screen {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 85vh;
.mat-spinner {
margin: 0 auto;
}
}
&__sidebar {
width: 350px;
display: block;
padding: 8px 0;
background-color: #fafafa;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.27);
border-left: 1px solid rgba(0, 0, 0, 0.07);
}
}

View File

@@ -0,0 +1,699 @@
/*!
* @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 { Location } from '@angular/common';
import { SpyLocation } from '@angular/common/testing';
import { Component, DebugElement } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AlfrescoApiService, RenditionsService } from '../../services';
import { MaterialModule } from './../../material.module';
import { ToolbarModule } from '../../toolbar';
import { Observable } from 'rxjs/Rx';
import { EventMock } from '../../mock/event.mock';
import { RenderingQueueServices } from '../services/rendering-queue.services';
import { ImgViewerComponent } from './imgViewer.component';
import { MediaPlayerComponent } from './mediaPlayer.component';
import { PdfViewerComponent } from './pdfViewer.component';
import { TxtViewerComponent } from './txtViewer.component';
import { UnknownFormatComponent } from './unknown-format/unknown-format.component';
import { ViewerMoreActionsComponent } from './viewer-more-actions.component';
import { ViewerOpenWithComponent } from './viewer-open-with.component';
import { ViewerSidebarComponent } from './viewer-sidebar.component';
import { ViewerToolbarComponent } from './viewer-toolbar.component';
import { ViewerComponent } from './viewer.component';
declare let jasmine: any;
@Component({
selector: 'adf-viewer-container-toolbar',
template: `
<adf-viewer>
<adf-viewer-toolbar>
<div class="custom-toolbar-element"></div>
</adf-viewer-toolbar>
</adf-viewer>
`
})
class ViewerWithCustomToolbarComponent {}
@Component({
selector: 'adf-viewer-container-sidebar',
template: `
<adf-viewer>
<adf-viewer-sidebar>
<div class="custom-sidebar"></div>
</adf-viewer-sidebar>
</adf-viewer>
`
})
class ViewerWithCustomSidebarComponent {}
@Component({
selector: 'adf-viewer-container-open-with',
template: `
<adf-viewer>
<adf-viewer-open-with>
<button mat-menu-item>
<mat-icon>dialpad</mat-icon>
<span>Option 1</span>
</button>
<button mat-menu-item disabled>
<mat-icon>voicemail</mat-icon>
<span>Option 2</span>
</button>
<button mat-menu-item>
<mat-icon>notifications_off</mat-icon>
<span>Option 3</span>
</button>
</adf-viewer-open-with>
</adf-viewer>
`
})
class ViewerWithCustomOpenWithComponent {}
@Component({
selector: 'adf-viewer-container-more-actions',
template: `
<adf-viewer>
<adf-viewer-more-actions>
<button mat-menu-item>
<mat-icon>dialpad</mat-icon>
<span>Action One</span>
</button>
<button mat-menu-item disabled>
<mat-icon>voicemail</mat-icon>
<span>Action Two</span>
</button>
<button mat-menu-item>
<mat-icon>notifications_off</mat-icon>
<span>Action Three</span>
</button>
</adf-viewer-more-actions>
</adf-viewer>
`
})
class ViewerWithCustomMoreActionsComponent {}
describe('ViewerComponent', () => {
let component: ViewerComponent;
let fixture: ComponentFixture<ViewerComponent>;
let debug: DebugElement;
let alfrescoApiService: AlfrescoApiService;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ToolbarModule,
MaterialModule
],
declarations: [
ViewerComponent,
PdfViewerComponent,
TxtViewerComponent,
MediaPlayerComponent,
ImgViewerComponent,
UnknownFormatComponent,
ViewerSidebarComponent,
ViewerToolbarComponent,
ViewerOpenWithComponent,
ViewerMoreActionsComponent,
ViewerWithCustomToolbarComponent,
ViewerWithCustomSidebarComponent,
ViewerWithCustomOpenWithComponent,
ViewerWithCustomMoreActionsComponent
],
providers: [
{provide: RenditionsService, useValue: {
getRendition: () => {
return Observable.throw('throwed');
}
}},
AlfrescoApiService,
RenderingQueueServices,
{ provide: Location, useClass: SpyLocation }
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ViewerComponent);
debug = fixture.debugElement;
element = fixture.nativeElement;
component = fixture.componentInstance;
jasmine.Ajax.install();
alfrescoApiService = TestBed.get(AlfrescoApiService);
component.showToolbar = true;
component.urlFile = 'base/src/assets/fake-test-file.pdf';
component.mimeType = 'application/pdf';
component.ngOnChanges(null);
fixture.detectChanges();
});
afterEach(() => {
jasmine.Ajax.uninstall();
});
it('should use custom toolbar', () => {
let customFixture = TestBed.createComponent(ViewerWithCustomToolbarComponent);
let customElement: HTMLElement = customFixture.nativeElement;
customFixture.detectChanges();
expect(customElement.querySelector('.custom-toolbar-element')).toBeDefined();
});
it('should use custom info drawer', () => {
let customFixture = TestBed.createComponent(ViewerWithCustomSidebarComponent);
let customElement: HTMLElement = customFixture.nativeElement;
customFixture.detectChanges();
expect(customElement.querySelector('.custom-info-drawer-element')).toBeDefined();
});
it('should use custom open with menu', () => {
let customFixture = TestBed.createComponent(ViewerWithCustomOpenWithComponent);
let customElement: HTMLElement = customFixture.nativeElement;
customFixture.detectChanges();
expect(customElement.querySelector('.adf-viewer-container-open-with')).toBeDefined();
});
it('should use custom more actions menu', () => {
let customFixture = TestBed.createComponent(ViewerWithCustomMoreActionsComponent);
let customElement: HTMLElement = customFixture.nativeElement;
customFixture.detectChanges();
expect(customElement.querySelector('.adf-viewer-container-more-actions')).toBeDefined();
});
it('should display left sidebar', () => {
component.sidebarPosition = 'left';
fixture.detectChanges();
expect(element.querySelector('.adf-viewer__sidebar-left')).toBeDefined();
});
it('should display right sidebar', () => {
component.sidebarPosition = 'right';
fixture.detectChanges();
expect(element.querySelector('.adf-viewer__sidebar-right')).toBeDefined();
});
it('should display right sidebar as fallback', () => {
component.sidebarPosition = 'unknown-value';
fixture.detectChanges();
expect(element.querySelector('.adf-viewer__sidebar-right')).toBeDefined();
});
describe('Toolbar', () => {
it('should render default download button', () => {
component.allowDownload = true;
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="toolbar-download"]')).toBeDefined();
});
it('should not render default download button', () => {
component.allowDownload = false;
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="toolbar-download"]')).toBeNull();
});
it('should invoke download action with the toolbar button', () => {
component.allowDownload = true;
fixture.detectChanges();
spyOn(component, 'downloadContent').and.stub();
const button: HTMLButtonElement = element.querySelector('[data-automation-id="toolbar-download"]') as HTMLButtonElement;
button.click();
expect(component.downloadContent).toHaveBeenCalled();
});
it('should raise download event with the toolbar button', (done) => {
component.allowDownload = true;
fixture.detectChanges();
component.download.subscribe(e => {
e.preventDefault();
done();
});
const button: HTMLButtonElement = element.querySelector('[data-automation-id="toolbar-download"]') as HTMLButtonElement;
button.click();
});
it('should render default print button', () => {
component.allowPrint = true;
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="toolbar-print"]')).toBeDefined();
});
it('should not render default print button', () => {
component.allowPrint = false;
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="toolbar-print"]')).toBeNull();
});
it('should invoke print action with the toolbar button', () => {
component.allowPrint = true;
fixture.detectChanges();
spyOn(component, 'printContent').and.stub();
const button: HTMLButtonElement = element.querySelector('[data-automation-id="toolbar-print"]') as HTMLButtonElement;
button.click();
expect(component.printContent).toHaveBeenCalled();
});
it('should raise the print event with the toolbar button', (done) => {
component.allowPrint = true;
fixture.detectChanges();
component.print.subscribe(e => {
e.preventDefault();
done();
});
const button: HTMLButtonElement = element.querySelector('[data-automation-id="toolbar-print"]') as HTMLButtonElement;
button.click();
});
it('should render default share button', () => {
component.allowShare = true;
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="toolbar-share"]')).toBeDefined();
});
it('should not render default share button', () => {
component.allowShare = false;
fixture.detectChanges();
expect(element.querySelector('[data-automation-id="toolbar-share"]')).toBeNull();
});
it('should invoke share action with the toolbar button', () => {
component.allowShare = true;
fixture.detectChanges();
spyOn(component, 'shareContent').and.stub();
const button: HTMLButtonElement = element.querySelector('[data-automation-id="toolbar-share"]') as HTMLButtonElement;
button.click();
expect(component.shareContent).toHaveBeenCalled();
});
it('should raise share event iwth the toolbar button', (done) => {
component.allowShare = true;
fixture.detectChanges();
component.share.subscribe(e => {
e.preventDefault();
done();
});
const button: HTMLButtonElement = element.querySelector('[data-automation-id="toolbar-share"]') as HTMLButtonElement;
button.click();
});
});
describe('View', () => {
describe('Overlay mode true', () => {
beforeEach(() => {
component.overlayMode = true;
fixture.detectChanges();
});
it('should header be present if is overlay mode', () => {
expect(element.querySelector('.adf-viewer-toolbar')).not.toBeNull();
});
it('should Name File be present if is overlay mode ', () => {
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('.adf-viewer-filename').innerHTML).toEqual('fake-test-file.pdf');
});
});
it('should Close button be present if overlay mode', () => {
expect(element.querySelector('.adf-viewer-close-button')).not.toBeNull();
});
it('should Click on close button hide the viewer', () => {
let closebutton: any = element.querySelector('.adf-viewer-close-button');
closebutton.click();
fixture.detectChanges();
expect(element.querySelector('.adf-viewer-content')).toBeNull();
});
it('should Esc button hide the viewer', () => {
EventMock.keyDown(27);
fixture.detectChanges();
expect(element.querySelector('.adf-viewer-content')).toBeNull();
});
});
describe('Overlay mode false', () => {
beforeEach(() => {
component.overlayMode = false;
fixture.detectChanges();
});
it('should header be NOT be present if is not overlay mode', () => {
expect(element.querySelector('header')).toBeNull();
});
it('should Esc button not hide the viewer if is not overlay mode', () => {
EventMock.keyDown(27);
fixture.detectChanges();
expect(element.querySelector('.adf-viewer-content')).not.toBeNull();
});
});
});
describe('Attribute', () => {
it('should Url or fileNodeId be mandatory', () => {
component.showViewer = true;
component.fileNodeId = undefined;
component.urlFile = undefined;
expect(() => {
component.ngOnChanges(null);
}).toThrow();
});
it('should FileNodeId present not thrown any error ', () => {
component.showViewer = true;
component.fileNodeId = 'file-node-id';
component.urlFile = undefined;
expect(() => {
component.ngOnChanges(null);
}).not.toThrow();
});
it('should urlFile present not thrown any error ', () => {
component.showViewer = true;
component.fileNodeId = undefined;
expect(() => {
component.ngOnChanges(null);
}).not.toThrow();
});
it('should showViewer default value be true', () => {
expect(component.showViewer).toBe(true);
});
it('should viewer be hide if showViewer value is false', () => {
component.showViewer = false;
fixture.detectChanges();
expect(element.querySelector('.adf-viewer-content')).toBeNull();
});
});
describe('Extension Type Test', () => {
it('should extension file pdf be loaded', (done) => {
component.urlFile = 'base/src/assets/fake-test-file.pdf';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-pdf-viewer')).not.toBeNull();
done();
});
});
it('should extension file png be loaded', (done) => {
component.urlFile = 'fake-url-file.png';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('#viewer-image')).not.toBeNull();
done();
});
});
it('should extension file mp4 be loaded', (done) => {
component.urlFile = 'fake-url-file.mp4';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-media-player')).not.toBeNull();
done();
});
});
it('should extension file mp3 be loaded', (done) => {
component.urlFile = 'fake-url-file.mp3';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-media-player')).not.toBeNull();
done();
});
});
it('should extension file wav be loaded', (done) => {
component.urlFile = 'fake-url-file.wav';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-media-player')).not.toBeNull();
done();
});
});
it('should extension file txt be loaded', (done) => {
component.urlFile = 'fake-url-file.txt';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-txt-viewer')).not.toBeNull();
done();
});
});
it('should display [unknown format] for unsupported extensions', (done) => {
component.urlFile = 'fake-url-file.unsupported';
component.mimeType = '';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-viewer-unknown-format')).toBeDefined();
done();
});
});
});
describe('MimeType handling', () => {
it('should display a PDF file identified by mimetype when the filename has no extension', (done) => {
component.urlFile = 'content';
component.mimeType = 'application/pdf';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-pdf-viewer')).not.toBeNull();
done();
});
});
it('should display a PDF file identified by mimetype when the file extension is wrong', (done) => {
component.urlFile = 'content.bin';
component.mimeType = 'application/pdf';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-pdf-viewer')).not.toBeNull();
done();
});
});
it('should display an image file identified by mimetype when the filename has no extension', (done) => {
component.urlFile = 'content';
component.mimeType = 'image/png';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('#viewer-image')).not.toBeNull();
done();
});
});
it('should display a image file identified by mimetype when the file extension is wrong', (done) => {
component.urlFile = 'content.bin';
component.mimeType = 'image/png';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('#viewer-image')).not.toBeNull();
done();
});
});
it('should display the media player if the file identified by mimetype is a media when the filename has wrong extension', (done) => {
component.urlFile = 'content.bin';
component.mimeType = 'video/mp4';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-media-player')).not.toBeNull();
done();
});
});
it('should display the txt viewer if the file identified by mimetype is a txt when the filename has wrong extension', (done) => {
component.urlFile = 'content.bin';
component.mimeType = 'text/txt';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-txt-viewer')).not.toBeNull();
done();
});
});
it('should display the media player if the file identified by mimetype is a media when the filename has no extension', (done) => {
component.urlFile = 'content';
component.mimeType = 'video/mp4';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('adf-media-player')).not.toBeNull();
done();
});
});
});
describe('Events', () => {
it('should if the extension change extension Change event be fired ', (done) => {
component.extensionChange.subscribe((fileExtension) => {
expect(fileExtension).toEqual('png');
done();
});
component.urlFile = 'fake-url-file.png';
component.ngOnChanges(null);
});
});
describe('display name property override by urlFile', () => {
it('should displayName override the default name if is present and urlFile is set' , (done) => {
component.urlFile = 'base/src/assets/fake-test-file.pdf';
component.displayName = 'test name';
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('#adf-viewer-display-name').textContent).toEqual('test name');
done();
});
});
it('should use the urlFile name if displayName is NOT set and urlFile is set' , (done) => {
component.urlFile = 'base/src/assets/fake-test-file.pdf';
component.displayName = null;
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('#adf-viewer-display-name').textContent).toEqual('fake-test-file.pdf');
done();
});
});
});
describe('display name property override by blobFile', () => {
it('should displayName override the name if is present and blobFile is set' , (done) => {
component.displayName = 'blob file display name';
component.blobFile = new Blob(['This is my blob content'], {type : 'text/plain'});
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('#adf-viewer-display-name').textContent).toEqual('blob file display name');
done();
});
});
it('should show uknownn name if displayName is NOT set and blobFile is set' , (done) => {
component.displayName = null;
component.blobFile = new Blob(['This is my blob content'], {type : 'text/plain'});
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('#adf-viewer-display-name').textContent).toEqual('Unknown');
done();
});
});
});
describe('display name property override by nodeId', () => {
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) },
content: { getContentUrl: () => contentUrl }
};
it('should use the displayName if displayName is set and fileNodeId is set' , (done) => {
const userDefinedDisplayName = 'user defined display name';
component.fileNodeId = '12';
component.urlFile = null;
component.displayName = userDefinedDisplayName;
spyOn(alfrescoApiService, 'getInstance').and.returnValue(alfrescoApiInstanceMock);
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('#adf-viewer-display-name').textContent).toEqual(userDefinedDisplayName);
done();
});
});
it('should use the node name if displayName is NOT set and fileNodeId is set' , (done) => {
component.fileNodeId = '12';
component.urlFile = null;
component.displayName = null;
spyOn(alfrescoApiService, 'getInstance').and.returnValue(alfrescoApiInstanceMock);
component.ngOnChanges(null).then(() => {
fixture.detectChanges();
expect(element.querySelector('#adf-viewer-display-name').textContent).toEqual(displayName);
done();
});
});
});
});

View File

@@ -0,0 +1,441 @@
/*!
* @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 { Location } from '@angular/common';
import {
Component, ContentChild, EventEmitter, HostListener,
Input, OnChanges, OnDestroy, Output, TemplateRef, ViewEncapsulation
} from '@angular/core';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { BaseEvent } from '../../events';
import { AlfrescoApiService, LogService, RenditionsService } from '../../services';
import { ViewerMoreActionsComponent } from './viewer-more-actions.component';
import { ViewerOpenWithComponent } from './viewer-open-with.component';
import { ViewerSidebarComponent } from './viewer-sidebar.component';
import { ViewerToolbarComponent } from './viewer-toolbar.component';
@Component({
selector: 'adf-viewer',
templateUrl: './viewer.component.html',
styleUrls: ['./viewer.component.scss'],
host: { 'class': 'adf-viewer' },
encapsulation: ViewEncapsulation.None
})
export class ViewerComponent implements OnDestroy, OnChanges {
@ContentChild(ViewerToolbarComponent)
toolbar: ViewerToolbarComponent;
@ContentChild(ViewerSidebarComponent)
sidebar: ViewerSidebarComponent;
@ContentChild(ViewerOpenWithComponent)
mnuOpenWith: ViewerOpenWithComponent;
@ContentChild(ViewerMoreActionsComponent)
mnuMoreActions: ViewerMoreActionsComponent;
@Input()
urlFile = '';
@Input()
blobFile: Blob;
@Input()
fileNodeId: string = null;
@Input()
overlayMode = false;
@Input()
showViewer = true;
@Input()
showToolbar = true;
@Input()
displayName: string;
@Input()
allowGoBack = true;
@Input()
allowDownload = true;
@Input()
allowPrint = false;
@Input()
allowShare = false;
@Input()
allowSidebar = false;
@Input()
showSidebar = false;
@Input()
sidebarPosition = 'right';
@Output()
goBack = new EventEmitter<BaseEvent<any>>();
@Output()
download = new EventEmitter<BaseEvent<any>>();
@Output()
print = new EventEmitter<BaseEvent<any>>();
@Output()
share = new EventEmitter<BaseEvent<any>>();
@Output()
showViewerChange = new EventEmitter<boolean>();
@Output()
extensionChange = new EventEmitter<string>();
viewerType = 'unknown';
downloadUrl: string = null;
fileName = 'document';
isLoading = false;
extensionTemplates: { template: TemplateRef<any>, isVisible: boolean }[] = [];
externalExtensions: string[] = [];
urlFileContent: string;
otherMenu: any;
extension: string;
mimeType: string;
private extensions = {
image: ['png', 'jpg', 'jpeg', 'gif', 'bpm'],
media: ['wav', 'mp4', 'mp3', 'webm', 'ogg'],
text: ['txt', 'xml', 'js', 'html', 'json'],
pdf: ['pdf']
};
private mimeTypes = [
{ mimeType: 'application/x-javascript', type: 'text' },
{ mimeType: 'application/pdf', type: 'pdf' }
];
constructor(private apiService: AlfrescoApiService,
private logService: LogService,
private location: Location,
private renditionService: RenditionsService) {
}
ngOnChanges(changes) {
if (this.showViewer) {
if (!this.urlFile && !this.blobFile && !this.fileNodeId) {
throw new Error('Attribute urlFile or fileNodeId or blobFile is required');
}
return new Promise((resolve, reject) => {
if (this.blobFile) {
this.displayName = this.getDisplayName('Unknown');
this.isLoading = true;
this.mimeType = this.blobFile.type;
this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
this.allowDownload = false;
// TODO: wrap blob into the data url and allow downloading
this.extensionChange.emit(this.mimeType);
this.isLoading = false;
this.scrollTop();
resolve();
} else if (this.urlFile) {
this.isLoading = true;
let filenameFromUrl = this.getFilenameFromUrl(this.urlFile);
this.displayName = this.getDisplayName(filenameFromUrl);
this.extension = this.getFileExtension(filenameFromUrl);
this.urlFileContent = this.urlFile;
this.downloadUrl = this.urlFile;
this.fileName = this.displayName;
this.viewerType = this.getViewerTypeByExtension(this.extension);
if (this.viewerType === 'unknown') {
this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
}
this.extensionChange.emit(this.extension);
this.isLoading = false;
this.scrollTop();
resolve();
} else if (this.fileNodeId) {
this.isLoading = true;
this.apiService.getInstance().nodes.getNodeInfo(this.fileNodeId).then(
(data: MinimalNodeEntryEntity) => {
this.mimeType = data.content.mimeType;
this.displayName = this.getDisplayName(data.name);
this.urlFileContent = this.apiService.getInstance().content.getContentUrl(data.id);
this.extension = this.getFileExtension(data.name);
this.fileName = data.name;
this.downloadUrl = this.apiService.getInstance().content.getContentUrl(data.id, true);
this.viewerType = this.getViewerTypeByExtension(this.extension);
if (this.viewerType === 'unknown') {
this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
}
if (this.viewerType === 'unknown') {
this.displayAsPdf(data.id);
} else {
this.isLoading = false;
}
this.extensionChange.emit(this.extension);
this.scrollTop();
resolve();
},
(error) => {
this.isLoading = false;
reject(error);
this.logService.error('This node does not exist');
}
);
}
});
}
}
private getDisplayName(name) {
return this.displayName || name;
}
scrollTop() {
window.scrollTo(0, 1);
}
getViewerTypeByMimeType(mimeType: string) {
if (mimeType) {
mimeType = mimeType.toLowerCase();
if (mimeType.startsWith('image/')) {
return 'image';
}
if (mimeType.startsWith('text/')) {
return 'text';
}
if (mimeType.startsWith('video/')) {
return 'media';
}
if (mimeType.startsWith('audio/')) {
return 'media';
}
const registered = this.mimeTypes.find(t => t.mimeType === mimeType);
if (registered) {
return registered.type;
}
}
return 'unknown';
}
getViewerTypeByExtension(extension: string) {
if (extension) {
extension = extension.toLowerCase();
}
if (this.isCustomViewerExtension(extension)) {
return 'custom';
}
if (this.extensions.image.indexOf(extension) >= 0) {
return 'image';
}
if (this.extensions.media.indexOf(extension) >= 0) {
return 'media';
}
if (this.extensions.text.indexOf(extension) >= 0) {
return 'text';
}
if (this.extensions.pdf.indexOf(extension) >= 0) {
return 'pdf';
}
return 'unknown';
}
onBackButtonClick() {
if (this.overlayMode) {
this.close();
} else {
const event = new BaseEvent<any>();
this.goBack.next(event);
if (!event.defaultPrevented) {
this.location.back();
}
}
}
/**
* close the viewer
*/
close() {
if (this.otherMenu) {
this.otherMenu.hidden = false;
}
this.cleanup();
this.showViewer = false;
this.showViewerChange.emit(this.showViewer);
}
/**
* cleanup before the close
*/
cleanup() {
this.urlFileContent = '';
this.displayName = '';
this.fileNodeId = null;
this.extension = null;
this.mimeType = null;
}
ngOnDestroy() {
this.cleanup();
}
/**
* get File name from url
*
* @param {string} url - url file
* @returns {string} name file
*/
getFilenameFromUrl(url: string): string {
let anchor = url.indexOf('#');
let query = url.indexOf('?');
let end = Math.min(
anchor > 0 ? anchor : url.length,
query > 0 ? query : url.length);
return url.substring(url.lastIndexOf('/', end) + 1, end);
}
/**
* Get the token from the local storage
*
* @param {string} fileName - file name
* @returns {string} file name extension
*/
getFileExtension(fileName: string): string {
return fileName.split('.').pop().toLowerCase();
}
isCustomViewerExtension(extension: string): boolean {
const extensions = this.externalExtensions || [];
if (extension && extensions.length > 0) {
extension = extension.toLowerCase();
return extensions.indexOf(extension) >= 0;
}
return false;
}
/**
* Litener Keyboard Event
* @param {KeyboardEvent} event
*/
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
let key = event.keyCode;
if (key === 27 && this.overlayMode) { // esc
this.close();
}
}
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();
this.print.next(args);
}
}
shareContent() {
if (this.allowShare) {
const args = new BaseEvent();
this.share.next(args);
}
}
private displayAsPdf(nodeId: string) {
this.isLoading = true;
this.renditionService.getRendition(nodeId, 'pdf').subscribe(
(response) => {
const status = response.entry.status.toString();
if (status === 'CREATED') {
this.isLoading = false;
this.showPdfRendition(nodeId);
} else if (status === 'NOT_CREATED') {
this.renditionService.convert(nodeId, 'pdf').subscribe({
complete: () => {
this.isLoading = false;
this.showPdfRendition(nodeId);
},
error: (error) => {
this.isLoading = false;
}
});
} else {
this.isLoading = false;
}
},
(err) => {
this.isLoading = false;
}
);
}
private showPdfRendition(nodeId: string) {
if (nodeId) {
this.viewerType = 'pdf';
this.urlFileContent = this.renditionService.getRenditionUrl(nodeId, 'pdf');
}
}
}