mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-08-07 17:48:54 +00:00
Dev eromano external viewer (#1636)
* add extensibility point to add a custom viewer * documentation and tests * add example in document list
This commit is contained in:
committed by
Mario Romano
parent
e3ce65880f
commit
59cbbcbe4d
@@ -0,0 +1,40 @@
|
||||
.viewer-image-row {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
||||
-webkit-box-orient: horizontal;
|
||||
-moz-box-orient: horizontal;
|
||||
box-orient: horizontal;
|
||||
flex-direction: row;
|
||||
|
||||
-webkit-box-pack: center;
|
||||
-moz-box-pack: center;
|
||||
box-pack: center;
|
||||
justify-content: center;
|
||||
|
||||
-webkit-box-align: center;
|
||||
-moz-box-align: center;
|
||||
box-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.viewer-image-cell {
|
||||
-webkit-box-flex: 1;
|
||||
-moz-box-flex: 1;
|
||||
box-flex: 1;
|
||||
-webkit-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.viewer-image {
|
||||
height: 80vh;
|
||||
max-width:100%;
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<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"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -0,0 +1,76 @@
|
||||
/*!
|
||||
* @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 { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { ImgViewerComponent } from './imgViewer.component';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import {
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
CoreModule
|
||||
} from 'ng2-alfresco-core';
|
||||
|
||||
describe('Test ng2-alfresco-viewer Img viewer component ', () => {
|
||||
|
||||
let component: ImgViewerComponent;
|
||||
let fixture: ComponentFixture<ImgViewerComponent>;
|
||||
let debug: DebugElement;
|
||||
let element: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule
|
||||
],
|
||||
declarations: [ImgViewerComponent],
|
||||
providers: [
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoApiService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ImgViewerComponent);
|
||||
|
||||
debug = fixture.debugElement;
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('If no url is passed should thrown an error', () => {
|
||||
expect(() => {
|
||||
component.ngOnChanges(null);
|
||||
}).toThrow(new Error('Attribute urlFile 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'));
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
@@ -0,0 +1,39 @@
|
||||
/*!
|
||||
* @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 } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'img-viewer',
|
||||
templateUrl: './imgViewer.component.html',
|
||||
styleUrls: ['./imgViewer.component.css']
|
||||
})
|
||||
export class ImgViewerComponent {
|
||||
|
||||
@Input()
|
||||
urlFile: string;
|
||||
|
||||
@Input()
|
||||
nameFile: string;
|
||||
|
||||
ngOnChanges(changes) {
|
||||
if (!this.urlFile) {
|
||||
throw new Error('Attribute urlFile is required');
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
.viewer-video-row {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
||||
-webkit-box-orient: horizontal;
|
||||
-moz-box-orient: horizontal;
|
||||
box-orient: horizontal;
|
||||
flex-direction: row;
|
||||
|
||||
-webkit-box-pack: center;
|
||||
-moz-box-pack: center;
|
||||
box-pack: center;
|
||||
justify-content: center;
|
||||
|
||||
-webkit-box-align: center;
|
||||
-moz-box-align: center;
|
||||
box-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.viewer-video-cell {
|
||||
-webkit-box-flex: 1;
|
||||
-moz-box-flex: 1;
|
||||
box-flex: 1;
|
||||
-webkit-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
video {
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
max-width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
<div class="viewer-video-content">
|
||||
<div class="viewer-video-row">
|
||||
<div class="viewer-video-cell">
|
||||
<video controls >
|
||||
<source [src]="urlFile" [type]="mimeType" />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -0,0 +1,77 @@
|
||||
/*!
|
||||
* @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 { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { MediaPlayerComponent } from './mediaPlayer.component';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import {
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
CoreModule
|
||||
} from 'ng2-alfresco-core';
|
||||
|
||||
describe('Test ng2-alfresco-viewer Media player component ', () => {
|
||||
|
||||
let component: MediaPlayerComponent;
|
||||
let fixture: ComponentFixture<MediaPlayerComponent>;
|
||||
let debug: DebugElement;
|
||||
let element: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule
|
||||
],
|
||||
declarations: [MediaPlayerComponent],
|
||||
providers: [
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoApiService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MediaPlayerComponent);
|
||||
|
||||
debug = fixture.debugElement;
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('If no url is passed should thrown an error', () => {
|
||||
expect(() => {
|
||||
component.ngOnChanges(null);
|
||||
}).toThrow(new Error('Attribute urlFile 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'));
|
||||
});
|
||||
|
||||
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'));
|
||||
});
|
||||
});
|
@@ -0,0 +1,43 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'media-player',
|
||||
templateUrl: './mediaPlayer.component.html',
|
||||
styleUrls: ['./mediaPlayer.component.css']
|
||||
})
|
||||
export class MediaPlayerComponent {
|
||||
|
||||
@Input()
|
||||
urlFile: string;
|
||||
|
||||
@Input()
|
||||
mimeType: string;
|
||||
|
||||
@Input()
|
||||
nameFile: string;
|
||||
|
||||
ngOnChanges(changes) {
|
||||
if (!this.urlFile) {
|
||||
throw new Error('Attribute urlFile is required');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
|
||||
.viewer-download-text {
|
||||
text-align: center;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.viewer-margin-cloud-download{
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.viewer-margin {
|
||||
margin: auto !important;
|
||||
}
|
||||
|
||||
.center-element {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.full_width{
|
||||
width :95% !important;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
<section class="section--center mdl-grid mdl-grid--no-spacing">
|
||||
<div class="viewer-margin mdl-card mdl-cell mdl-cell--9-col-desktop mdl-cell--6-col-tablet mdl-cell--4-col-phone mdl-shadow--2dp">
|
||||
<div class="viewer-download-text mdl-card__supporting-text viewer-margin">
|
||||
<h4>File '<span>{{nameFile}}</span>' is of an unsupported format</h4>
|
||||
</div>
|
||||
<div class="center-element mdl-card__actions">
|
||||
<button id="viewer-download-button" aria-label="Download" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--accent" (click)="download()">
|
||||
<i class="viewer-margin-cloud-download material-icons">cloud_download</i> Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@@ -0,0 +1,81 @@
|
||||
/*!
|
||||
* @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 { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { NotSupportedFormat } from './notSupportedFormat.component';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import {
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
CoreModule
|
||||
} from 'ng2-alfresco-core';
|
||||
|
||||
describe('Test ng2-alfresco-viewer Not Supported Format View component', () => {
|
||||
|
||||
let component: NotSupportedFormat;
|
||||
let fixture: ComponentFixture<NotSupportedFormat>;
|
||||
let debug: DebugElement;
|
||||
let element: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule
|
||||
],
|
||||
declarations: [NotSupportedFormat],
|
||||
providers: [
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoApiService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NotSupportedFormat);
|
||||
|
||||
debug = fixture.debugElement;
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('View', () => {
|
||||
|
||||
it('Download button should be present', () => {
|
||||
expect(element.querySelector('#viewer-download-button')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should display the name of the file', () => {
|
||||
component.nameFile = 'Example Content.xls';
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('h4 span').innerHTML).toEqual('Example Content.xls');
|
||||
});
|
||||
});
|
||||
|
||||
describe('User Interaction', () => {
|
||||
it('Click on Download button should call download method', () => {
|
||||
spyOn(window, 'open');
|
||||
|
||||
let downloadButton: any = element.querySelector('#viewer-download-button');
|
||||
downloadButton.click();
|
||||
|
||||
expect(window.open).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,40 @@
|
||||
/*!
|
||||
* @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 } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'not-supported-format',
|
||||
templateUrl: './notSupportedFormat.component.html',
|
||||
styleUrls: ['./notSupportedFormat.component.css']
|
||||
})
|
||||
export class NotSupportedFormat {
|
||||
|
||||
@Input()
|
||||
nameFile: string;
|
||||
|
||||
@Input()
|
||||
urlFile: string;
|
||||
|
||||
/**
|
||||
* Download file opening it in a new window
|
||||
*/
|
||||
download() {
|
||||
window.open(this.urlFile);
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
.loader-text{
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
color : #fff;
|
||||
}
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.viewer-toolbar-pagination{
|
||||
padding-top: 4px;
|
||||
top: 80px;
|
||||
right:35px;
|
||||
width:auto;
|
||||
position:absolute;
|
||||
border-radius: 10px;
|
||||
background: #3E3E3E;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.viewer-toolbar-command{
|
||||
height: 30px;
|
||||
padding-top: 4px;
|
||||
top: 80px;
|
||||
left:35px;
|
||||
width:auto;
|
||||
position:absolute;
|
||||
border-radius: 10px;
|
||||
background: #3E3E3E;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.viewer-pagenumber-input {
|
||||
border: none;
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
padding: 4px 0;
|
||||
background: 0 0;
|
||||
text-align: right;
|
||||
color: inherit;
|
||||
width: 33px;
|
||||
margin-right: 4px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.viewer-total-pages {
|
||||
border: medium none;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
padding: 4px 0px;
|
||||
background: transparent none repeat scroll 0px 0px;
|
||||
text-align: right;
|
||||
color: inherit;
|
||||
margin-right: 4px;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.viewer-page-counter {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.button-page {
|
||||
margin-right: 4px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-left: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button-page:hover {
|
||||
cursor: pointer;
|
||||
background: grey;
|
||||
border-radius: 24px;
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<!-- Start Pdf Canvas -->
|
||||
<div id="viewer-pdf-container" class="viewer-pdf-container" (keypress)=eventHandler() (window:resize)="onResize($event)">
|
||||
<div id="viewer-viewerPdf" class="pdfViewer">
|
||||
<div id="loader-container" class="loader-container">
|
||||
<div class="loader-item">
|
||||
<div id="loader-spin" class="mdl-progress mdl-js-progress mdl-progress__indeterminate"></div>
|
||||
<div id="loader-text" class="loader-text">Loading <span>{{nameFile}}</span> {{laodingPercent}}%</div>
|
||||
</div >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Pdf Canvas -->
|
||||
|
||||
|
||||
<!-- Pagination toolbar start -->
|
||||
<div *ngIf="showToolbar" id="viewer-toolbar-pagination" class="viewer-toolbar-pagination mdl-cell--hide-tablet mdl-cell--hide-phone">
|
||||
<div id="viewer-previous-page-button" aria-label="arrow left" class="button-page left" (click)="previousPage()">
|
||||
<i class="icon material-icons">keyboard_arrow_left</i>
|
||||
</div>
|
||||
|
||||
<div class="viewer-page-counter left" >
|
||||
<input id="viewer-pagenumber-input" #page
|
||||
(keyup.enter)="inputPage(page.value)" class="viewer-pagenumber-input left" type="text" pattern="-?[0-9]*(\.[0-9]+)?" value="{{displayPage}}">
|
||||
<div id="viewer-total-pages" class="left viewer-total-pages">/ {{totalPages}}</div>
|
||||
</div>
|
||||
|
||||
<div id="viewer-next-page-button" aria-label="arrow right" class="button-page left" (click)="nextPage()" >
|
||||
<i class="icon material-icons" >keyboard_arrow_right</i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Pagination toolbar end -->
|
||||
|
||||
<!-- Command toolbar start -->
|
||||
<div *ngIf="showToolbar" id="viewer-toolbar-command" class="viewer-toolbar-command">
|
||||
<div id="viewer-scale-page-button" aria-label="zoom out map" class="button-page left" (click)="pageFit()">
|
||||
<i class="icon material-icons">zoom_out_map</i>
|
||||
</div>
|
||||
<div id="viewer-zoom-in-button" aria-label="zoom in" class="button-page left" (click)="zoomIn()">
|
||||
<i class="icon material-icons">zoom_in</i>
|
||||
</div>
|
||||
<div id="viewer-zoom-out-button" aria-label="zoom out" class="button-page left" (click)="zoomOut()">
|
||||
<i class="icon material-icons">zoom_out</i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Command toolbar end -->
|
@@ -0,0 +1,275 @@
|
||||
/*!
|
||||
* @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 { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { RenderingQueueServices } from '../services/rendering-queue.services';
|
||||
import { PdfViewerComponent } from './pdfViewer.component';
|
||||
import { EventMock } from '../assets/event.mock';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import {
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
CoreModule
|
||||
} from 'ng2-alfresco-core';
|
||||
|
||||
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: [
|
||||
CoreModule
|
||||
],
|
||||
declarations: [PdfViewerComponent],
|
||||
providers: [
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoApiService,
|
||||
RenderingQueueServices
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PdfViewerComponent);
|
||||
|
||||
debug = fixture.debugElement;
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
|
||||
component.showToolbar = true;
|
||||
component.urlFile = 'base/src/assets/fake-test-file.pdf';
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('View', () => {
|
||||
|
||||
it('If urlfile is not present should not be thrown any error ', () => {
|
||||
component.urlFile = undefined;
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(() => {
|
||||
component.ngOnChanges(null);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
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('User interaction', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.inputPage('1');
|
||||
});
|
||||
|
||||
it('Total number of pages should be loaded', (done) => {
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.totalPages).toEqual(6);
|
||||
done();
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
it('right arrow should move to the next page', (done) => {
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.displayPage).toBe(1);
|
||||
EventMock.keyDown(39);
|
||||
fixture.detectChanges();
|
||||
expect(component.displayPage).toBe(2);
|
||||
done();
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
it('nextPage should move to the next page', (done) => {
|
||||
let nextPageButton: any = element.querySelector('#viewer-next-page-button');
|
||||
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.displayPage).toBe(1);
|
||||
nextPageButton.click();
|
||||
fixture.detectChanges();
|
||||
expect(component.displayPage).toBe(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('left arrow should move to the previous page', (done) => {
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.displayPage).toBe(1);
|
||||
EventMock.keyDown(39);
|
||||
EventMock.keyDown(39);
|
||||
EventMock.keyDown(37);
|
||||
fixture.detectChanges();
|
||||
expect(component.displayPage).toBe(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('previous page should 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();
|
||||
expect(component.displayPage).toBe(1);
|
||||
nextPageButton.click();
|
||||
nextPageButton.click();
|
||||
previousPageButton.click();
|
||||
fixture.detectChanges();
|
||||
expect(component.displayPage).toBe(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('previous page should not move to the previous page if is page 1', (done) => {
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.displayPage).toBe(1);
|
||||
component.previousPage();
|
||||
fixture.detectChanges();
|
||||
expect(component.displayPage).toBe(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Input page should move to the inserted page', (done) => {
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
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('In should 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('Out should 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('fit-in button should 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', () => {
|
||||
it('resize event should trigger setScaleUpdatePages', (done) => {
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
spyOn(component, 'onResize');
|
||||
EventMock.resizeMobileView();
|
||||
expect(component.onResize).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('scroll interaction', () => {
|
||||
it('scroll page should return the current page', (done) => {
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,381 @@
|
||||
/*!
|
||||
* @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, HostListener } from '@angular/core';
|
||||
import { LogService } from 'ng2-alfresco-core';
|
||||
import { RenderingQueueServices } from '../services/rendering-queue.services';
|
||||
|
||||
declare let PDFJS: any;
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'pdf-viewer',
|
||||
templateUrl: './pdfViewer.component.html',
|
||||
styleUrls: ['./pdfViewer.component.css', './pdfViewerHost.component.css'],
|
||||
providers: [RenderingQueueServices]
|
||||
})
|
||||
export class PdfViewerComponent {
|
||||
|
||||
@Input()
|
||||
urlFile: string;
|
||||
|
||||
@Input()
|
||||
nameFile: string;
|
||||
|
||||
@Input()
|
||||
showToolbar: boolean = true;
|
||||
|
||||
currentPdfDocument: any;
|
||||
page: number;
|
||||
displayPage: number;
|
||||
totalPages: number;
|
||||
laodingPercent: 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) {
|
||||
}
|
||||
|
||||
ngOnChanges(changes) {
|
||||
if (!this.urlFile) {
|
||||
throw new Error('Attribute urlFile 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);
|
||||
};
|
||||
|
||||
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', (event) => {
|
||||
this.watchScroll(event.target);
|
||||
}, true);
|
||||
|
||||
this.pdfViewer = new PDFJS.PDFViewer({
|
||||
container: documentContainer,
|
||||
viewer: viewer,
|
||||
renderingQueue: this.renderingQueueServices
|
||||
});
|
||||
|
||||
this.renderingQueueServices.setViewer(this.pdfViewer);
|
||||
|
||||
this.pdfViewer.setDocument(pdfDocument);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,265 @@
|
||||
:host >>> .textLayer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0.2;
|
||||
line-height: 1.0;
|
||||
}
|
||||
|
||||
:host >>> .textLayer > 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%;
|
||||
}
|
||||
|
||||
:host >>> .textLayer .highlight {
|
||||
margin: -1px;
|
||||
padding: 1px;
|
||||
|
||||
background-color: rgb(180, 0, 170);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:host >>> .textLayer .highlight.begin {
|
||||
border-radius: 4px 0px 0px 4px;
|
||||
}
|
||||
|
||||
:host >>> .textLayer .highlight.end {
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
}
|
||||
|
||||
:host >>> .textLayer .highlight.middle {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
:host >>> .textLayer .highlight.selected {
|
||||
background-color: rgb(0, 100, 0);
|
||||
}
|
||||
|
||||
:host >>> .textLayer ::selection { background: rgb(0,0,255); }
|
||||
:host >>> .textLayer ::-moz-selection { background: rgb(0,0,255); }
|
||||
|
||||
:host >>> .textLayer .endOfContent {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 100%;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
z-index: -1;
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
:host >>> .textLayer .endOfContent.active {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
|
||||
:host >>> .annotationLayer section {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
:host >>> .annotationLayer .linkAnnotation > a {
|
||||
position: absolute;
|
||||
font-size: 1em;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:host >>> .annotationLayer .linkAnnotation > a /* -ms-a */ {
|
||||
background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0 repeat;
|
||||
}
|
||||
|
||||
:host >>> .annotationLayer .linkAnnotation > a:hover {
|
||||
opacity: 0.2;
|
||||
background: #ff0;
|
||||
box-shadow: 0px 2px 10px #ff0;
|
||||
}
|
||||
|
||||
:host >>> .annotationLayer .textAnnotation img {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:host >>> .annotationLayer .popupWrapper {
|
||||
position: absolute;
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
:host >>> .annotationLayer .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;
|
||||
}
|
||||
|
||||
:host >>> .annotationLayer .popup h1 {
|
||||
font-size: 1em;
|
||||
border-bottom: 1px solid #000000;
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
|
||||
:host >>> .annotationLayer .popup p {
|
||||
padding-top: 0.2em;
|
||||
}
|
||||
|
||||
:host >>> .annotationLayer .highlightAnnotation,
|
||||
.annotationLayer .underlineAnnotation,
|
||||
.annotationLayer .squigglyAnnotation,
|
||||
.annotationLayer .strikeoutAnnotation,
|
||||
.annotationLayer .fileAttachmentAnnotation {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:host >>> .pdfViewer .canvasWrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host >>> .pdfViewer .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;
|
||||
}
|
||||
|
||||
:host >>> .pdfViewer.removePageBorders .page {
|
||||
margin: 0px auto 10px auto;
|
||||
border: none;
|
||||
}
|
||||
|
||||
:host >>> .pdfViewer .page canvas {
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host >>> .pdfViewer .page .loadingIcon {
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
:host >>> .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;
|
||||
}
|
||||
:host >>> .loadingIcon,
|
||||
:host >>> .loadingIcon:after {
|
||||
border-radius: 50%;
|
||||
}
|
||||
@-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);
|
||||
}
|
||||
}
|
||||
|
||||
:host >>> * {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:host >>> html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/* Font size is needed to make the activity bar the correct size. */
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
:host >>> body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #404040;
|
||||
background-image: url(images/texture.png);
|
||||
}
|
||||
|
||||
:host >>> body,
|
||||
input,
|
||||
button,
|
||||
select {
|
||||
font: message-box;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
:host >>> .hidden {
|
||||
display: none !important;
|
||||
}
|
||||
:host >>> [hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
#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);
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
.button-container {
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#page-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mdl-grid {
|
||||
width: 100vw;
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
.viewer-name-file {
|
||||
width: 100%;
|
||||
height: 21px;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 60px;
|
||||
margin-right: -40px;
|
||||
}
|
||||
|
||||
.viewer-shadow-transparent {
|
||||
z-index: 1000;
|
||||
background-color: #3E3E3E;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
opacity: .90;
|
||||
}
|
||||
|
||||
.viewer-overlay-view {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
img-viewer {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.center-element {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.all-space{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #515151;
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
<div id="viewer" *ngIf="showViewer" [ngClass]="{'all-space': !overlayMode }">
|
||||
<div *ngIf="overlayMode">
|
||||
<div id="viewer-shadow-transparent" class="viewer-shadow-transparent"></div>
|
||||
</div>
|
||||
|
||||
<div id="viewer-main-container" class="all-space" [ngClass]="{'viewer-overlay-view': overlayMode }">
|
||||
|
||||
<!-- Start Layout -->
|
||||
<div [ngClass]="{'mdl-layout mdl-js-layout mdl-layout--fixed-header': overlayMode, 'all-space': !overlayMode}">
|
||||
|
||||
<header *ngIf="overlayMode" class="mdl-layout__header">
|
||||
<div class="mdl-layout__header-row">
|
||||
|
||||
<!-- File Title -->
|
||||
<span id="viewer-name-file" class="mdl-layout-title viewer-name-file">{{displayName}}</span>
|
||||
|
||||
<span class="vertical-divider"></span>
|
||||
|
||||
<div class="mdl-layout-spacer"></div>
|
||||
|
||||
<!-- Start Navigation -->
|
||||
<nav class="mdl-navigation">
|
||||
<div id="viewer-toolbar-view-options">
|
||||
<button *ngIf="overlayMode"
|
||||
class="mdl-color--black mdl-button mdl-js-button mdl-button--fab mdl-button--mini-fab mdl-button--colored"
|
||||
(click)="close()" aria-label="Close">
|
||||
<i id="viewer-close-button" class="icon material-icons">close</i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- End Navigation -->
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="page-content" class="mdl-layout__content" [ngClass]="{'all-space': !overlayMode }">
|
||||
|
||||
<div class="mdl-grid">
|
||||
|
||||
<div id="viewer-content-container" *ngIf="isLoaded()"
|
||||
class="center-element mdl-cell mdl-cell--12-col">
|
||||
|
||||
<!-- Start View Switch-->
|
||||
<div *ngIf="isPdf()">
|
||||
<pdf-viewer [showToolbar]="showToolbar" [urlFile]="urlFileContent"
|
||||
[nameFile]="displayName"></pdf-viewer>
|
||||
</div>
|
||||
<div class="center-element" *ngIf="isImage()">
|
||||
<img-viewer [urlFile]="urlFileContent" [nameFile]="displayName"></img-viewer>
|
||||
</div>
|
||||
<div class="center-element" *ngIf="isMedia()">
|
||||
<media-player [urlFile]="urlFileContent" [mimeType]="mimeType"
|
||||
[nameFile]="displayName"></media-player>
|
||||
</div>
|
||||
|
||||
<span *ngFor="let extensionTemplate of extensionTemplates">
|
||||
<template [ngTemplateOutlet]="extensionTemplate.template" *ngIf="extensionTemplate.isVisible"
|
||||
[ngOutletContext]="{ urlFileContent: urlFileContent, extension:extension }"></template>
|
||||
</span>
|
||||
|
||||
<div *ngIf="!supportedExtension()">
|
||||
<not-supported-format *ngIf="!extensionTemplate" [urlFile]="urlFileContent" [nameFile]="displayName"></not-supported-format>
|
||||
</div>
|
||||
<!-- End View Switch -->
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<!-- End Layout -->
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,334 @@
|
||||
/*!
|
||||
* @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 { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { PdfViewerComponent } from './pdfViewer.component';
|
||||
import { NotSupportedFormat } from './notSupportedFormat.component';
|
||||
import { MediaPlayerComponent } from './mediaPlayer.component';
|
||||
import { ImgViewerComponent } from './imgViewer.component';
|
||||
import { RenderingQueueServices } from '../services/rendering-queue.services';
|
||||
import { ViewerComponent } from './viewer.component';
|
||||
import { EventMock } from '../assets/event.mock';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import {
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
CoreModule
|
||||
} from 'ng2-alfresco-core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('Test ng2-alfresco-viewer ViewerComponent', () => {
|
||||
|
||||
let component: ViewerComponent;
|
||||
let fixture: ComponentFixture<ViewerComponent>;
|
||||
let debug: DebugElement;
|
||||
let element: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule
|
||||
],
|
||||
declarations: [
|
||||
ViewerComponent,
|
||||
PdfViewerComponent,
|
||||
NotSupportedFormat,
|
||||
MediaPlayerComponent,
|
||||
ImgViewerComponent
|
||||
],
|
||||
providers: [
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoApiService,
|
||||
RenderingQueueServices
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ViewerComponent);
|
||||
|
||||
debug = fixture.debugElement;
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
|
||||
jasmine.Ajax.install();
|
||||
|
||||
component.showToolbar = true;
|
||||
component.urlFile = 'base/src/assets/fake-test-file.pdf';
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
describe('View', () => {
|
||||
|
||||
describe('Overlay mode true', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.overlayMode = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('shadow overlay should be present if is overlay mode', () => {
|
||||
expect(element.querySelector('#viewer-shadow-transparent')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('header should be present if is overlay mode', () => {
|
||||
expect(element.querySelector('header')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('Name File should be present if is overlay mode ', () => {
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#viewer-name-file').innerHTML).toEqual('fake-test-file.pdf');
|
||||
});
|
||||
});
|
||||
|
||||
it('Close button should be present if overlay mode', () => {
|
||||
expect(element.querySelector('#viewer-close-button')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('Click on close button should hide the viewer', () => {
|
||||
let closebutton: any = element.querySelector('#viewer-close-button');
|
||||
closebutton.click();
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#viewer-main-container')).toBeNull();
|
||||
});
|
||||
|
||||
it('Esc button should hide the viewer', () => {
|
||||
EventMock.keyDown(27);
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#viewer-main-container')).toBeNull();
|
||||
});
|
||||
|
||||
it('all-space class should not be present if is in overlay mode', () => {
|
||||
expect(element.querySelector('#viewer').getAttribute('class')).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Overlay mode false', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.overlayMode = false;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('header should be NOT be present if is not overlay mode', () => {
|
||||
expect(element.querySelector('header')).toBeNull();
|
||||
});
|
||||
|
||||
it('Close button should be not present if is not overlay mode', () => {
|
||||
expect(element.querySelector('#viewer-close-button')).toBeNull();
|
||||
});
|
||||
|
||||
it('Esc button should not hide the viewer if is not overlay mode', () => {
|
||||
EventMock.keyDown(27);
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#viewer-main-container')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('all-space class should be present if is not overlay mode', () => {
|
||||
expect(element.querySelector('#viewer').getAttribute('class')).toEqual('all-space');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Attribute', () => {
|
||||
it('Url or fileNodeId File should be mandatory', () => {
|
||||
component.showViewer = true;
|
||||
component.fileNodeId = undefined;
|
||||
component.urlFile = undefined;
|
||||
|
||||
expect(() => {
|
||||
component.ngOnChanges(null);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('If FileNodeId is present should not be thrown any error ', () => {
|
||||
component.showViewer = true;
|
||||
component.fileNodeId = 'file-node-id';
|
||||
component.urlFile = undefined;
|
||||
|
||||
expect(() => {
|
||||
component.ngOnChanges(null);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('If urlFile is present should not be thrown any error ', () => {
|
||||
component.showViewer = true;
|
||||
component.fileNodeId = undefined;
|
||||
|
||||
expect(() => {
|
||||
component.ngOnChanges(null);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('showViewer default value should be true', () => {
|
||||
expect(component.showViewer).toBe(true);
|
||||
});
|
||||
|
||||
it('if showViewer value is false the viewer should be hide', () => {
|
||||
component.showViewer = false;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#viewer-main-container')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Extension Type Test', () => {
|
||||
it('if extension file is a pdf the pdf viewer should be loaded', (done) => {
|
||||
component.urlFile = 'base/src/assets/fake-test-file.pdf';
|
||||
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('pdf-viewer')).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('if extension file is a image the img viewer should be loaded', (done) => {
|
||||
component.urlFile = 'fake-url-file.png';
|
||||
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#viewer-image')).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('if extension file is a video the the media player should be loaded', (done) => {
|
||||
component.urlFile = 'fake-url-file.mp4';
|
||||
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('media-player')).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('if extension file is a not supported the not supported div should be loaded', (done) => {
|
||||
component.urlFile = 'fake-url-file.unsupported';
|
||||
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('not-supported-format')).not.toBeNull();
|
||||
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('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('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('media-player')).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('media-player')).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not display the media player if the file identified by mimetype is a media but with not supported extension', (done) => {
|
||||
component.urlFile = 'content';
|
||||
component.mimeType = 'video/avi';
|
||||
|
||||
component.ngOnChanges(null).then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('media-player')).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Events', () => {
|
||||
it('if extension change exextensionChange event should be fired ', (done) => {
|
||||
|
||||
component.extensionChange.subscribe((fileExtension) => {
|
||||
expect(fileExtension).toEqual('png');
|
||||
done();
|
||||
});
|
||||
|
||||
component.urlFile = 'fake-url-file.png';
|
||||
|
||||
component.ngOnChanges(null);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,337 @@
|
||||
/*!
|
||||
* @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, ElementRef, Input, Output, HostListener, EventEmitter, Inject, TemplateRef } from '@angular/core';
|
||||
import { DOCUMENT } from '@angular/platform-browser';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { AlfrescoApiService, LogService } from 'ng2-alfresco-core';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'alfresco-viewer',
|
||||
templateUrl: './viewer.component.html',
|
||||
styleUrls: ['./viewer.component.css']
|
||||
})
|
||||
export class ViewerComponent {
|
||||
|
||||
@Input()
|
||||
urlFile: string = '';
|
||||
|
||||
@Input()
|
||||
fileNodeId: string = null;
|
||||
|
||||
@Input()
|
||||
overlayMode: boolean = false;
|
||||
|
||||
@Input()
|
||||
showViewer: boolean = true;
|
||||
|
||||
@Input()
|
||||
showToolbar: boolean = true;
|
||||
|
||||
@Output()
|
||||
showViewerChange: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||
|
||||
@Output()
|
||||
extensionChange: EventEmitter<String> = new EventEmitter<String>();
|
||||
|
||||
extensionTemplates: { template: TemplateRef<any>, isVisible: boolean }[] = [];
|
||||
|
||||
externalExtensions: string[] = [];
|
||||
|
||||
urlFileContent: string;
|
||||
otherMenu: any;
|
||||
displayName: string;
|
||||
extension: string;
|
||||
mimeType: string;
|
||||
loaded: boolean = false;
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private element: ElementRef,
|
||||
@Inject(DOCUMENT) private document,
|
||||
private logService: LogService) {
|
||||
}
|
||||
|
||||
ngOnChanges(changes) {
|
||||
if (this.showViewer) {
|
||||
this.hideOtherHeaderBar();
|
||||
this.blockOtherScrollBar();
|
||||
if (!this.urlFile && !this.fileNodeId) {
|
||||
throw new Error('Attribute urlFile or fileNodeId is required');
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let alfrescoApi = this.apiService.getInstance();
|
||||
if (this.urlFile) {
|
||||
let filenameFromUrl = this.getFilenameFromUrl(this.urlFile);
|
||||
this.displayName = filenameFromUrl ? filenameFromUrl : '';
|
||||
this.extension = this.getFileExtension(filenameFromUrl);
|
||||
this.extensionChange.emit(this.extension);
|
||||
this.urlFileContent = this.urlFile;
|
||||
resolve();
|
||||
} else if (this.fileNodeId) {
|
||||
alfrescoApi.nodes.getNodeInfo(this.fileNodeId).then((data: MinimalNodeEntryEntity) => {
|
||||
this.mimeType = data.content.mimeType;
|
||||
this.displayName = data.name;
|
||||
this.urlFileContent = alfrescoApi.content.getContentUrl(data.id);
|
||||
this.extension = this.getFileExtension(data.name);
|
||||
this.extensionChange.emit(this.extension);
|
||||
this.loaded = true;
|
||||
resolve();
|
||||
}, function (error) {
|
||||
reject(error);
|
||||
this.logService.error('This node does not exist');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* close the viewer
|
||||
*/
|
||||
close() {
|
||||
this.unblockOtherScrollBar();
|
||||
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.loaded = false;
|
||||
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) {
|
||||
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
|
||||
*/
|
||||
private getFileExtension(fileName: string) {
|
||||
return fileName.split('.').pop().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the content is an image through the extension or mime type
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isImage() {
|
||||
return this.isImageExtension() || this.isImageMimeType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the content is a media through the extension or mime type
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isMedia() {
|
||||
return this.isMediaExtension(this.extension) || this.isMediaMimeType();
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the current file is a supported image extension
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isImageExtension() {
|
||||
return this.extension === 'png' || this.extension === 'jpg' ||
|
||||
this.extension === 'jpeg' || this.extension === 'gif' || this.extension === 'bmp';
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the current file has an image-based mimetype
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isMediaMimeType() {
|
||||
let mimeExtension;
|
||||
if (this.mimeType && this.mimeType.indexOf('/')) {
|
||||
mimeExtension = this.mimeType.substr(this.mimeType.indexOf('/') + 1, this.mimeType.length);
|
||||
}
|
||||
return this.mimeType && this.mimeType.indexOf('video/') === 0 && this.isMediaExtension(mimeExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the current file is a supported media extension
|
||||
* @param {string} extension
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isMediaExtension(extension: string) {
|
||||
return extension === 'mp4' || extension === 'WebM' || extension === 'Ogg';
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the current file has an image-based mimetype
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isImageMimeType() {
|
||||
return this.mimeType && this.mimeType.indexOf('image/') === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the current file is a supported pdf extension
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isPdf() {
|
||||
return this.extension === 'pdf' || this.mimeType === 'application/pdf';
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the current file is a supported extension
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
supportedExtension() {
|
||||
return this.isImage() || this.isPdf() || this.isMedia() || this.isExternalSupportedExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is compatible with one of the extension
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isExternalSupportedExtension() {
|
||||
let externalType: string;
|
||||
|
||||
if (this.externalExtensions && (this.externalExtensions instanceof Array)) {
|
||||
externalType = this.externalExtensions.find((externalExtension) => {
|
||||
return externalExtension.toLowerCase() === this.extension;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return !!externalType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if in the document there are scrollable main area and disable it
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private blockOtherScrollBar() {
|
||||
let mainElements: any = document.getElementsByTagName('main');
|
||||
|
||||
for (let i = 0; i < mainElements.length; i++) {
|
||||
mainElements[i].style.overflow = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if in the document there are scrollable main area and renable it
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private unblockOtherScrollBar() {
|
||||
let mainElements: any = document.getElementsByTagName('main');
|
||||
|
||||
for (let i = 0; i < mainElements.length; i++) {
|
||||
mainElements[i].style.overflow = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the viewer is used inside and header element
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isParentElementHeaderBar() {
|
||||
return !!this.closestElement(this.element.nativeElement, 'header');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the viewer is used inside and header element
|
||||
* @param {HTMLElement} elelemnt
|
||||
* @param {string} nodeName
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
private closestElement(element: HTMLElement, nodeName: string) {
|
||||
let parent = element.parentElement;
|
||||
if (parent) {
|
||||
if (parent.nodeName.toLowerCase() === nodeName) {
|
||||
return parent;
|
||||
} else {
|
||||
return this.closestElement(parent, nodeName);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the other possible menu in the application
|
||||
*/
|
||||
private hideOtherHeaderBar() {
|
||||
if (this.overlayMode && !this.isParentElementHeaderBar()) {
|
||||
this.otherMenu = document.querySelector('header');
|
||||
if (this.otherMenu) {
|
||||
this.otherMenu.hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if the data about the node in the ecm are loaded
|
||||
*/
|
||||
isLoaded() {
|
||||
return this.fileNodeId ? this.loaded : true;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user