👽 Angular 14 rebase 👽 (#7769)

* fix after rebase

* new release strategy for ng next

Signed-off-by: eromano <eugenioromano16@gmail.com>

* peer dep

Signed-off-by: eromano <eugenioromano16@gmail.com>

* Angular 14

fix unit test and storybook

Signed-off-by: eromano <eugenioromano16@gmail.com>

fix after rebase

Signed-off-by: eromano <eugenioromano16@gmail.com>

update pkg.json

Signed-off-by: eromano <eugenioromano16@gmail.com>

missing dep

Signed-off-by: eromano <eugenioromano16@gmail.com>

Fix mistake and missing code

Dream....build only affected libs

Add utility run commands

* Use nx command to run affected tests

* Fix nx test core

fix content tests

Run unit with watch false

core test fixes

reduce test warnings

Fix process cloud unit

Fix adf unit test

Fix lint process cloud

Disable lint next line

Use right core path

Fix insights unit

fix linting insights

Fix process-services unit

fix the extensions test report

fix test warnings

Fix content unit

Fix bunch of content unit

* Produce an adf alpha of 14

* hopefully fixing the content

* Push back the npm publish

* Remove flaky unit

* Fix linting

* Make the branch as root

* Get rid of angualar13

* Remove the travis depth

* Fixing version for npm

* Enabling cache for unit and build

* Fix scss for core and paths

Copy i18 and asset by using ng-packager

Export the theming alias and fix path

Use ng-package to copy assets process-services-cloud

Use ng-package to copy assets process-services

Use ng-package to copy assets content-services

Use ng-package to copy assets insights

* feat: fix api secondary entry point

* fix storybook rebase

* Move dist under dist/libs from lib/dist

* Fix the webstyle

* Use only necessary nrwl deps and improve lint

* Fix unit for libs

* Convert lint.sh to targets - improve performance

* Use latest of angular

* Align alfresco-js-api

Signed-off-by: eromano <eugenioromano16@gmail.com>
Co-authored-by: eromano <eugenioromano16@gmail.com>
Co-authored-by: Mikolaj Serwicki <mikolaj.serwicki@hyland.com>
Co-authored-by: Tomasz <tomasz.gnyp@hyland.com>
This commit is contained in:
Maurizio Vitale
2022-08-25 10:50:30 +01:00
committed by GitHub
co-authored by eromano Mikolaj Serwicki Tomasz
parent 53bc5aab2c
commit 1fa81962a0
1351 changed files with 26853 additions and 11958 deletions
Binary file not shown.
@@ -1 +0,0 @@
Text example
Binary file not shown.
Binary file not shown.
@@ -1,77 +0,0 @@
<div id="adf-image-container" (keydown)="onKeyDown($event)" class="adf-image-container" tabindex="0" role="img" [attr.aria-label]="nameFile" data-automation-id="adf-image-container">
<img #image id="viewer-image" [src]="urlFile" [alt]="nameFile" (error)="onImageError()" />
</div>
<div class="adf-image-viewer__toolbar" *ngIf="showToolbar">
<adf-toolbar class="adf-main-toolbar">
<button
id="viewer-zoom-out-button"
title="{{ 'ADF_VIEWER.ARIA.ZOOM_OUT' | translate }}"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.ZOOM_OUT' | translate }}"
mat-icon-button
(click)="zoomOut()">
<mat-icon>zoom_out</mat-icon>
</button>
<div class="adf-viewer__toolbar-page-scale" data-automation-id="adf-page-scale">
{{ currentScaleText }}
</div>
<button
id="viewer-zoom-in-button"
mat-icon-button
title="{{ 'ADF_VIEWER.ARIA.ZOOM_IN' | translate }}"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.ZOOM_IN' | translate }}"
(click)="zoomIn()">
<mat-icon>zoom_in</mat-icon>
</button>
<button
*ngIf="!readOnly" id="viewer-rotate-button"
title="{{ 'ADF_VIEWER.ARIA.ROTATE' | translate }}"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.ROTATE' | translate }}"
mat-icon-button
(click)="rotateImage()">
<mat-icon>rotate_left</mat-icon>
</button>
<button
*ngIf="!readOnly" id="viewer-crop-button"
title="{{ 'ADF_VIEWER.ARIA.CROP' | translate }}"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.CROP' | translate }}"
mat-icon-button
(click)="cropImage()">
<mat-icon>crop</mat-icon>
</button>
<button
id="viewer-reset-button"
title="{{ 'ADF_VIEWER.ARIA.RESET' | translate }}"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.RESET' | translate }}"
mat-icon-button
(click)="reset()">
<mat-icon>zoom_out_map</mat-icon>
</button>
</adf-toolbar>
<adf-toolbar class="adf-secondary-toolbar" *ngIf="!readOnly && isEditing">
<button
id="viewer-cancel-button"
title="{{ 'ADF_VIEWER.ARIA.CANCEL' | translate }}"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.CANCEL' | translate }}"
mat-icon-button
(click)="reset()">
<mat-icon>close</mat-icon>
</button>
<button
id="viewer-save-button"
title="{{ 'ADF_VIEWER.ARIA.SAVE' | translate }}"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.SAVE' | translate }}"
mat-icon-button
(click)="save()">
<mat-icon>check</mat-icon>
</button>
</adf-toolbar>
</div>
@@ -1,52 +0,0 @@
.adf-image-viewer {
width: 100%;
.adf-image-container {
&:focus {
outline-offset: -1px;
outline: 1px solid var(--theme-accent-color-a200);
}
display: flex;
height: 90vh;
align-items: center;
justify-content: center;
img {
max-height: 100%;
max-width: 100%;
}
/* query for Microsoft IE 11 */
@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
img {
height: 100%;
}
}
}
&__toolbar {
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
.adf-toolbar .mat-toolbar {
max-height: 48px;
background-color: var(--theme-card-bg-color);
border-width: 0;
border-radius: 2px;
/* stylelint-disable-next-line */
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 0 2px 0 rgba(0, 0, 0, 0.12);
}
.adf-main-toolbar {
display: inline-block;
}
.adf-secondary-toolbar {
display: inline-block;
margin-left: 10px;
}
}
}
@@ -1,355 +0,0 @@
/*!
* @license
* Copyright 2019 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 { SimpleChange } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { ContentService } from '../../services/content.service';
import { ImgViewerComponent } from './img-viewer.component';
import { setupTestBed, CoreTestingModule } from '../../testing';
import { AppConfigService } from '../../app-config/app-config.service';
import { TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser';
describe('Test Img viewer component ', () => {
let component: ImgViewerComponent;
let service: ContentService;
let fixture: ComponentFixture<ImgViewerComponent>;
let element: HTMLElement;
const createFakeBlob = () => {
const data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
return new Blob([data], { type: 'image/png' });
};
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule
]
});
describe('Zoom customization', () => {
beforeEach(() => {
service = TestBed.inject(ContentService);
fixture = TestBed.createComponent(ImgViewerComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
component.urlFile = 'fake-url-file.png';
fixture.detectChanges();
});
describe('default value', () => {
it('should use default zoom if is not present a custom zoom in the app.config', () => {
fixture.detectChanges();
expect(component.scale).toBe(1.0);
});
});
describe('custom value', () => {
beforeEach(() => {
const appConfig: AppConfigService = TestBed.inject(AppConfigService);
appConfig.config['adf-viewer.image-viewer-scaling'] = 70;
component.initializeScaling();
});
it('should use the custom zoom if it is present in the app.config', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(component.scale).toBe(0.70);
done();
});
});
});
});
describe('Url', () => {
beforeEach(() => {
service = TestBed.inject(ContentService);
fixture = TestBed.createComponent(ImgViewerComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
component.urlFile = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
fixture.detectChanges();
fixture.componentInstance.ngAfterViewInit();
component.ngAfterViewInit();
fixture.detectChanges();
});
it('should display current scale as percent string', () => {
component.scale = 0.5;
expect(component.currentScaleText).toBe('50%');
component.scale = 1.0;
expect(component.currentScaleText).toBe('100%');
});
it('should define cropper after init', () => {
fixture.componentInstance.ngAfterViewInit();
expect(component.cropper).toBeDefined();
});
});
describe('Blob', () => {
beforeEach(() => {
service = TestBed.inject(ContentService);
fixture = TestBed.createComponent(ImgViewerComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
fixture.detectChanges();
});
it('If no url or blob are passed should thrown an error', () => {
const 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', () => {
const blob = createFakeBlob();
spyOn(service, 'createTrustedUrl').and.returnValue('fake-blob-url');
const 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');
});
});
describe('toolbar actions', () => {
beforeEach(() => {
fixture = TestBed.createComponent(ImgViewerComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
component.blobFile = createFakeBlob();
const change = new SimpleChange(null, component.blobFile, true);
component.ngOnChanges({ blobFile: change });
fixture.detectChanges();
});
it('should update scales on zoom in', fakeAsync(() => {
spyOn(component, 'zoomIn').and.callThrough();
spyOn(component.cropper, 'zoom');
component.scale = 1.0;
tick();
component.zoomIn();
expect(component.scale).toBe(1.2);
expect(component.cropper.zoom).toHaveBeenCalledWith(0.2);
component.zoomIn();
expect(component.scale).toBe(1.4);
expect(component.cropper.zoom).toHaveBeenCalledWith(0.2);
}));
it('should update scales on zoom out', fakeAsync(() => {
spyOn(component, 'zoomOut').and.callThrough();
spyOn(component.cropper, 'zoom');
component.scale = 1.0;
tick();
component.zoomOut();
expect(component.scale).toBe(0.8);
expect(component.cropper.zoom).toHaveBeenCalledWith(-0.2);
component.zoomOut();
expect(component.scale).toBe(0.6);
expect(component.cropper.zoom).toHaveBeenCalledWith(-0.2);
}));
it('should not zoom out past 20%', fakeAsync(() => {
component.scale = 0.2;
tick();
component.zoomOut();
component.zoomOut();
component.zoomOut();
expect(component.scale).toBe(0.2);
}));
it('should show rotate button if not in read only mode', () => {
component.readOnly = false;
fixture.detectChanges();
const rotateButtonElement = element.querySelector('#viewer-rotate-button');
expect(rotateButtonElement).not.toEqual(null);
});
it('should not show rotate button by default', () => {
const rotateButtonElement = element.querySelector('#viewer-rotate-button');
expect(rotateButtonElement).toEqual(null);
});
it('should not show crop button by default', () => {
const rotateButtonElement = element.querySelector('#viewer-crop-button');
expect(rotateButtonElement).toEqual(null);
});
it('should start cropping when clicking the crop button', fakeAsync(() => {
component.readOnly = false;
spyOn(component, 'cropImage').and.callThrough();
spyOn(component.cropper, 'crop');
spyOn(component.cropper, 'setDragMode');
fixture.detectChanges();
const cropButtonElement = fixture.debugElement.query(By.css('#viewer-crop-button'));
cropButtonElement.triggerEventHandler('click', null);
tick();
expect(component.cropImage).toHaveBeenCalled();
expect(component.cropper.crop).toHaveBeenCalled();
expect(component.cropper.setDragMode).toHaveBeenCalledWith('crop');
}));
it('should rotate image by -90 degrees on button click', fakeAsync(() => {
component.readOnly = false;
spyOn(component, 'rotateImage').and.callThrough();
spyOn(component.cropper, 'rotate');
fixture.detectChanges();
const rotateButtonElement = fixture.debugElement.query(By.css('#viewer-rotate-button'));
rotateButtonElement.triggerEventHandler('click', null);
tick();
expect(component.rotateImage).toHaveBeenCalled();
expect(component.cropper.rotate).toHaveBeenCalledWith(-90);
}));
it('should display the second toolbar when in editing and not in read only mode', fakeAsync(() => {
component.readOnly = false;
component.isEditing = true;
fixture.detectChanges();
const secondaryToolbar = document.querySelector('.adf-secondary-toolbar');
expect(secondaryToolbar).not.toEqual(null);
}));
it('should not display the second toolbar when in read only mode', () => {
component.readOnly = true;
fixture.detectChanges();
const secondaryToolbar = document.querySelector('.adf-secondary-toolbar');
expect(secondaryToolbar).toEqual(null);
});
it('should not display the second toolbar when not in editing', () => {
component.readOnly = true;
component.isEditing = false;
fixture.detectChanges();
const secondaryToolbar = document.querySelector('.adf-secondary-toolbar');
expect(secondaryToolbar).toEqual(null);
});
it('should display second toolbar in edit mode', fakeAsync(() => {
component.readOnly = false;
component.isEditing = true;
fixture.detectChanges();
const secondaryToolbar = document.querySelector('.adf-secondary-toolbar');
const resetButton = document.querySelector('#viewer-cancel-button');
const saveButton = document.querySelector('#viewer-save-button');
expect(secondaryToolbar).not.toEqual(null);
expect(resetButton).not.toEqual(null);
expect(saveButton).not.toEqual(null);
}));
it('should not be in editing mode by default', () => {
component.readOnly = false;
expect(component.isEditing).toEqual(false);
});
it('should get in editing mode when the image gets rotated', () => {
component.readOnly = false;
component.rotateImage();
expect(component.isEditing).toEqual(true);
});
it('should get in editing mode when the image gets cropped', () => {
component.readOnly = false;
component.cropImage();
expect(component.isEditing).toEqual(true);
});
it('should reset the scale and hide second toolbar', fakeAsync(() => {
component.readOnly = false;
component.isEditing = true;
spyOn(component, 'reset').and.callThrough();
spyOn(component, 'updateCanvasContainer');
spyOn(component.cropper, 'reset');
spyOn(component.cropper, 'clear');
spyOn(component.cropper, 'zoomTo');
fixture.detectChanges();
const cancelButtonElement = fixture.debugElement.query(By.css('#viewer-cancel-button'));
cancelButtonElement.triggerEventHandler('click', null);
tick();
expect(component.reset).toHaveBeenCalled();
expect(component.scale).toEqual(1.0);
expect(component.isEditing).toEqual(false);
expect(component.cropper.reset).toHaveBeenCalled();
expect(component.cropper.clear).toHaveBeenCalled();
expect(component.updateCanvasContainer).toHaveBeenCalled();
}));
it('should save when clicked on toolbar button', fakeAsync(() => {
component.readOnly = false;
component.isEditing = true;
spyOn(component, 'save');
fixture.detectChanges();
const saveButtonElement = fixture.debugElement.query(By.css('#viewer-save-button'));
saveButtonElement.triggerEventHandler('click', null);
tick();
expect(component.save).toHaveBeenCalled();
}));
});
});
@@ -1,213 +0,0 @@
/*!
* @license
* Copyright 2019 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,
ElementRef,
Output,
EventEmitter, AfterViewInit, ViewChild, HostListener, OnDestroy
} from '@angular/core';
import { ContentService } from '../../services/content.service';
import { AppConfigService } from './../../app-config/app-config.service';
import Cropper from 'cropperjs';
@Component({
selector: 'adf-img-viewer',
templateUrl: './img-viewer.component.html',
styleUrls: ['./img-viewer.component.scss'],
host: { class: 'adf-image-viewer' },
encapsulation: ViewEncapsulation.None
})
export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input()
showToolbar = true;
@Input()
readOnly = true;
@Input()
urlFile: string;
@Input()
blobFile: Blob;
@Input()
nameFile: string;
// eslint-disable-next-line @angular-eslint/no-output-native
@Output()
error = new EventEmitter<any>();
// eslint-disable-next-line @angular-eslint/no-output-native
@Output()
submit = new EventEmitter<any>();
@ViewChild('image', { static: false})
public imageElement: ElementRef;
public scale: number = 1.0;
public cropper: Cropper;
public isEditing: boolean = false;
get currentScaleText(): string {
return Math.round(this.scale * 100) + '%';
}
constructor(
private appConfigService: AppConfigService,
private contentService: ContentService) {
this.initializeScaling();
}
initializeScaling() {
const scaling = this.appConfigService.get<number>('adf-viewer.image-viewer-scaling', undefined) / 100;
if (scaling) {
this.scale = scaling;
}
}
ngAfterViewInit() {
this.cropper = new Cropper(this.imageElement.nativeElement, {
autoCrop: false,
dragMode: 'move',
background: false,
scalable: true,
zoomOnWheel: false,
toggleDragModeOnDblclick: false,
viewMode: 1,
checkCrossOrigin: false,
ready: () => {
this.updateCanvasContainer();
}
});
}
ngOnDestroy() {
this.cropper.destroy();
}
@HostListener('document:keydown', ['$event'])
onKeyDown(event: KeyboardEvent) {
switch (event.key) {
case 'ArrowLeft':
event.preventDefault();
this.cropper.move(-3, 0);
break;
case 'ArrowUp':
event.preventDefault();
this.cropper.move(0, -3);
break;
case 'ArrowRight':
event.preventDefault();
this.cropper.move(3, 0);
break;
case 'ArrowDown':
event.preventDefault();
this.cropper.move(0, 3);
break;
case 'i':
this.zoomIn();
break;
case 'o':
this.zoomOut();
break;
case 'r':
this.rotateImage();
break;
default:
}
}
ngOnChanges(changes: SimpleChanges) {
const 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');
}
}
zoomIn() {
this.cropper.zoom( 0.2);
this.scale = +((this.scale + 0.2).toFixed(1));
}
zoomOut() {
if (this.scale > 0.2) {
this.cropper.zoom( -0.2 );
this.scale = +((this.scale - 0.2).toFixed(1));
}
}
rotateImage() {
this.isEditing = true;
this.cropper.rotate( -90);
}
cropImage() {
this.isEditing = true;
this.cropper.setDragMode('crop');
this.cropper.crop();
}
save() {
this.isEditing = false;
this.cropper.setDragMode('move');
this.cropper.getCroppedCanvas().toBlob((blob) => {
this.submit.emit(blob);
this.cropper.replace(this.cropper.getCroppedCanvas().toDataURL());
this.cropper.clear();
});
}
reset() {
this.isEditing = false;
this.cropper.clear();
this.cropper.reset();
this.cropper.setDragMode('move');
this.scale = 1.0;
this.updateCanvasContainer();
}
updateCanvasContainer() {
if (this.imageElement.nativeElement.width < this.cropper.getContainerData().width) {
const width = this.imageElement.nativeElement.width;
const height = this.imageElement.nativeElement.height;
const top = (this.cropper.getContainerData().height - this.imageElement.nativeElement.height) / 2;
const left = (this.cropper.getContainerData().width - this.imageElement.nativeElement.width) / 2;
this.cropper.setCanvasData({
width,
height,
top,
left
});
}
}
onImageError() {
this.error.emit();
}
}
@@ -1,4 +0,0 @@
<video controls [ngClass]="{'adf-audio-file': mimeType && mimeType.startsWith('audio')}">
<source [src]="urlFile" [type]="mimeType" (error)="onMediaPlayerError()"/>
<track *ngFor="let track of tracks" [kind]="track.kind" [label]="track.label" [srclang]="track.srclang" [src]="track.src"/>
</video>
@@ -1,14 +0,0 @@
.adf-media-player {
display: flex;
video {
display: flex;
flex: 1;
max-height: 90vh;
max-width: 100%;
}
video.adf-audio-file::-webkit-media-text-track-container {
transform: translateY(-50%) !important;
}
}
@@ -1,77 +0,0 @@
/*!
* @license
* Copyright 2019 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, Output, EventEmitter } from '@angular/core';
import { ContentService } from '../../services/content.service';
import { Track } from '../models/viewer.model';
import { ViewUtilService } from '../services/view-util.service';
@Component({
selector: 'adf-media-player',
templateUrl: './media-player.component.html',
styleUrls: ['./media-player.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;
@Input()
nodeId: string;
@Input()
tracks: Track[] = [];
@Output()
error = new EventEmitter<any>();
constructor(private contentService: ContentService, private viewUtils: ViewUtilService) {
}
ngOnChanges(changes: SimpleChanges) {
const blobFile = changes['blobFile'];
const nodeId = changes['nodeId'];
if (blobFile && blobFile.currentValue) {
this.urlFile = this.contentService.createTrustedUrl(this.blobFile);
return;
}
if (nodeId && nodeId.currentValue) {
this.viewUtils.generateMediaTracks(this.nodeId).then((tracks) => this.tracks = tracks);
}
if (!this.urlFile && !this.blobFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
}
onMediaPlayerError() {
this.error.emit();
}
}
@@ -1,639 +0,0 @@
.adf-pdf-viewer {
.textLayer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0.2;
line-height: 1;
}
.textLayer > span {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
transform-origin: 0% 0%;
}
.textLayer .highlight {
margin: -1px;
padding: 1px;
background-color: rgb(180, 0, 170);
border-radius: 4px;
}
.textLayer .highlight.begin {
border-radius: 4px 0 0 4px;
}
.textLayer .highlight.end {
border-radius: 0 4px 4px 0;
}
.textLayer .highlight.middle {
border-radius: 0;
}
.textLayer .highlight.selected {
background-color: rgb(0, 100, 0);
}
.textLayer ::-moz-selection { background: rgb(0, 0, 255); }
.textLayer ::selection { background: rgb(0, 0, 255); }
.textLayer .endOfContent {
display: block;
position: absolute;
left: 0;
top: 100%;
right: 0;
bottom: 0;
z-index: -1;
cursor: default;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.textLayer .endOfContent.active {
top: 0;
}
.annotationLayer section {
position: absolute;
}
.annotationLayer .linkAnnotation > a,
.annotationLayer .buttonWidgetAnnotation.pushButton > a {
position: absolute;
font-size: 1em;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.annotationLayer .linkAnnotation > a:hover,
.annotationLayer .buttonWidgetAnnotation.pushButton > a:hover {
opacity: 0.2;
background: #ff0;
box-shadow: 0 2px 10px #ff0;
}
.annotationLayer .textAnnotation img {
position: absolute;
cursor: pointer;
}
.annotationLayer .textWidgetAnnotation input,
.annotationLayer .textWidgetAnnotation textarea,
.annotationLayer .choiceWidgetAnnotation select,
.annotationLayer .buttonWidgetAnnotation.checkBox input,
.annotationLayer .buttonWidgetAnnotation.radioButton input {
background-color: rgba(0, 54, 255, 0.13);
border: 1px solid transparent;
box-sizing: border-box;
font-size: 9px;
height: 100%;
margin: 0;
padding: 0 3px;
vertical-align: top;
width: 100%;
}
.annotationLayer .choiceWidgetAnnotation select option {
padding: 0;
}
.annotationLayer .buttonWidgetAnnotation.radioButton input {
border-radius: 50%;
}
.annotationLayer .textWidgetAnnotation textarea {
font: message-box;
font-size: 9px;
resize: none;
}
.annotationLayer .textWidgetAnnotation input[disabled],
.annotationLayer .textWidgetAnnotation textarea[disabled],
.annotationLayer .choiceWidgetAnnotation select[disabled],
.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled],
.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] {
background: none;
border: 1px solid transparent;
cursor: not-allowed;
}
.annotationLayer .textWidgetAnnotation input:hover,
.annotationLayer .textWidgetAnnotation textarea:hover,
.annotationLayer .choiceWidgetAnnotation select:hover,
.annotationLayer .buttonWidgetAnnotation.checkBox input:hover,
.annotationLayer .buttonWidgetAnnotation.radioButton input:hover {
border: 1px solid #000;
}
.annotationLayer .textWidgetAnnotation input:focus,
.annotationLayer .textWidgetAnnotation textarea:focus,
.annotationLayer .choiceWidgetAnnotation select:focus {
background: none;
border: 1px solid transparent;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after,
.annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before {
background-color: #000;
content: '';
display: block;
position: absolute;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after {
height: 80%;
left: 45%;
width: 1px;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before {
transform: rotate(45deg);
}
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after {
transform: rotate(-45deg);
}
.annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before {
border-radius: 50%;
height: 50%;
left: 30%;
top: 20%;
width: 50%;
}
.annotationLayer .textWidgetAnnotation input.comb {
font-family: monospace;
padding-left: 2px;
padding-right: 0;
}
.annotationLayer .textWidgetAnnotation input.comb:focus {
/*
* Letter spacing is placed on the right side of each character. Hence, the
* letter spacing of the last character may be placed outside the visible
* area, causing horizontal scrolling. We avoid this by extending the width
* when the element has focus and revert this when it loses focus.
*/
width: 115%;
}
.annotationLayer .buttonWidgetAnnotation.checkBox input,
.annotationLayer .buttonWidgetAnnotation.radioButton input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
padding: 0;
}
.annotationLayer .popupWrapper {
position: absolute;
width: 20em;
}
.annotationLayer .popup {
position: absolute;
z-index: 200;
max-width: 20em;
background-color: #ffff99;
box-shadow: 0 2px 5px #888;
border-radius: 2px;
padding: 6px;
margin-left: 5px;
cursor: pointer;
font: message-box;
font-size: 9px;
word-wrap: break-word;
}
.annotationLayer .popup > * {
font-size: 9px;
}
.annotationLayer .popup h1 {
display: inline-block;
}
.annotationLayer .popup span {
display: inline-block;
margin-left: 5px;
}
.annotationLayer .popup p {
border-top: 1px solid #333;
margin-top: 2px;
padding-top: 2px;
}
.annotationLayer .highlightAnnotation,
.annotationLayer .underlineAnnotation,
.annotationLayer .squigglyAnnotation,
.annotationLayer .strikeoutAnnotation,
.annotationLayer .freeTextAnnotation,
.annotationLayer .lineAnnotation svg line,
.annotationLayer .squareAnnotation svg rect,
.annotationLayer .circleAnnotation svg ellipse,
.annotationLayer .polylineAnnotation svg polyline,
.annotationLayer .polygonAnnotation svg polygon,
.annotationLayer .caretAnnotation,
.annotationLayer .inkAnnotation svg polyline,
.annotationLayer .stampAnnotation,
.annotationLayer .fileAttachmentAnnotation {
cursor: pointer;
}
.pdfViewer .canvasWrapper {
overflow: hidden;
}
.pdfViewer .page {
direction: ltr;
width: 816px;
height: 1056px;
margin: 1px auto -8px;
position: relative;
overflow: visible;
border: 9px solid transparent;
background-clip: content-box;
background-color: white;
}
.pdfViewer.removePageBorders .page {
margin: 0 auto 10px;
border: none;
}
.pdfViewer.singlePageView {
display: inline-block;
}
.pdfViewer.singlePageView .page {
margin: 0;
border: none;
}
.pdfViewer.scrollHorizontal, .pdfViewer.scrollWrapped, .spread {
margin-left: 3.5px;
margin-right: 3.5px;
text-align: center;
}
.pdfViewer.scrollHorizontal, .spread {
white-space: nowrap;
}
.pdfViewer.removePageBorders,
.pdfViewer.scrollHorizontal .spread,
.pdfViewer.scrollWrapped .spread {
margin-left: 0;
margin-right: 0;
}
.spread .page,
.pdfViewer.scrollHorizontal .page,
.pdfViewer.scrollWrapped .page,
.pdfViewer.scrollHorizontal .spread,
.pdfViewer.scrollWrapped .spread {
display: inline-block;
vertical-align: middle;
}
.spread .page,
.pdfViewer.scrollHorizontal .page,
.pdfViewer.scrollWrapped .page {
margin-left: -3.5px;
margin-right: -3.5px;
}
.pdfViewer.removePageBorders .spread .page,
.pdfViewer.removePageBorders.scrollHorizontal .page,
.pdfViewer.removePageBorders.scrollWrapped .page {
margin-left: 5px;
margin-right: 5px;
}
.pdfViewer .page canvas {
margin: 0;
display: block;
}
.pdfViewer .page canvas[hidden] {
display: none;
}
.pdfViewer .page .loadingIcon {
position: absolute;
display: block;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.pdfPresentationMode .pdfViewer {
margin-left: 0;
margin-right: 0;
}
.pdfPresentationMode .pdfViewer .page,
.pdfPresentationMode .pdfViewer .spread {
display: block;
}
.pdfPresentationMode .pdfViewer .page,
.pdfPresentationMode .pdfViewer.removePageBorders .page {
margin-left: auto;
margin-right: auto;
}
.pdfPresentationMode:-ms-fullscreen .pdfViewer .page {
margin-bottom: 100% !important;
}
.pdfPresentationMode:-webkit-full-screen .pdfViewer .page {
margin-bottom: 100%;
border: 0;
}
.pdfPresentationMode:-moz-full-screen .pdfViewer .page {
margin-bottom: 100%;
border: 0;
}
.pdfPresentationMode:fullscreen .pdfViewer .page {
margin-bottom: 100%;
border: 0;
}
.textLayer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0.2;
line-height: 1;
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;
}
.adf-highlight {
margin: -1px;
padding: 1px;
background-color: rgb(180, 0, 170);
border-radius: 4px;
&.adf-begin {
border-radius: 4px 0 0 4px;
}
&.adf-end {
border-radius: 0 4px 4px 0;
}
&.adf-middle {
border-radius: 0;
}
&.adf-selected {
background-color: rgb(0, 100, 0);
}
}
&::selection { background: rgb(0, 0, 255); }
&::-moz-selection { background: rgb(0, 0, 255); }
.adf-endOfContent {
display: block;
position: absolute;
left: 0;
top: 100%;
right: 0;
bottom: 0;
z-index: -1;
cursor: default;
user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
&.adf-active {
top: 0;
}
}
}
.adf-annotationLayer {
section {
position: absolute;
}
.adf-linkAnnotation {
& > a {
position: absolute;
font-size: 1em;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* stylelint-disable */
background: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7') 0 0 repeat;
/* stylelint-enable */
&:hover {
opacity: 0.2;
background: #ff0;
box-shadow: 0 2px 10px #ff0;
}
}
}
.adf-textAnnotation {
img {
position: absolute;
cursor: pointer;
}
}
.adf-popupWrapper {
position: absolute;
width: 20em;
}
.adf-popup {
position: absolute;
z-index: 200;
max-width: 20em;
background-color: #ffff99;
box-shadow: 0 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;
}
}
.adf-highlightAnnotation,
.adf-underlineAnnotation,
.adf-squigglyAnnotation,
.adf-strikeoutAnnotation,
.adf-fileAttachmentAnnotation {
cursor: pointer;
}
}
.adf-pdfViewer {
.canvasWrapper {
overflow: hidden;
}
.page {
direction: ltr;
width: 816px;
height: 1056px;
margin: 1px auto -8px;
position: relative;
overflow: visible;
border: 9px solid transparent;
background-clip: content-box;
background-color: white;
canvas {
margin: 0;
display: block;
}
.adf-loadingIcon {
position: absolute;
display: block;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
* {
padding: 0;
margin: 0;
}
}
&.adf-removePageBorders {
.adf-page {
margin: 0 auto 10px;
border: none;
}
}
.adf-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%;
}
}
}
.adf-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);
}
}
.adf-viewer-pdf-viewer {
overflow: auto;
-webkit-overflow-scrolling: touch;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
outline: none;
}
html[dir='ltr'] .adf-viewer-pdf-viewer {
box-shadow: inset 1px 0 0 hsla(0, 0%, 100%, 0.05);
}
html[dir='rtl'] .adf-viewer-pdf-viewer {
box-shadow: inset -1px 0 0 hsla(0, 0%, 100%, 0.05);
}
@@ -1,31 +0,0 @@
<div mat-dialog-title>
<mat-icon>lock</mat-icon>
</div>
<mat-dialog-content>
<form (submit)="submit()">
<mat-form-field class="adf-full-width">
<input matInput
data-automation-id='adf-password-dialog-input'
type="password"
placeholder="{{ 'ADF_VIEWER.PDF_DIALOG.PLACEHOLDER' | translate }}"
[formControl]="passwordFormControl" />
</mat-form-field>
<mat-error *ngIf="isError()" data-automation-id='adf-password-dialog-error'>{{ 'ADF_VIEWER.PDF_DIALOG.ERROR' | translate }}</mat-error>
</form>
</mat-dialog-content>
<mat-dialog-actions class="adf-dialog-buttons">
<span class="adf-fill-remaining-space"></span>
<button mat-button mat-dialog-close data-automation-id='adf-password-dialog-close'>{{ 'ADF_VIEWER.PDF_DIALOG.CLOSE' | translate }}</button>
<button mat-button
data-automation-id='adf-password-dialog-submit'
class="adf-dialog-action-button"
[disabled]="!isValid()"
(click)="submit()">
{{ 'ADF_VIEWER.PDF_DIALOG.SUBMIT' | translate }}
</button>
</mat-dialog-actions>
@@ -1,15 +0,0 @@
.adf-fill-remaining-space {
flex: 1 1 auto;
}
.adf-full-width {
width: 100%;
}
.adf-dialog-buttons button {
text-transform: uppercase;
}
.adf-dialog-action-button:enabled {
color: var(--theme-primary-color);
}
@@ -1,109 +0,0 @@
/*!
* @license
* Copyright 2019 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 } from '@angular/core/testing';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { PdfPasswordDialogComponent } from './pdf-viewer-password-dialog';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
declare const pdfjsLib: any;
describe('PdfPasswordDialogComponent', () => {
let component: PdfPasswordDialogComponent;
let fixture: ComponentFixture<PdfPasswordDialogComponent>;
let dialogRef: MatDialogRef<PdfPasswordDialogComponent>;
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
providers: [
{
provide: MAT_DIALOG_DATA,
useValue: {
reason: null
}
},
{
provide: MatDialogRef,
useValue: {
close: jasmine.createSpy('open')
}
}
]
});
beforeEach(() => {
fixture = TestBed.createComponent(PdfPasswordDialogComponent);
component = fixture.componentInstance;
dialogRef = TestBed.inject(MatDialogRef);
});
it('should have empty default value', () => {
fixture.detectChanges();
expect(component.passwordFormControl.value).toBe('');
});
describe('isError', () => {
beforeEach(() => {
fixture.detectChanges();
});
it('should return false', () => {
component.data.reason = pdfjsLib.PasswordResponses.NEED_PASSWORD;
expect(component.isError()).toBe(false);
});
it('should return true', () => {
component.data.reason = pdfjsLib.PasswordResponses.INCORRECT_PASSWORD;
expect(component.isError()).toBe(true);
});
});
describe('isValid', () => {
beforeEach(() => {
fixture.detectChanges();
});
it('should return false when input has no value', () => {
component.passwordFormControl.setValue('');
expect(component.isValid()).toBe(false);
});
it('should return true when input has a valid value', () => {
component.passwordFormControl.setValue('some-text');
expect(component.isValid()).toBe(true);
});
});
it('should close dialog with input value', () => {
fixture.detectChanges();
component.passwordFormControl.setValue('some-value');
component.submit();
expect(dialogRef.close).toHaveBeenCalledWith('some-value');
});
});
@@ -1,53 +0,0 @@
/*!
* @license
* Copyright 2019 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, Inject, OnInit, ViewEncapsulation } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { FormControl, Validators } from '@angular/forms';
declare const pdfjsLib: any;
@Component({
selector: 'adf-pdf-viewer-password-dialog',
templateUrl: './pdf-viewer-password-dialog.html',
styleUrls: ['./pdf-viewer-password-dialog.scss'],
encapsulation: ViewEncapsulation.None
})
export class PdfPasswordDialogComponent implements OnInit {
passwordFormControl: FormControl;
constructor(
private dialogRef: MatDialogRef<PdfPasswordDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {}
ngOnInit() {
this.passwordFormControl = new FormControl('', [Validators.required]);
}
isError(): boolean {
return this.data.reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD;
}
isValid(): boolean {
return !this.passwordFormControl.hasError('required');
}
submit(): void {
this.dialogRef.close(this.passwordFormControl.value);
}
}
@@ -1,5 +0,0 @@
<ng-container *ngIf="image$ | async as image">
<img [src]="image" [alt]="'ADF_VIEWER.SIDEBAR.THUMBNAILS.PAGE' | translate: { pageNum: page.id }"
title="{{ 'ADF_VIEWER.SIDEBAR.THUMBNAILS.PAGE' | translate: { pageNum: page.id } }}"
[attr.aria-label]="'ADF_VIEWER.SIDEBAR.THUMBNAILS.PAGE' | translate: { pageNum: page.id }">
</ng-container>
@@ -1,77 +0,0 @@
/*!
* @license
* Copyright 2019 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 { DomSanitizer } from '@angular/platform-browser';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PdfThumbComponent } from './pdf-viewer-thumb.component';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
describe('PdfThumbComponent', () => {
let fixture: ComponentFixture<PdfThumbComponent>;
let component: PdfThumbComponent;
const domSanitizer = {
bypassSecurityTrustUrl: () => 'image-data'
};
const width = 91;
const height = 119;
const page = {
id: 'pageId',
getPage: jasmine.createSpy('getPage').and.returnValue(Promise.resolve({
getViewport: () => ({ height: width, width: height }),
render: jasmine.createSpy('render').and.returnValue({ promise: Promise.resolve() })
})),
getWidth: jasmine.createSpy('getWidth').and.returnValue(width),
getHeight: jasmine.createSpy('getHeight').and.returnValue(height)
};
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
providers: [
{ provide: DomSanitizer, useValue: domSanitizer }
]
});
beforeEach(() => {
fixture = TestBed.createComponent(PdfThumbComponent);
component = fixture.componentInstance;
});
it('should have resolve image data', (done) => {
component.page = page;
fixture.detectChanges();
component.image$.then((result) => {
expect(result).toBe('image-data');
done();
});
});
it('should focus element', () => {
component.page = page;
fixture.detectChanges();
component.focus();
expect(fixture.debugElement.nativeElement.id).toBe(document.activeElement.id);
});
});
@@ -1,68 +0,0 @@
/*!
* @license
* Copyright 2019 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, OnInit, ViewEncapsulation } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { FocusableOption } from '@angular/cdk/a11y';
@Component({
selector: 'adf-pdf-thumb',
templateUrl: './pdf-viewer-thumb.component.html',
encapsulation: ViewEncapsulation.None,
host: { tabindex: '0'}
})
export class PdfThumbComponent implements OnInit, FocusableOption {
@Input()
page: any = null;
image$: Promise<string>;
constructor(private sanitizer: DomSanitizer, private element: ElementRef) {
}
ngOnInit() {
this.image$ = this.page.getPage().then((page) => this.getThumb(page));
}
focus() {
this.element.nativeElement.focus();
}
private getThumb(page): Promise<string> {
const viewport = page.getViewport({ scale: 1 });
const canvas = this.getCanvas();
const scale = Math.min((canvas.height / viewport.height), (canvas.width / viewport.width));
return page.render({
canvasContext: canvas.getContext('2d'),
viewport: page.getViewport({ scale })
}).promise.then(() => {
const imageSource = canvas.toDataURL();
return this.sanitizer.bypassSecurityTrustUrl(imageSource);
});
}
private getCanvas(): HTMLCanvasElement {
const canvas = document.createElement('canvas');
canvas.width = this.page.getWidth();
canvas.height = this.page.getHeight();
return canvas;
}
}
@@ -1,12 +0,0 @@
<div class="adf-pdf-thumbnails__content"
data-automation-id='adf-thumbnails-content'
[style.height.px]="virtualHeight"
[style.transform]="'translate(-50%, ' + translateY + 'px)'">
<adf-pdf-thumb *ngFor="let page of renderItems; trackBy: trackByFn"
class="adf-pdf-thumbnails__thumb"
[id]="page.id"
[ngClass]="{'adf-pdf-thumbnails__thumb--selected' : isSelected(page.id)}"
[page]="page"
(click)="goTo(page.id)">
</adf-pdf-thumb>
</div>
@@ -1,30 +0,0 @@
.adf-pdf-thumbnails {
display: block;
overflow: hidden;
overflow-y: auto;
height: 100%;
position: relative;
&__content {
top: 5px;
left: 50%;
height: 0;
position: absolute;
}
&__thumb {
cursor: pointer;
display: block;
width: 91px;
background: var(--theme-background-color);
margin-bottom: 15px;
}
&__thumb:hover {
box-shadow: 0 0 5px 0 var(--theme-text-bold-color);
}
&__thumb--selected {
border: 2px solid var(--theme-accent-color-a200);
}
}
@@ -1,229 +0,0 @@
/*!
* @license
* Copyright 2019 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 } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { PdfThumbListComponent } from './pdf-viewer-thumbnails.component';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { DOWN_ARROW, UP_ARROW, ESCAPE } from '@angular/cdk/keycodes';
declare const pdfjsViewer: any;
describe('PdfThumbListComponent', () => {
let fixture: ComponentFixture<PdfThumbListComponent>;
let component: PdfThumbListComponent;
const page = (id) => ({
id,
getPage: Promise.resolve()
});
const viewerMock = {
_currentPageNumber: null,
set currentPageNumber(pageNum) {
this._currentPageNumber = pageNum;
/* cspell:disable-next-line */
this.eventBus.dispatch('pagechanging', { pageNumber: pageNum });
},
get currentPageNumber() {
return this._currentPageNumber;
},
pdfDocument: {
getPage: () => Promise.resolve({
getViewport: () => ({ height: 421, width: 335 }),
render: jasmine.createSpy('render').and.returnValue({ promise: Promise.resolve() })
})
},
_pages: [
page(1), page(2), page(3), page(4),
page(5), page(6), page(7), page(8),
page(9), page(10), page(11), page(12),
page(13), page(14), page(15), page(16)
],
eventBus: new pdfjsViewer.EventBus()
};
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(PdfThumbListComponent);
component = fixture.componentInstance;
component.pdfViewer = viewerMock;
// provide scrollable container
fixture.nativeElement.style.display = 'block';
fixture.nativeElement.style.height = '700px';
fixture.nativeElement.style.overflow = 'scroll';
const content = fixture.debugElement.query(By.css('.adf-pdf-thumbnails__content')).nativeElement;
content.style.height = '2000px';
content.style.position = 'unset';
});
it('should render initial rage of items', () => {
fixture.nativeElement.scrollTop = 0;
fixture.detectChanges();
const renderedIds = component.renderItems.map((item) => item.id);
// eslint-disable-next-line no-underscore-dangle
const rangeIds = viewerMock._pages.slice(0, 6).map((item) => item.id);
expect(renderedIds).toEqual(rangeIds);
});
it('should render next range on scroll', () => {
component.currentHeight = 114;
fixture.nativeElement.scrollTop = 700;
fixture.detectChanges();
const renderedIds = component.renderItems.map((item) => item.id);
// eslint-disable-next-line no-underscore-dangle
const rangeIds = viewerMock._pages.slice(5, 12).map((item) => item.id);
expect(renderedIds).toEqual(rangeIds);
});
it('should render items containing current document page', () => {
fixture.detectChanges();
const renderedIds = component.renderItems.map((item) => item.id);
expect(renderedIds).not.toContain(10);
component.scrollInto(10);
const newRenderedIds = component.renderItems.map((item) => item.id);
expect(newRenderedIds).toContain(10);
});
it('should not change items if range contains current document page', () => {
fixture.nativeElement.scrollTop = 1700;
fixture.detectChanges();
const renderedIds = component.renderItems.map((item) => item.id);
expect(renderedIds).toContain(12);
/* cspell:disable-next-line */
viewerMock.eventBus.dispatch('pagechanging', { pageNumber: 12 });
const newRenderedIds = component.renderItems.map((item) => item.id);
expect(newRenderedIds).toContain(12);
});
it('should scroll thumbnail height amount to buffer thumbnail onPageChange event', () => {
spyOn(component, 'scrollInto');
fixture.detectChanges();
expect(component.renderItems[component.renderItems.length - 1].id).toBe(6);
expect(fixture.debugElement.nativeElement.scrollTop).toBe(0);
component.pdfViewer.eventBus.dispatch('pagechanging', { pageNumber: 6 });
expect(component.scrollInto).not.toHaveBeenCalled();
expect(fixture.debugElement.nativeElement.scrollTop).toBe(0);
});
it('should set active current page on onPageChange event', () => {
fixture.detectChanges();
component.pdfViewer.eventBus.dispatch('pagechanging', { pageNumber: 6 });
expect(document.activeElement.id).toBe('6');
});
it('should return current viewed page as selected', () => {
fixture.nativeElement.scrollTop = 0;
fixture.detectChanges();
viewerMock.currentPageNumber = 2;
expect(component.isSelected(2)).toBe(true);
});
it('should go to selected page', () => {
fixture.detectChanges();
component.goTo(12);
expect(viewerMock.currentPageNumber).toBe(12);
});
describe('Keyboard events', () => {
it('should select next page in the list on DOWN_ARROW event', () => {
const event = new KeyboardEvent('keydown', {keyCode: DOWN_ARROW} as KeyboardEventInit);
fixture.detectChanges();
component.goTo(1);
expect(document.activeElement.id).toBe('1');
fixture.debugElement.nativeElement.dispatchEvent(event);
expect(document.activeElement.id).toBe('2');
});
it('should select previous page in the list on UP_ARROW event', () => {
const event = new KeyboardEvent('keydown', {keyCode: UP_ARROW} as KeyboardEventInit);
fixture.detectChanges();
component.goTo(2);
expect(document.activeElement.id).toBe('2');
fixture.debugElement.nativeElement.dispatchEvent(event);
expect(document.activeElement.id).toBe('1');
});
it('should not select previous page if it is the first page', () => {
const event = new KeyboardEvent('keydown', {keyCode: UP_ARROW} as KeyboardEventInit);
fixture.detectChanges();
component.goTo(1);
expect(document.activeElement.id).toBe('1');
fixture.debugElement.nativeElement.dispatchEvent(event);
expect(document.activeElement.id).toBe('1');
});
it('should not select next item if it is the last page', () => {
const event = new KeyboardEvent('keydown', {keyCode: DOWN_ARROW} as KeyboardEventInit);
fixture.detectChanges();
component.scrollInto(16);
fixture.detectChanges();
component.pdfViewer.eventBus.dispatch('pagechanging', { pageNumber: 16 });
expect(document.activeElement.id).toBe('16');
fixture.debugElement.nativeElement.dispatchEvent(event);
expect(document.activeElement.id).toBe('16');
});
it('should emit on ESCAPE event', () => {
const event = new KeyboardEvent('keydown', {keyCode: ESCAPE} as KeyboardEventInit);
spyOn(component.close, 'emit');
fixture.detectChanges();
fixture.debugElement.nativeElement.dispatchEvent(event);
expect(component.close.emit).toHaveBeenCalled();
});
});
});
@@ -1,228 +0,0 @@
/*!
* @license
* Copyright 2019 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, ContentChild, TemplateRef, HostListener, OnInit,
AfterViewInit, ElementRef, OnDestroy, ViewEncapsulation, EventEmitter, Output, Inject, ViewChildren, QueryList
} from '@angular/core';
import { ESCAPE, UP_ARROW, DOWN_ARROW } from '@angular/cdk/keycodes';
import { DOCUMENT } from '@angular/common';
import { FocusKeyManager } from '@angular/cdk/a11y';
import { PdfThumbComponent } from './pdf-viewer-thumb.component';
import { delay } from 'rxjs/operators';
@Component({
selector: 'adf-pdf-thumbnails',
templateUrl: './pdf-viewer-thumbnails.component.html',
styleUrls: ['./pdf-viewer-thumbnails.component.scss'],
host: { class: 'adf-pdf-thumbnails' },
encapsulation: ViewEncapsulation.None
})
export class PdfThumbListComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() pdfViewer: any;
@Output()
close = new EventEmitter<void>();
virtualHeight: number = 0;
translateY: number = 0;
renderItems = [];
width: number = 91;
currentHeight: number = 0;
private items = [];
private margin: number = 15;
private itemHeight: number = 114 + this.margin;
private previouslyFocusedElement: HTMLElement | null = null;
private keyManager: FocusKeyManager<PdfThumbComponent>;
@ContentChild(TemplateRef)
template: any;
@ViewChildren(PdfThumbComponent)
thumbsList: QueryList<PdfThumbComponent>;
@HostListener('keydown', ['$event'])
onKeydown(event: KeyboardEvent): void {
const keyCode = event.keyCode;
if (keyCode === UP_ARROW && this.canSelectPreviousItem()) {
this.pdfViewer.currentPageNumber -= 1;
}
if (keyCode === DOWN_ARROW && this.canSelectNextItem()) {
this.pdfViewer.currentPageNumber += 1;
}
if (keyCode === ESCAPE) {
this.close.emit();
}
this.keyManager.setFocusOrigin('keyboard');
event.preventDefault();
}
@HostListener('window:resize')
onResize() {
this.calculateItems();
}
constructor(private element: ElementRef, @Inject(DOCUMENT) private document: any) {
this.calculateItems = this.calculateItems.bind(this);
this.onPageChange = this.onPageChange.bind(this);
}
ngOnInit() {
/* cspell:disable-next-line */
this.pdfViewer.eventBus.on('pagechanging', this.onPageChange);
this.element.nativeElement.addEventListener('scroll', this.calculateItems, true);
this.setHeight(this.pdfViewer.currentPageNumber);
this.items = this.getPages();
this.calculateItems();
this.previouslyFocusedElement = this.document.activeElement as HTMLElement;
}
ngAfterViewInit() {
this.keyManager = new FocusKeyManager(this.thumbsList);
this.thumbsList.changes
.pipe(delay(0))
.subscribe(() => this.keyManager.setActiveItem(this.getPageIndex(this.pdfViewer.currentPageNumber)));
setTimeout(() => {
this.scrollInto(this.pdfViewer.currentPageNumber);
this.keyManager.setActiveItem(this.getPageIndex(this.pdfViewer.currentPageNumber));
}, 0);
}
ngOnDestroy() {
this.element.nativeElement.removeEventListener('scroll', this.calculateItems, true);
/* cspell:disable-next-line */
this.pdfViewer.eventBus.on('pagechanging', this.onPageChange);
if (this.previouslyFocusedElement) {
this.previouslyFocusedElement.focus();
this.previouslyFocusedElement = null;
}
}
trackByFn(_: number, item: any): number {
return item.id;
}
isSelected(pageNumber: number) {
return this.pdfViewer.currentPageNumber === pageNumber;
}
goTo(pageNumber: number) {
this.pdfViewer.currentPageNumber = pageNumber;
}
scrollInto(pageNumber: number) {
if (this.items.length) {
const index: number = this.items.findIndex((element) => element.id === pageNumber);
if (index < 0 || index >= this.items.length) {
return;
}
this.element.nativeElement.scrollTop = index * this.itemHeight;
this.calculateItems();
}
}
getPages() {
// eslint-disable-next-line no-underscore-dangle
return this.pdfViewer._pages.map((page) => ({
id: page.id,
getWidth: () => this.width,
getHeight: () => this.currentHeight,
getPage: () => this.pdfViewer.pdfDocument.getPage(page.id)
}));
}
private setHeight(id): number {
const height = this.pdfViewer.pdfDocument.getPage(id).then((page) => this.calculateHeight(page));
return height;
}
private calculateHeight(page) {
const viewport = page.getViewport({ scale: 1 });
const pageRatio = viewport.width / viewport.height;
const height = Math.floor(this.width / pageRatio);
this.currentHeight = height;
this.itemHeight = height + this.margin;
}
private calculateItems() {
const { element, viewPort, itemsInView } = this.getContainerSetup();
const indexByScrollTop = element.scrollTop / viewPort * this.items.length / itemsInView;
const start = Math.floor(indexByScrollTop);
const end = Math.ceil(indexByScrollTop) + (itemsInView);
this.translateY = this.itemHeight * Math.ceil(start);
this.virtualHeight = this.itemHeight * this.items.length - this.translateY;
this.renderItems = this.items.slice(start, end);
}
private getContainerSetup() {
const element = this.element.nativeElement;
const elementRec = element.getBoundingClientRect();
const itemsInView = Math.ceil(elementRec.height / this.itemHeight);
const viewPort = (this.itemHeight * this.items.length) / itemsInView;
return {
element,
viewPort,
itemsInView
};
}
private onPageChange(event) {
const index = this.renderItems.findIndex((element) => element.id === event.pageNumber);
if (index < 0) {
this.scrollInto(event.pageNumber);
}
if (index >= this.renderItems.length - 1) {
this.element.nativeElement.scrollTop += this.itemHeight;
}
this.keyManager.setActiveItem(this.getPageIndex(event.pageNumber));
}
private getPageIndex(pageNumber: number): number {
const thumbsListArray = this.thumbsList.toArray();
return thumbsListArray.findIndex(el => el.page.id === pageNumber);
}
private canSelectNextItem(): boolean {
return this.pdfViewer.currentPageNumber !== this.pdfViewer.pagesCount;
}
private canSelectPreviousItem(): boolean {
return this.pdfViewer.currentPageNumber !== 1;
}
}
@@ -1,116 +0,0 @@
<div class="adf-pdf-viewer__container">
<ng-container *ngIf="showThumbnails">
<div class="adf-pdf-viewer__thumbnails">
<div class="adf-thumbnails-template__container">
<div class="adf-thumbnails-template__buttons">
<button
mat-icon-button
data-automation-id='adf-thumbnails-close'
(click)="toggleThumbnails()"
[attr.aria-label]="'ADF_VIEWER.ARIA.THUMBNAILS_PANLEL_CLOSE' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.CLOSE' | translate }}">
<mat-icon>close</mat-icon>
</button>
</div>
<ng-container *ngIf="thumbnailsTemplate">
<ng-container *ngTemplateOutlet="thumbnailsTemplate;context:pdfThumbnailsContext"></ng-container>
</ng-container>
<ng-container *ngIf="!thumbnailsTemplate">
<adf-pdf-thumbnails (close)="toggleThumbnails()" [pdfViewer]="pdfViewer"></adf-pdf-thumbnails>
</ng-container>
</div>
</div>
</ng-container>
<div class="adf-pdf-viewer__content">
<div [id]="randomPdfId+'-viewer-pdf-viewer'" class="adf-viewer-pdf-viewer" (window:resize)="onResize()">
<div [id]="randomPdfId+'-viewer-viewerPdf'" class="adf-pdfViewer" role="document" tabindex="0" aria-expanded="true">
<div id="loader-container" class="adf-loader-container">
<div class="adf-loader-item">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="adf-pdf-viewer__toolbar" *ngIf="showToolbar">
<adf-toolbar>
<ng-container *ngIf="allowThumbnails">
<button mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ARIA.THUMBNAILS' | translate"
[attr.aria-expanded]="showThumbnails"
data-automation-id="adf-thumbnails-button"
[disabled]="isPanelDisabled"
(click)="toggleThumbnails()">
<mat-icon>dashboard</mat-icon>
</button>
<adf-toolbar-divider></adf-toolbar-divider>
</ng-container>
<button
id="viewer-previous-page-button"
title="{{ 'ADF_VIEWER.ARIA.PREVIOUS_PAGE' | translate }}"
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"
title="{{ 'ADF_VIEWER.ARIA.NEXT_PAGE' | translate }}"
attr.aria-label="{{ 'ADF_VIEWER.ARIA.NEXT_PAGE' | translate }}"
mat-icon-button
(click)="nextPage()">
<mat-icon>keyboard_arrow_down</mat-icon>
</button>
<div class="adf-pdf-viewer__toolbar-page-selector">
<span>{{ 'ADF_VIEWER.PAGE_LABEL.SHOWING' | translate }}</span>
<input #page
type="text"
data-automation-id="adf-page-selector"
pattern="-?[0-9]*(\.[0-9]+)?"
value="{{ displayPage }}"
(keyup.enter)="inputPage(page.value)">
<span>{{ 'ADF_VIEWER.PAGE_LABEL.OF' | translate }} {{ totalPages }}</span>
</div>
<div class="adf-viewer__toolbar-page-scale" data-automation-id="adf-page-scale">
{{ currentScaleText }}
</div>
<button
id="viewer-zoom-in-button"
title="{{ 'ADF_VIEWER.ARIA.ZOOM_IN' | translate }}"
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"
title="{{ 'ADF_VIEWER.ARIA.ZOOM_OUT' | translate }}"
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"
title="{{ 'ADF_VIEWER.ARIA.FIT_PAGE' | translate }}"
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>
@@ -1,91 +0,0 @@
.adf-viewer-content-container {
width: 100%;
height: 100%;
}
.adf-pdf-viewer {
width: 100%;
height: 100%;
margin: 0;
.adf-loader-container {
display: flex;
flex-direction: row;
height: 100%;
}
&__thumbnails {
position: relative;
height: 100%;
width: 190px;
background-color: rgba(0, 0, 0, 0.12);
}
.adf-thumbnails-template {
&__container {
display: flex;
flex-direction: column;
height: 100%;
}
&__buttons {
height: 45px;
justify-content: flex-end;
align-items: flex-end;
display: flex;
color: var(--theme-text-color);
}
}
&__container {
display: flex;
height: 100vh;
}
&__content {
flex: 1 1 auto;
position: relative;
}
.adf-loader-item {
margin: auto;
max-height: 100px;
max-width: 300px;
}
&__toolbar {
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
.adf-toolbar .mat-toolbar {
max-height: 48px;
background-color: var(--theme-card-bg-color);
border-width: 0;
border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 20.4), 0 0 2px 0 rgba(0, 0, 0, 0.12);
}
&-page-selector {
padding-left: 10px;
padding-right: 10px;
font-size: var(--theme-body-1-font-size);
& > input {
border: 1px solid var(--theme-border-color);
background-color: var(--theme-card-bg-color);
color: inherit;
font-size: var(--theme-body-1-font-size);
padding: 5px;
height: 24px;
line-height: 24px;
text-align: right;
width: 33px;
margin-right: 4px;
outline-width: 1px;
outline-color: gray;
}
}
}
}
@@ -1,802 +0,0 @@
/*!
* @license
* Copyright 2019 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, SimpleChange, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { EventMock } from '../../mock/event.mock';
import { RenderingQueueServices } from '../services/rendering-queue.services';
import { PdfViewerComponent } from './pdf-viewer.component';
import { RIGHT_ARROW, LEFT_ARROW } from '@angular/cdk/keycodes';
import { MatDialog } from '@angular/material/dialog';
import { of } from 'rxjs';
import { setupTestBed } from '../../testing/setup-test-bed';
import { take } from 'rxjs/operators';
import { AppConfigService } from '../../app-config/app-config.service';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
declare const pdfjsLib: any;
@Component({
selector: 'adf-test-dialog-component',
template: ''
})
class TestDialogComponent {
}
@Component({
template: `
<adf-pdf-viewer [allowThumbnails]="true"
[showToolbar]="true"
[urlFile]="urlFile">
</adf-pdf-viewer>
`
})
class UrlTestComponent {
@ViewChild(PdfViewerComponent, { static: true })
pdfViewerComponent: PdfViewerComponent;
urlFile: any;
constructor() {
this.urlFile = './fake-test-file.pdf';
}
}
@Component({
template: `
<adf-pdf-viewer [allowThumbnails]="true"
[showToolbar]="true"
[urlFile]="urlFile">
</adf-pdf-viewer>
`
})
class UrlTestPasswordComponent {
@ViewChild(PdfViewerComponent, { static: true })
pdfViewerComponent: PdfViewerComponent;
urlFile: any;
constructor() {
this.urlFile = './fake-test-password-file.pdf';
}
}
@Component({
template: `
<adf-pdf-viewer [allowThumbnails]="true"
[showToolbar]="true"
[blobFile]="blobFile">
</adf-pdf-viewer>
`
})
class BlobTestComponent {
@ViewChild(PdfViewerComponent, { static: true })
pdfViewerComponent: PdfViewerComponent;
blobFile: any;
constructor() {
this.blobFile = this.createFakeBlob();
}
createFakeBlob(): Blob {
const 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' });
}
}
describe('Test PdfViewer component', () => {
let component: PdfViewerComponent;
let fixture: ComponentFixture<PdfViewerComponent>;
let element: HTMLElement;
let change: any;
let dialog: MatDialog;
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
declarations: [
TestDialogComponent,
UrlTestComponent,
UrlTestPasswordComponent,
BlobTestComponent
],
providers: [
{
provide: MatDialog, useValue: {
open: () => {
}
}
},
RenderingQueueServices
]
});
beforeEach((done) => {
fixture = TestBed.createComponent(PdfViewerComponent);
dialog = TestBed.inject(MatDialog);
element = fixture.nativeElement;
component = fixture.componentInstance;
component.showToolbar = true;
component.inputPage('1');
component.currentScale = 1;
fixture.detectChanges();
fixture.whenStable().then(() => {
done();
});
});
describe('User interaction', () => {
let fixtureUrlTestComponent: ComponentFixture<UrlTestComponent>;
let componentUrlTestComponent: UrlTestComponent;
let elementUrlTestComponent: HTMLElement;
beforeEach((done) => {
fixtureUrlTestComponent = TestBed.createComponent(UrlTestComponent);
componentUrlTestComponent = fixtureUrlTestComponent.componentInstance;
elementUrlTestComponent = fixtureUrlTestComponent.nativeElement;
fixtureUrlTestComponent.detectChanges();
componentUrlTestComponent.pdfViewerComponent.rendered
.pipe(take(1))
.subscribe(() => {
done();
});
});
afterEach(() => {
document.body.removeChild(elementUrlTestComponent);
});
it('should Total number of pages be loaded', (done) => {
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.totalPages).toBe(6);
done();
});
}, 55000);
it('should nextPage move to the next page', (done) => {
const nextPageButton: any = elementUrlTestComponent.querySelector('#viewer-next-page-button');
nextPageButton.click();
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.displayPage).toBe(2);
done();
});
}, 55000);
it('should event RIGHT_ARROW keyboard change pages', fakeAsync(() => {
fixtureUrlTestComponent.whenStable();
fixtureUrlTestComponent.detectChanges();
EventMock.keyDown(RIGHT_ARROW);
tick(250);
expect(componentUrlTestComponent.pdfViewerComponent.displayPage).toBe(2);
}));
it('should event LEFT_ARROW keyboard change pages', (done) => {
component.inputPage('2');
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
EventMock.keyDown(LEFT_ARROW);
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.displayPage).toBe(1);
done();
});
});
}, 55000);
it('should previous page move to the previous page', (done) => {
const previousPageButton: any = elementUrlTestComponent.querySelector('#viewer-previous-page-button');
const nextPageButton: any = elementUrlTestComponent.querySelector('#viewer-next-page-button');
nextPageButton.click();
nextPageButton.click();
previousPageButton.click();
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.displayPage).toBe(2);
done();
});
}, 55000);
it('should previous page not move to the previous page if is page 1', (done) => {
component.previousPage();
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.displayPage).toBe(1);
done();
});
}, 55000);
it('should Input page move to the inserted page', (done) => {
componentUrlTestComponent.pdfViewerComponent.inputPage('2');
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.displayPage).toBe(2);
done();
});
}, 55000);
describe('Zoom', () => {
it('should zoom in increment the scale value', fakeAsync(() => {
spyOn(componentUrlTestComponent.pdfViewerComponent.pdfViewer, 'forceRendering').and.callFake(() => {
});
const zoomInButton: any = elementUrlTestComponent.querySelector('#viewer-zoom-in-button');
tick(250);
const zoomBefore = componentUrlTestComponent.pdfViewerComponent.currentScale;
zoomInButton.click();
expect(componentUrlTestComponent.pdfViewerComponent.currentScaleMode).toBe('auto');
const currentZoom = componentUrlTestComponent.pdfViewerComponent.currentScale;
expect(zoomBefore < currentZoom).toBe(true);
}), 55000);
it('should zoom out decrement the scale value', fakeAsync(() => {
spyOn(componentUrlTestComponent.pdfViewerComponent.pdfViewer, 'forceRendering').and.callFake(() => {
});
const zoomOutButton: any = elementUrlTestComponent.querySelector('#viewer-zoom-out-button');
tick(250);
const zoomBefore = componentUrlTestComponent.pdfViewerComponent.currentScale;
zoomOutButton.click();
expect(componentUrlTestComponent.pdfViewerComponent.currentScaleMode).toBe('auto');
const currentZoom = componentUrlTestComponent.pdfViewerComponent.currentScale;
expect(zoomBefore > currentZoom).toBe(true);
}), 55000);
it('should it-in button toggle page-fit and auto scale mode', fakeAsync(() => {
spyOn(componentUrlTestComponent.pdfViewerComponent.pdfViewer, 'forceRendering').and.callFake(() => {
});
const itPage: any = elementUrlTestComponent.querySelector('#viewer-scale-page-button');
tick(250);
expect(componentUrlTestComponent.pdfViewerComponent.currentScaleMode).toBe('init');
itPage.click();
expect(componentUrlTestComponent.pdfViewerComponent.currentScaleMode).toBe('page-fit');
itPage.click();
expect(componentUrlTestComponent.pdfViewerComponent.currentScaleMode).toBe('auto');
itPage.click();
expect(componentUrlTestComponent.pdfViewerComponent.currentScaleMode).toBe('page-fit');
}), 55000);
});
describe('Resize interaction', () => {
it('should resize event trigger setScaleUpdatePages', (done) => {
spyOn(componentUrlTestComponent.pdfViewerComponent, 'onResize');
EventMock.resizeMobileView();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.onResize).toHaveBeenCalled();
done();
});
}, 55000);
});
describe('Thumbnails', () => {
it('should have own context', (done) => {
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.pdfThumbnailsContext.viewer).not.toBeNull();
done();
});
}, 55000);
it('should open thumbnails panel', (done) => {
expect(elementUrlTestComponent.querySelector('.adf-pdf-viewer__thumbnails')).toBeNull();
componentUrlTestComponent.pdfViewerComponent.toggleThumbnails();
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(elementUrlTestComponent.querySelector('.adf-pdf-viewer__thumbnails')).not.toBeNull();
done();
});
}, 55000);
});
describe('Viewer events', () => {
it('should react on the emit of pageChange event', (done) => {
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
const args = {
pageNumber: 6,
source: {
container: document.getElementById(`${componentUrlTestComponent.pdfViewerComponent.randomPdfId}-viewer-pdf-viewer`)
}
};
/* cspell:disable-next-line */
componentUrlTestComponent.pdfViewerComponent.pdfViewer.eventBus.dispatch('pagechanging', args);
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.displayPage).toBe(6);
expect(componentUrlTestComponent.pdfViewerComponent.page).toBe(6);
done();
});
});
}, 55000);
it('should react on the emit of pagesLoaded event', (done) => {
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.isPanelDisabled).toBeFalsy();
const args = {
pagesCount: 10,
source: {
container: document.getElementById(`${componentUrlTestComponent.pdfViewerComponent.randomPdfId}-viewer-pdf-viewer`)
}
};
/* cspell:disable-next-line */
componentUrlTestComponent.pdfViewerComponent.pdfViewer.eventBus.dispatch('pagesloaded', args);
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.isPanelDisabled).toBe(false);
done();
});
});
}, 55000);
it('should check if page fits to documentContainer', (done) => {
spyOn(componentUrlTestComponent.pdfViewerComponent, 'checkPageFitInContainer');
componentUrlTestComponent.pdfViewerComponent.scalePage('auto');
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.checkPageFitInContainer).toHaveBeenCalled();
done();
});
});
});
});
it('should Loader be present', () => {
expect(element.querySelector('.adf-loader-container')).not.toBeNull();
});
describe('Zoom customization', () => {
describe('custom value', () => {
let fixtureUrlTestComponent: ComponentFixture<UrlTestComponent>;
let componentUrlTestComponent: UrlTestComponent;
let elementUrlTestComponent: HTMLElement;
beforeEach((done) => {
const appConfig: AppConfigService = TestBed.inject(AppConfigService);
appConfig.config['adf-viewer.pdf-viewer-scaling'] = 80;
fixtureUrlTestComponent = TestBed.createComponent(UrlTestComponent);
componentUrlTestComponent = fixtureUrlTestComponent.componentInstance;
elementUrlTestComponent = fixtureUrlTestComponent.nativeElement;
fixtureUrlTestComponent.detectChanges();
componentUrlTestComponent.pdfViewerComponent.rendered
.pipe(take(1))
.subscribe(() => {
done();
});
});
afterEach(() => {
document.body.removeChild(elementUrlTestComponent);
});
it('should use the custom zoom if it is present in the app.config', (done) => {
spyOn(componentUrlTestComponent.pdfViewerComponent.pdfViewer, 'forceRendering').and.callFake(() => {
});
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.currentScale).toBe(0.8);
done();
});
});
});
describe('less than the minimum allowed value', () => {
let fixtureUrlTestComponent: ComponentFixture<UrlTestComponent>;
let componentUrlTestComponent: UrlTestComponent;
let elementUrlTestComponent: HTMLElement;
beforeEach((done) => {
const appConfig: AppConfigService = TestBed.inject(AppConfigService);
appConfig.config['adf-viewer.pdf-viewer-scaling'] = 10;
fixtureUrlTestComponent = TestBed.createComponent(UrlTestComponent);
componentUrlTestComponent = fixtureUrlTestComponent.componentInstance;
elementUrlTestComponent = fixtureUrlTestComponent.nativeElement;
fixtureUrlTestComponent.detectChanges();
componentUrlTestComponent.pdfViewerComponent.rendered
.pipe(take(1))
.subscribe(() => {
done();
});
});
afterEach(() => {
document.body.removeChild(elementUrlTestComponent);
});
it('should use the minimum scale zoom if the value given in app.config is less than the minimum allowed scale', (done) => {
spyOn(componentUrlTestComponent.pdfViewerComponent.pdfViewer, 'forceRendering').and.callFake(() => {
});
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.currentScale).toBe(0.25);
done();
});
});
});
describe('greater than the maximum allowed value', () => {
let fixtureUrlTestComponent: ComponentFixture<UrlTestComponent>;
let componentUrlTestComponent: UrlTestComponent;
let elementUrlTestComponent: HTMLElement;
beforeEach((done) => {
const appConfig: AppConfigService = TestBed.inject(AppConfigService);
appConfig.config['adf-viewer.pdf-viewer-scaling'] = 55555;
fixtureUrlTestComponent = TestBed.createComponent(UrlTestComponent);
componentUrlTestComponent = fixtureUrlTestComponent.componentInstance;
elementUrlTestComponent = fixtureUrlTestComponent.nativeElement;
fixtureUrlTestComponent.detectChanges();
componentUrlTestComponent.pdfViewerComponent.rendered
.pipe(take(1))
.subscribe(() => {
done();
});
});
afterEach(() => {
document.body.removeChild(elementUrlTestComponent);
});
it('should use the maximum scale zoom if the value given in app.config is greater than the maximum allowed scale', (done) => {
spyOn(componentUrlTestComponent.pdfViewerComponent.pdfViewer, 'forceRendering').and.callFake(() => {
});
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(componentUrlTestComponent.pdfViewerComponent.currentScale).toBe(10);
done();
});
});
});
});
describe('Required values', () => {
it('should thrown an error If urlFile is not present', () => {
change = new SimpleChange(null, null, true);
expect(() => {
component.ngOnChanges({ urlFile: change });
}).toThrow(new Error('Attribute urlFile or blobFile is required'));
});
it('should If blobFile is not present thrown an error ', () => {
change = new SimpleChange(null, null, true);
expect(() => {
component.ngOnChanges({ blobFile: change });
}).toThrow(new Error('Attribute urlFile or blobFile is required'));
});
});
describe('View with url file', () => {
let fixtureUrlTestComponent: ComponentFixture<UrlTestComponent>;
let elementUrlTestComponent: HTMLElement;
beforeEach((done) => {
fixtureUrlTestComponent = TestBed.createComponent(UrlTestComponent);
elementUrlTestComponent = fixtureUrlTestComponent.nativeElement;
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
done();
});
});
afterEach(() => {
document.body.removeChild(elementUrlTestComponent);
fixture.destroy();
});
it('should Canvas be present', (done) => {
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(elementUrlTestComponent.querySelector('.adf-pdfViewer')).not.toBeNull();
expect(elementUrlTestComponent.querySelector('.adf-viewer-pdf-viewer')).not.toBeNull();
done();
});
}, 55000);
it('should Input Page elements be present', (done) => {
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
/* cspell:disable-next-line */
expect(elementUrlTestComponent.querySelector('.viewer-pagenumber-input')).toBeDefined();
expect(elementUrlTestComponent.querySelector('.viewer-total-pages')).toBeDefined();
expect(elementUrlTestComponent.querySelector('#viewer-previous-page-button')).not.toBeNull();
expect(elementUrlTestComponent.querySelector('#viewer-next-page-button')).not.toBeNull();
done();
});
}, 55000);
it('should Toolbar be hide if showToolbar is false', (done) => {
component.showToolbar = false;
fixtureUrlTestComponent.detectChanges();
fixtureUrlTestComponent.whenStable().then(() => {
expect(elementUrlTestComponent.querySelector('.viewer-toolbar-command')).toBeNull();
expect(elementUrlTestComponent.querySelector('.viewer-toolbar-pagination')).toBeNull();
done();
});
}, 55000);
});
describe('View with blob file', () => {
let fixtureBlobTestComponent: ComponentFixture<BlobTestComponent>;
let componentBlobTestComponent: BlobTestComponent;
let elementBlobTestComponent: HTMLElement;
beforeEach((done) => {
fixtureBlobTestComponent = TestBed.createComponent(BlobTestComponent);
componentBlobTestComponent = fixtureBlobTestComponent.componentInstance;
elementBlobTestComponent = fixtureBlobTestComponent.nativeElement;
fixtureBlobTestComponent.detectChanges();
componentBlobTestComponent.pdfViewerComponent.rendered
.pipe(take(1))
.subscribe(() => {
done();
});
});
afterEach(() => {
document.body.removeChild(elementBlobTestComponent);
fixture.destroy();
});
it('should Canvas be present', () => {
fixtureBlobTestComponent.detectChanges();
fixtureBlobTestComponent.whenStable().then(() => {
expect(elementBlobTestComponent.querySelector('.adf-pdfViewer')).not.toBeNull();
expect(elementBlobTestComponent.querySelector('.adf-viewer-pdf-viewer')).not.toBeNull();
});
});
it('should Next an Previous Buttons be present', (done) => {
fixtureBlobTestComponent.detectChanges();
fixtureBlobTestComponent.whenStable().then(() => {
expect(elementBlobTestComponent.querySelector('#viewer-previous-page-button')).not.toBeNull();
expect(elementBlobTestComponent.querySelector('#viewer-next-page-button')).not.toBeNull();
done();
});
}, 55000);
it('should Input Page elements be present', (done) => {
fixtureBlobTestComponent.detectChanges();
fixtureBlobTestComponent.whenStable().then(() => {
/* cspell:disable-next-line */
expect(elementBlobTestComponent.querySelector('.adf-viewer-pagenumber-input')).toBeDefined();
expect(elementBlobTestComponent.querySelector('.adf-viewer-total-pages')).toBeDefined();
expect(elementBlobTestComponent.querySelector('#viewer-previous-page-button')).not.toBeNull();
expect(elementBlobTestComponent.querySelector('#viewer-next-page-button')).not.toBeNull();
done();
});
}, 55000);
it('should Toolbar be hide if showToolbar is false', (done) => {
componentBlobTestComponent.pdfViewerComponent.showToolbar = false;
fixtureBlobTestComponent.detectChanges();
fixtureBlobTestComponent.whenStable().then(() => {
expect(elementBlobTestComponent.querySelector('.viewer-toolbar-command')).toBeNull();
expect(elementBlobTestComponent.querySelector('.viewer-toolbar-pagination')).toBeNull();
done();
});
}, 55000);
});
describe('Password protection dialog', () => {
let fixtureUrlTestPasswordComponent: ComponentFixture<UrlTestPasswordComponent>;
let componentUrlTestPasswordComponent: UrlTestPasswordComponent;
describe('Open password dialog', () => {
beforeEach((done) => {
fixtureUrlTestPasswordComponent = TestBed.createComponent(UrlTestPasswordComponent);
componentUrlTestPasswordComponent = fixtureUrlTestPasswordComponent.componentInstance;
spyOn(dialog, 'open').and.callFake((_: any, context: any) => {
if (context.data.reason === pdfjsLib.PasswordResponses.NEED_PASSWORD) {
return {
afterClosed: () => of('wrong_password')
} as any;
}
if (context.data.reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
return {
afterClosed: () => of('password')
} as any;
}
return undefined;
});
fixtureUrlTestPasswordComponent.detectChanges();
componentUrlTestPasswordComponent.pdfViewerComponent.rendered.subscribe(() => {
done();
});
});
afterEach(() => {
document.body.removeChild(fixtureUrlTestPasswordComponent.nativeElement);
});
it('should try to access protected pdf', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(dialog.open).toHaveBeenCalledTimes(2);
done();
});
});
it('should raise dialog asking for password', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(dialog.open['calls'].all()[0].args[1].data).toEqual({
reason: pdfjsLib.PasswordResponses.NEED_PASSWORD
});
done();
});
});
it('it should raise dialog with incorrect password', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(dialog.open['calls'].all()[1].args[1].data).toEqual({
reason: pdfjsLib.PasswordResponses.INCORRECT_PASSWORD
});
done();
});
});
});
describe('Close password dialog ', () => {
beforeEach((done) => {
fixtureUrlTestPasswordComponent = TestBed.createComponent(UrlTestPasswordComponent);
componentUrlTestPasswordComponent = fixtureUrlTestPasswordComponent.componentInstance;
spyOn(dialog, 'open').and.callFake(() => ({
afterClosed: () => {
done();
return of('');
}
} as any));
spyOn(componentUrlTestPasswordComponent.pdfViewerComponent.close, 'emit');
fixtureUrlTestPasswordComponent.detectChanges();
componentUrlTestPasswordComponent.pdfViewerComponent.rendered.subscribe(() => {
done();
});
});
afterEach(() => {
document.body.removeChild(fixtureUrlTestPasswordComponent.nativeElement);
});
it('should try to access protected pdf', (done) => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(componentUrlTestPasswordComponent.pdfViewerComponent.close.emit).toHaveBeenCalledWith();
done();
});
});
});
});
});
@@ -1,561 +0,0 @@
/*!
* @license
* Copyright 2019 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.
*/
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable no-underscore-dangle */
/* eslint-disable @angular-eslint/no-output-native */
import {
Component,
TemplateRef,
HostListener,
Output,
Input,
OnChanges,
OnDestroy,
ViewEncapsulation,
EventEmitter,
SimpleChanges
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { LogService } from '../../services/log.service';
import { RenderingQueueServices } from '../services/rendering-queue.services';
import { PdfPasswordDialogComponent } from './pdf-viewer-password-dialog';
import { AppConfigService } from './../../app-config/app-config.service';
import { PDFDocumentProxy, PDFSource } from 'pdfjs-dist';
import { Subject } from 'rxjs';
import { catchError, delay } from 'rxjs/operators';
declare const pdfjsLib: any;
declare const pdfjsViewer: any;
@Component({
selector: 'adf-pdf-viewer',
templateUrl: './pdf-viewer.component.html',
styleUrls: ['./pdf-viewer-host.component.scss', './pdf-viewer.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;
@Input()
thumbnailsTemplate: TemplateRef<any> = null;
@Input()
cacheType: string = '';
@Output()
rendered = new EventEmitter<any>();
@Output()
error = new EventEmitter<any>();
@Output()
close = new EventEmitter<any>();
page: number;
displayPage: number;
totalPages: number;
loadingPercent: number;
pdfViewer: any;
currentScaleMode: 'init' | 'page-actual' | 'page-width' | 'page-height' | 'page-fit' | 'auto' = 'init';
currentScale: number = 1;
MAX_AUTO_SCALE: number = 1.25;
DEFAULT_SCALE_DELTA: number = 1.1;
MIN_SCALE: number = 0.25;
MAX_SCALE: number = 10.0;
loadingTask: any;
isPanelDisabled = true;
showThumbnails: boolean = false;
pdfThumbnailsContext: { viewer: any } = { viewer: null };
randomPdfId: string;
get currentScaleText(): string {
return Math.round(this.currentScale * 100) + '%';
}
private eventBus = new pdfjsViewer.EventBus();
private pdfjsDefaultOptions = {
disableAutoFetch: true,
disableStream: true
};
private pdfjsWorkerDestroy$ = new Subject<boolean>();
private onDestroy$ = new Subject<boolean>();
constructor(
private dialog: MatDialog,
private renderingQueueServices: RenderingQueueServices,
private logService: LogService,
private appConfigService: AppConfigService) {
// needed to preserve "this" context
this.onPageChange = this.onPageChange.bind(this);
this.onPagesLoaded = this.onPagesLoaded.bind(this);
this.onPageRendered = this.onPageRendered.bind(this);
this.randomPdfId = this.generateUuid();
this.pdfjsWorkerDestroy$.pipe(catchError(() => null), delay(700)).subscribe(() => this.destroyPdJsWorker());
}
getUserScaling(): number {
let scaleConfig = this.appConfigService.get<number>('adf-viewer.pdf-viewer-scaling', undefined);
if (scaleConfig) {
scaleConfig = scaleConfig / 100;
scaleConfig = this.checkLimits(scaleConfig);
}
return scaleConfig;
}
checkLimits(scaleConfig: number): number {
if (scaleConfig > this.MAX_SCALE) {
return this.MAX_SCALE;
} else if (scaleConfig < this.MIN_SCALE) {
return this.MIN_SCALE;
} else {
return scaleConfig;
}
}
ngOnChanges(changes: SimpleChanges) {
const blobFile = changes['blobFile'];
if (blobFile && blobFile.currentValue) {
const reader = new FileReader();
reader.onload = async () => {
const pdfSource: PDFSource = {
...this.pdfjsDefaultOptions,
data: reader.result,
withCredentials: this.appConfigService.get<boolean>('auth.withCredentials', undefined)
};
this.executePdf(pdfSource);
};
reader.readAsArrayBuffer(blobFile.currentValue);
}
const urlFile = changes['urlFile'];
if (urlFile && urlFile.currentValue) {
const pdfSource: PDFSource = {
...this.pdfjsDefaultOptions,
url: urlFile.currentValue,
withCredentials: this.appConfigService.get<boolean>('auth.withCredentials', undefined)
};
if (this.cacheType) {
pdfSource.httpHeaders = {
'Cache-Control': this.cacheType
};
}
this.executePdf(pdfSource);
}
if (!this.urlFile && !this.blobFile) {
throw new Error('Attribute urlFile or blobFile is required');
}
}
executePdf(pdfOptions: PDFSource) {
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdf.worker.min.js';
this.loadingTask = pdfjsLib.getDocument(pdfOptions);
this.loadingTask.onPassword = (callback, reason) => {
this.onPdfPassword(callback, reason);
};
this.loadingTask.onProgress = (progressData) => {
const level = progressData.loaded / progressData.total;
this.loadingPercent = Math.round(level * 100);
};
this.loadingTask.promise.then((pdfDocument: PDFDocumentProxy) => {
this.totalPages = pdfDocument.numPages;
this.page = 1;
this.displayPage = 1;
this.initPDFViewer(pdfDocument);
return pdfDocument.getPage(1);
})
.then(() => this.scalePage('init'))
.catch(() => this.error.emit());
}
initPDFViewer(pdfDocument: PDFDocumentProxy) {
const viewer: any = this.getViewer();
const container = this.getDocumentContainer();
if (viewer && container) {
this.pdfViewer = new pdfjsViewer.PDFViewer({
container,
viewer,
renderingQueue: this.renderingQueueServices,
eventBus: this.eventBus
});
// cspell: disable-next
this.eventBus.on('pagechanging', this.onPageChange);
// cspell: disable-next
this.eventBus.on('pagesloaded', this.onPagesLoaded);
// cspell: disable-next
this.eventBus.on('textlayerrendered', this.onPageRendered);
this.renderingQueueServices.setViewer(this.pdfViewer);
this.pdfViewer.setDocument(pdfDocument);
this.pdfThumbnailsContext.viewer = this.pdfViewer;
}
}
ngOnDestroy() {
if (this.pdfViewer) {
// cspell: disable-next
this.eventBus.off('pagechanging');
// cspell: disable-next
this.eventBus.off('pagesloaded');
// cspell: disable-next
this.eventBus.off('textlayerrendered');
}
if (this.loadingTask) {
this.pdfjsWorkerDestroy$.next();
}
this.onDestroy$.next();
this.pdfjsWorkerDestroy$.complete();
this.onDestroy$.complete();
}
private destroyPdJsWorker() {
this.loadingTask.destroy();
this.loadingTask = null;
}
toggleThumbnails() {
this.showThumbnails = !this.showThumbnails;
}
/**
* Method to scale the page current support implementation
*
* @param scaleMode - new scale mode
*/
scalePage(scaleMode) {
this.currentScaleMode = scaleMode;
const viewerContainer = document.getElementById(`${this.randomPdfId}-viewer-main-container`);
const documentContainer = this.getDocumentContainer();
if (this.pdfViewer && documentContainer) {
let widthContainer;
let heightContainer;
if (viewerContainer && viewerContainer.clientWidth <= documentContainer.clientWidth) {
widthContainer = viewerContainer.clientWidth;
heightContainer = viewerContainer.clientHeight;
} else {
widthContainer = documentContainer.clientWidth;
heightContainer = documentContainer.clientHeight;
}
const currentPage = this.pdfViewer._pages[this.pdfViewer._currentPageNumber - 1];
const padding = 20;
const pageWidthScale = (widthContainer - padding) / currentPage.width * currentPage.scale;
const pageHeightScale = (heightContainer - padding) / currentPage.width * currentPage.scale;
let scale;
switch (this.currentScaleMode) {
case 'init':
scale = this.getUserScaling();
if (!scale) {
scale = this.autoScaling(pageHeightScale, pageWidthScale);
}
break;
case 'page-actual':
scale = 1;
break;
case 'page-width':
scale = pageWidthScale;
break;
case 'page-height':
scale = pageHeightScale;
break;
case 'page-fit':
scale = this.getUserScaling();
if (!scale) {
scale = this.autoScaling(pageHeightScale, pageWidthScale);
}
break;
case 'auto':
scale = this.autoScaling(pageHeightScale, pageWidthScale);
break;
default:
this.logService.error('pdfViewSetScale: \'' + scaleMode + '\' is an unknown zoom value.');
return;
}
this.setScaleUpdatePages(scale);
}
}
private autoScaling(pageHeightScale: number, pageWidthScale: number) {
let horizontalScale;
if (this.isLandscape) {
horizontalScale = Math.min(pageHeightScale, pageWidthScale);
} else {
horizontalScale = pageWidthScale;
}
horizontalScale = Math.round(horizontalScale);
const scale = Math.min(this.MAX_AUTO_SCALE, horizontalScale);
return this.checkPageFitInContainer(scale);
}
private getDocumentContainer() {
return document.getElementById(`${this.randomPdfId}-viewer-pdf-viewer`);
}
private getViewer() {
return document.getElementById(`${this.randomPdfId}-viewer-viewerPdf`);
}
checkPageFitInContainer(scale: number): number {
const documentContainerSize = this.getDocumentContainer();
const page = this.pdfViewer._pages[this.pdfViewer._currentPageNumber - 1];
if (page.width > documentContainerSize.clientWidth) {
scale = Math.fround((documentContainerSize.clientWidth - 20) / page.width);
if (scale < this.MIN_SCALE) {
scale = this.MIN_SCALE;
}
}
return scale;
}
/**
* Update all the pages with the newScale scale
*
* @param newScale - new scale page
*/
setScaleUpdatePages(newScale: number) {
if (this.pdfViewer) {
if (!this.isSameScale(this.currentScale, newScale)) {
this.currentScale = newScale;
this.pdfViewer._pages.forEach((currentPage) => {
currentPage.update(newScale);
});
}
this.pdfViewer.update();
}
}
/**
* Check if the request scale of the page is the same for avoid useless re-rendering
*
* @param oldScale - old scale page
* @param newScale - new scale page
*
*/
isSameScale(oldScale: number, newScale: number): boolean {
return (newScale === oldScale);
}
/**
* Check if is a land scape view
*
* @param width
* @param height
*/
isLandscape(width: number, height: number): boolean {
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 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 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 page to load
*/
inputPage(page: string) {
const 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;
}
}
/**
* Page Change Event
*
* @param event
*/
onPageChange(event: any) {
if (event.source && event.source.container.id === `${this.randomPdfId}-viewer-pdf-viewer`) {
this.page = event.pageNumber;
this.displayPage = event.pageNumber;
}
}
onPdfPassword(callback, reason) {
this.dialog
.open(PdfPasswordDialogComponent, {
width: '400px',
data: { reason }
})
.afterClosed().subscribe((password) => {
if (password) {
callback(password);
} else {
this.close.emit();
}
});
}
/**
* Page Rendered Event
*/
onPageRendered() {
this.rendered.emit();
}
/**
* Pages Loaded Event
*
* @param event
*/
onPagesLoaded() {
this.isPanelDisabled = false;
}
/**
* Keyboard Event Listener
*
* @param KeyboardEvent event
*/
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
const key = event.keyCode;
if (key === 39) { // right arrow
this.nextPage();
} else if (key === 37) {// left arrow
this.previousPage();
}
}
private generateUuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}
@@ -1,3 +0,0 @@
<pre class="adf-txt-viewer-content">
{{content}}
</pre>
@@ -1,6 +0,0 @@
.adf-txt-viewer {
background-color: var(--theme-background-color);
overflow: auto;
height: 100%;
width: 100%;
}
@@ -1,75 +0,0 @@
/*!
* @license
* Copyright 2019 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 { SimpleChange } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TxtViewerComponent } from './txt-viewer.component';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
describe('Text View component', () => {
let component: TxtViewerComponent;
let fixture: ComponentFixture<TxtViewerComponent>;
let element: HTMLElement;
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(TxtViewerComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
});
describe('View', () => {
it('Should text container be present with urlFile', (done) => {
fixture.detectChanges();
const urlFile = './fake-test-file.txt';
const 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) => {
const blobFile = new Blob(['text example'], {type: 'text/txt'});
const 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();
});
});
});
});
});
@@ -1,90 +0,0 @@
/*!
* @license
* Copyright 2019 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, SimpleChanges } from '@angular/core';
import { AppConfigService } from './../../app-config/app-config.service';
@Component({
selector: 'adf-txt-viewer',
templateUrl: './txt-viewer.component.html',
styleUrls: ['./txt-viewer.component.scss'],
host: { class: 'adf-txt-viewer' },
encapsulation: ViewEncapsulation.None
})
export class TxtViewerComponent implements OnChanges {
@Input()
urlFile: any;
@Input()
blobFile: Blob;
content: string | ArrayBuffer;
constructor(private http: HttpClient, private appConfigService: AppConfigService) {
}
ngOnChanges(changes: SimpleChanges): Promise<void> {
const blobFile = changes['blobFile'];
if (blobFile && blobFile.currentValue) {
return this.readBlob(blobFile.currentValue);
}
const 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');
}
return Promise.resolve();
}
private getUrlContent(url: string): Promise<void> {
const withCredentialsMode = this.appConfigService.get<boolean>('auth.withCredentials', false);
return new Promise((resolve, reject) => {
this.http.get(url, { responseType: 'text', withCredentials: withCredentialsMode }).subscribe((res) => {
this.content = res;
resolve();
}, (event) => {
reject(event);
});
});
}
private readBlob(blob: Blob): Promise<void> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
this.content = reader.result;
resolve();
};
reader.onerror = (error: any) => {
reject(error);
};
reader.readAsText(blob);
});
}
}
@@ -1,6 +0,0 @@
<div class="adf-viewer__unknown-format-view">
<div>
<mat-icon class="icon">error</mat-icon>
<div class="adf-viewer__unknown-label">{{ 'ADF_VIEWER.UNKNOWN_FORMAT' | translate }}</div>
</div>
</div>
@@ -1,9 +0,0 @@
.adf-viewer__unknown-format-view {
height: 90vh;
text-align: center;
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
color: var(--theme-text-fg-color);
}
@@ -1,27 +0,0 @@
/*!
* @license
* Copyright 2019 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, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'adf-viewer-unknown-format',
templateUrl: './unknown-format.component.html',
styleUrls: ['./unknown-format.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class UnknownFormatComponent {
}
@@ -1,28 +0,0 @@
/*!
* @license
* Copyright 2019 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 {
}
@@ -1,28 +0,0 @@
/*!
* @license
* Copyright 2019 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 {
}
@@ -1,37 +0,0 @@
/*!
* @license
* Copyright 2019 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, HostListener, 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 {
@HostListener('keydown', ['$event'])
onKeyDown(event: KeyboardEvent) {
event.cancelBubble = true;
}
@HostListener('keyup', ['$event'])
onKeyUp(event: KeyboardEvent) {
event.cancelBubble = true;
}
}
@@ -1,28 +0,0 @@
/*!
* @license
* Copyright 2019 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-actions',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'adf-viewer-toolbar-actions' },
template: `<ng-content></ng-content>`
})
export class ViewerToolbarActionsComponent {
}
@@ -1,28 +0,0 @@
/*!
* @license
* Copyright 2019 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 {
}
@@ -1,289 +0,0 @@
<div *ngIf="showViewer"
class="adf-viewer-container"
[class.adf-viewer-overlay-container]="overlayMode"
[class.adf-viewer-inline-container]="!overlayMode">
<div class="adf-viewer-content"
fxLayout="column"
[cdkTrapFocus]="overlayMode"
cdkTrapFocusAutoCapture>
<ng-content select="adf-viewer-toolbar"></ng-content>
<ng-container *ngIf="showToolbar && !toolbar">
<adf-toolbar id="adf-viewer-toolbar" class="adf-viewer-toolbar">
<adf-toolbar-title>
<ng-container *ngIf="allowLeftSidebar">
<button mat-icon-button
[attr.aria-expanded]="showLeftSidebar"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.INFO' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.INFO' | translate }}"
data-automation-id="adf-toolbar-left-sidebar"
[color]="showLeftSidebar ? 'accent' : null"
(click)="toggleLeftSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
</ng-container>
<button *ngIf="allowGoBack"
class="adf-viewer-close-button"
data-automation-id="adf-toolbar-back"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.CLOSE' | translate"
mat-icon-button
title="{{ 'ADF_VIEWER.ACTIONS.CLOSE' | translate }}"
(click)="onBackButtonClick()">
<mat-icon>close</mat-icon>
</button>
</adf-toolbar-title>
<div fxFlex="1 1 auto"
class="adf-viewer__file-title">
<button *ngIf="allowNavigate && canNavigateBefore"
data-automation-id="adf-toolbar-pref-file"
mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ACTIONS.PREV_FILE' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.PREV_FILE' | translate }}"
(click)="onNavigateBeforeClick($event)">
<mat-icon>navigate_before</mat-icon>
</button>
<img class="adf-viewer__mimeicon"
[alt]="mimeType"
[src]="mimeType | adfMimeTypeIcon"
data-automation-id="adf-file-thumbnail">
<span class="adf-viewer__display-name"
id="adf-viewer-display-name">{{ fileTitle }}</span>
<button *ngIf="allowNavigate && canNavigateNext"
data-automation-id="adf-toolbar-next-file"
mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ACTIONS.NEXT_FILE' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.NEXT_FILE' | translate }}"
(click)="onNavigateNextClick($event)">
<mat-icon>navigate_next</mat-icon>
</button>
</div>
<ng-content select="adf-viewer-toolbar-actions"></ng-content>
<ng-container *ngIf="mnuOpenWith"
data-automation-id='adf-toolbar-custom-btn'>
<button id="adf-viewer-openwith"
mat-button
[matMenuTriggerFor]="mnuOpenWith"
data-automation-id="adf-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 id="adf-viewer-download"
*ngIf="allowDownload"
mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate }}"
data-automation-id="adf-toolbar-download"
[adfNodeDownload]="nodeEntry"
[version]="versionEntry">
<mat-icon>file_download</mat-icon>
</button>
<button id="adf-viewer-print"
*ngIf="allowPrint"
mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ACTIONS.PRINT' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.PRINT' | translate }}"
data-automation-id="adf-toolbar-print"
(click)="printContent()">
<mat-icon>print</mat-icon>
</button>
<button id="adf-viewer-fullscreen"
*ngIf="viewerType !== 'media' && allowFullScreen"
mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ACTIONS.FULLSCREEN' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.FULLSCREEN' | translate }}"
data-automation-id="adf-toolbar-fullscreen"
(click)="enterFullScreen()">
<mat-icon>fullscreen</mat-icon>
</button>
<ng-container *ngIf="allowRightSidebar">
<adf-toolbar-divider></adf-toolbar-divider>
<button mat-icon-button
[attr.aria-expanded]="showRightSidebar"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.INFO' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.INFO' | translate }}"
data-automation-id="adf-toolbar-sidebar"
[color]="showRightSidebar ? 'accent' : null"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
</ng-container>
<ng-container *ngIf="mnuMoreActions">
<button id="adf-viewer-moreactions"
mat-icon-button
[matMenuTriggerFor]="mnuMoreActions"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.MORE_ACTIONS' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.MORE_ACTIONS' | translate }}"
data-automation-id="adf-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>
</adf-toolbar>
</ng-container>
<div fxLayout="row"
fxFlex="1 1 auto">
<ng-container *ngIf="allowRightSidebar && showRightSidebar">
<div class="adf-viewer__sidebar"
[ngClass]="'adf-viewer__sidebar__right'"
fxFlexOrder="4"
id="adf-right-sidebar">
<ng-container *ngIf="sidebarRightTemplate">
<ng-container *ngTemplateOutlet="sidebarRightTemplate;context:sidebarRightTemplateContext">
</ng-container>
</ng-container>
<ng-content *ngIf="!sidebarRightTemplate"
select="adf-viewer-sidebar"></ng-content>
</div>
</ng-container>
<ng-container *ngIf="allowLeftSidebar && showLeftSidebar">
<div class="adf-viewer__sidebar"
[ngClass]="'adf-viewer__sidebar__left'"
fxFlexOrder="1"
id="adf-left-sidebar">
<ng-container *ngIf="sidebarLeftTemplate">
<ng-container *ngTemplateOutlet="sidebarLeftTemplate;context:sidebarLeftTemplateContext">
</ng-container>
</ng-container>
<ng-content *ngIf="!sidebarLeftTemplate"
select="adf-viewer-sidebar"></ng-content>
</div>
</ng-container>
<div *ngIf="isLoading"
class="adf-viewer-main"
fxFlexOrder="1"
fxFlex="1 1 auto">
<div class="adf-viewer-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-content-container">
<ng-container *ngIf="isLoading">
<div class="adf-viewer__loading-screen"
fxFlex="1 1 auto">
<h2>{{ 'ADF_VIEWER.LOADING' | translate }}</h2>
<div>
<mat-spinner></mat-spinner>
</div>
</div>
</ng-container>
</div>
</div>
</div>
<div *ngIf="!isLoading"
class="adf-viewer-main"
fxFlexOrder="1"
fxFlex="1 1 auto">
<div class="adf-viewer-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-content-container" [ngSwitch]="viewerType">
<ng-container *ngSwitchCase="'external'">
<adf-preview-extension
*ngIf="!!externalViewer"
[id]="externalViewer.component"
[node]="nodeEntry?.entry"
[url]="urlFileContent"
[extension]="externalViewer.fileExtension"
[attr.data-automation-id]="externalViewer.component">
</adf-preview-extension>
</ng-container>
<ng-container *ngSwitchCase="'pdf'">
<adf-pdf-viewer (close)="onBackButtonClick()"
[thumbnailsTemplate]="thumbnailsTemplate"
[allowThumbnails]="allowThumbnails"
[blobFile]="blobFile"
[urlFile]="urlFileContent"
[nameFile]="displayName"
[cacheType]="cacheTypeForContent"
(error)="onUnsupportedFile()"></adf-pdf-viewer>
</ng-container>
<ng-container *ngSwitchCase="'image'">
<adf-img-viewer [urlFile]="urlFileContent"
[nameFile]="displayName"
[blobFile]="blobFile"
[readOnly]="readOnly"
(error)="onUnsupportedFile()"
(submit)="onSubmitFile($event)"
></adf-img-viewer>
</ng-container>
<ng-container *ngSwitchCase="'media'">
<adf-media-player id="adf-mdedia-player"
[urlFile]="urlFileContent"
[nodeId]="nodeEntry?.entry?.id"
[mimeType]="mimeType"
[blobFile]="blobFile"
[nameFile]="displayName"
(error)="onUnsupportedFile()"></adf-media-player>
</ng-container>
<ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFileContent"
[blobFile]="blobFile"></adf-txt-viewer>
</ng-container>
<ng-container *ngSwitchCase="'in_creation'">
<div class="adf-viewer__loading-screen"
fxFlex="1 1 auto">
<h2>{{ 'ADF_VIEWER.LOADING' | translate }}</h2>
<div>
<mat-spinner></mat-spinner>
</div>
</div>
</ng-container>
<ng-container *ngSwitchCase="'custom'">
<ng-container *ngFor="let ext of viewerExtensions">
<adf-preview-extension *ngIf="checkExtensions(ext.fileExtension)"
[id]="ext.component"
[node]="nodeEntry.entry"
[url]="urlFileContent"
[extension]="extension"
[attr.data-automation-id]="ext.component">
</adf-preview-extension>
</ng-container>
<span class="adf-viewer-custom-content"
*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>
</div>
</div>
</div>
</div>
@@ -1,205 +0,0 @@
/* stylelint-disable scss/at-extend-no-missing-placeholder */
.adf-full-screen {
width: 100%;
height: 100%;
background-color: var(--theme-card-bg-color);
}
.adf-viewer {
position: absolute;
width: 100%;
height: 100%;
.mat-toolbar {
color: var(--theme-text-color);
.adf-toolbar-title {
width: auto;
}
}
&-main {
width: 0;
}
&__mimeicon {
vertical-align: middle;
height: 18px;
width: 18px;
}
&-toolbar {
.mat-toolbar {
background-color: var(--theme-card-bg-bold-color);
}
}
&__file-title {
text-align: center;
}
&__display-name {
font-size: var(--theme-subheading-2-font-size);
opacity: 0.87;
line-height: 1.5;
letter-spacing: -0.4px;
font-weight: normal;
font-style: normal;
font-stretch: normal;
max-width: 400px;
text-overflow: ellipsis;
overflow: hidden;
display: inline-block;
vertical-align: middle;
color: var(--theme-text-fg-color);
}
&-container {
.adf-viewer-layout-content {
@extend .adf-full-screen;
position: relative;
overflow-y: hidden;
overflow-x: hidden;
z-index: 1;
background-color: var(--theme-background-color);
display: flex;
flex-direction: row;
/* stylelint-disable-next-line declaration-block-no-redundant-longhand-properties */
flex-wrap: wrap;
flex: 1;
& > div {
display: flex;
flex-flow: row wrap;
margin: 0 auto;
align-items: stretch;
height: 100%;
}
}
.adf-viewer-layout {
@extend .adf-full-screen;
display: flex;
flex-direction: row;
overflow-y: auto;
overflow-x: hidden;
position: relative;
}
.adf-viewer-content {
@extend .adf-full-screen;
flex: 1;
& > div {
height: 0; // Firefox
}
}
}
&-overlay-container {
.adf-viewer-content {
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
}
&-inline-container {
@extend .adf-full-screen;
}
&-content-container {
display: flex;
justify-content: center;
}
&-custom-content {
width: 100vw;
}
&-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: 0;
background-color: var(--theme-background-color);
box-shadow: 0 2px 4px 0 var(--theme-text-fg-shadow-color);
overflow: auto;
&__right {
border-left: 1px solid var(--theme-border-color);
}
&__left {
border-right: 1px solid var(--theme-border-color);
}
}
&__thumbnails {
width: 180px;
display: flex;
flex-direction: column;
padding: 0;
background: #e6e6e6;
.adf-info-drawer-layout {
display: flex;
flex-direction: column;
flex: 1;
background: #e6e6e6;
}
.adf-info-drawer-layout-header {
margin-bottom: 0;
}
.adf-info-drawer-layout-content {
padding: 0;
height: 100%;
overflow: hidden;
}
.adf-info-drawer-content {
height: 100%;
}
.adf-info-drawer-layout-content > *:last-child {
height: 100%;
overflow: hidden;
}
}
&__toolbar {
&-page-scale {
cursor: default;
width: 79px;
height: 24px;
font-size: var(--theme-body-1-font-size);
border: 1px solid var(--theme-border-color);
text-align: center;
line-height: 24px;
margin-left: 4px;
margin-right: 4px;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,794 +0,0 @@
/*!
* @license
* Copyright 2019 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, ContentChild, EventEmitter, HostListener, ElementRef,
Input, OnChanges, Output, TemplateRef,
ViewEncapsulation, OnInit, OnDestroy, ChangeDetectorRef
} from '@angular/core';
import {
SharedLinkEntry,
Node,
Version,
RenditionEntry,
NodeEntry,
VersionEntry,
SharedlinksApi, VersionsApi, NodesApi, ContentApi
} from '@alfresco/js-api';
import { BaseEvent } from '../../events';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service';
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 { fromEvent, Subject } from 'rxjs';
import { ViewUtilService } from '../services/view-util.service';
import { AppExtensionService, ViewerExtensionRef } from '@alfresco/adf-extensions';
import { filter, skipWhile, takeUntil } from 'rxjs/operators';
import { MatDialog } from '@angular/material/dialog';
import { ContentService } from '../../services/content.service';
import { UploadService } from '../../services/upload.service';
import { FileModel } from '../../models';
@Component({
selector: 'adf-viewer',
templateUrl: './viewer.component.html',
styleUrls: ['./viewer.component.scss'],
host: { class: 'adf-viewer' },
encapsulation: ViewEncapsulation.None,
providers: [ViewUtilService]
})
export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
@ContentChild(ViewerToolbarComponent)
toolbar: ViewerToolbarComponent;
@ContentChild(ViewerSidebarComponent)
sidebar: ViewerSidebarComponent;
@ContentChild(ViewerOpenWithComponent)
mnuOpenWith: ViewerOpenWithComponent;
@ContentChild(ViewerMoreActionsComponent)
mnuMoreActions: ViewerMoreActionsComponent;
/** If you want to load an external file that does not come from ACS you
* can use this URL to specify where to load the file from.
*/
@Input()
urlFile = '';
/** Viewer to use with the `urlFile` address (`pdf`, `image`, `media`, `text`).
* Used when `urlFile` has no filename and extension.
*/
@Input()
urlFileViewer: string = null;
/** Loads a Blob File */
@Input()
blobFile: Blob;
/** Node Id of the file to load. */
@Input()
nodeId: string = null;
/** Version Id of the file to load. */
@Input()
versionId: string = null;
/** Shared link id (to display shared file). */
@Input()
sharedLinkId: string = null;
/** If `true` then show the Viewer as a full page over the current content.
* Otherwise fit inside the parent div.
*/
@Input()
overlayMode = false;
/** Hide or show the viewer */
@Input()
showViewer = true;
/** Hide or show the toolbar */
@Input()
showToolbar = true;
/** Specifies the name of the file when it is not available from the URL. */
@Input()
displayName: string;
/** @deprecated 3.2.0 */
/** Allows `back` navigation */
@Input()
allowGoBack = true;
/** Toggles downloading. */
@Input()
allowDownload = true;
/** Toggles printing. */
@Input()
allowPrint = false;
/** Toggles the 'Full Screen' feature. */
@Input()
allowFullScreen = true;
/** Toggles before/next navigation. You can use the arrow buttons to navigate
* between documents in the collection.
*/
@Input()
allowNavigate = false;
/** Toggles the "before" ("<") button. Requires `allowNavigate` to be enabled. */
@Input()
canNavigateBefore = true;
/** Toggles the next (">") button. Requires `allowNavigate` to be enabled. */
@Input()
canNavigateNext = true;
/** Allow the left the sidebar. */
@Input()
allowLeftSidebar = false;
/** Allow the right sidebar. */
@Input()
allowRightSidebar = false;
/** Toggles PDF thumbnails. */
@Input()
allowThumbnails = true;
/** Toggles right sidebar visibility. Requires `allowRightSidebar` to be set to `true`. */
@Input()
showRightSidebar = false;
/** Toggles left sidebar visibility. Requires `allowLeftSidebar` to be set to `true`. */
@Input()
showLeftSidebar = false;
/** The template for the right sidebar. The template context contains the loaded node data. */
@Input()
sidebarRightTemplate: TemplateRef<any> = null;
/** The template for the left sidebar. The template context contains the loaded node data. */
@Input()
sidebarLeftTemplate: TemplateRef<any> = null;
/** The template for the pdf thumbnails. */
@Input()
thumbnailsTemplate: TemplateRef<any> = null;
/** MIME type of the file content (when not determined by the filename extension). */
@Input()
mimeType: string;
/** Content filename. */
@Input()
fileName: string;
/** Number of times the Viewer will retry fetching content Rendition.
* There is a delay of at least one second between attempts.
*/
@Input()
maxRetries = 30;
/** Emitted when user clicks the 'Back' button. */
@Output()
goBack = new EventEmitter<BaseEvent<any>>();
/** Emitted when user clicks the 'Print' button. */
@Output()
print = new EventEmitter<BaseEvent<any>>();
/** Emitted when the viewer is shown or hidden. */
@Output()
showViewerChange = new EventEmitter<boolean>();
/** Emitted when the filename extension changes. */
@Output()
extensionChange = new EventEmitter<string>();
/** Emitted when user clicks 'Navigate Before' ("<") button. */
@Output()
navigateBefore = new EventEmitter<MouseEvent | KeyboardEvent>();
/** Emitted when user clicks 'Navigate Next' (">") button. */
@Output()
navigateNext = new EventEmitter<MouseEvent | KeyboardEvent>();
/** Emitted when the shared link used is not valid. */
@Output()
invalidSharedLink = new EventEmitter();
viewerType = 'unknown';
isLoading = false;
nodeEntry: NodeEntry;
versionEntry: VersionEntry;
extensionTemplates: { template: TemplateRef<any>; isVisible: boolean }[] = [];
urlFileContent: string;
otherMenu: any;
extension: string;
sidebarRightTemplateContext: { node: Node } = { node: null };
sidebarLeftTemplateContext: { node: Node } = { node: null };
fileTitle: string;
/**
* Returns a list of the active Viewer content extensions.
*/
get viewerExtensions(): ViewerExtensionRef[] {
return this.extensionService.getViewerExtensions();
}
/**
* Provides a list of file extensions supported by external plugins.
*/
get externalExtensions(): string[] {
return this.viewerExtensions.map(ext => ext.fileExtension);
}
private _externalViewer: ViewerExtensionRef;
get externalViewer(): ViewerExtensionRef {
if (!this._externalViewer) {
this._externalViewer = this.viewerExtensions.find(ext => ext.fileExtension === '*');
}
return this._externalViewer;
}
readOnly = true;
private cacheBusterNumber: number;
cacheTypeForContent = '';
// Extensions that are supported by the Viewer without conversion
private extensions = {
image: ['png', 'jpg', 'jpeg', 'gif', 'bpm', 'svg'],
media: ['wav', 'mp4', 'mp3', 'webm', 'ogg'],
text: ['txt', 'xml', 'html', 'json', 'ts', 'css', 'md'],
pdf: ['pdf']
};
// Mime types that are supported by the Viewer without conversion
private mimeTypes = {
text: ['text/plain', 'text/csv', 'text/xml', 'text/html', 'application/x-javascript'],
pdf: ['application/pdf'],
image: ['image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/svg+xml'],
media: ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/mp3', 'audio/ogg', 'audio/wav']
};
private onDestroy$ = new Subject<boolean>();
private shouldCloseViewer = true;
private keyDown$ = fromEvent<KeyboardEvent>(document, 'keydown');
_sharedLinksApi: SharedlinksApi;
get sharedLinksApi(): SharedlinksApi {
this._sharedLinksApi = this._sharedLinksApi ?? new SharedlinksApi(this.apiService.getInstance());
return this._sharedLinksApi;
}
_versionsApi: VersionsApi;
get versionsApi(): VersionsApi {
this._versionsApi = this._versionsApi ?? new VersionsApi(this.apiService.getInstance());
return this._versionsApi;
}
_nodesApi: NodesApi;
get nodesApi(): NodesApi {
this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance());
return this._nodesApi;
}
_contentApi: ContentApi;
get contentApi(): ContentApi {
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
return this._contentApi;
}
constructor(private apiService: AlfrescoApiService,
private viewUtilService: ViewUtilService,
private logService: LogService,
private extensionService: AppExtensionService,
private contentService: ContentService,
private uploadService: UploadService,
private el: ElementRef,
public dialog: MatDialog,
private cdr: ChangeDetectorRef) {
viewUtilService.maxRetries = this.maxRetries;
}
isSourceDefined(): boolean {
return !!(this.urlFile || this.blobFile || this.nodeId || this.sharedLinkId);
}
ngOnInit() {
this.apiService.nodeUpdated.pipe(
filter((node) => node && node.id === this.nodeId &&
(node.name !== this.fileName ||
this.getNodeVersionProperty(this.nodeEntry.entry) !== this.getNodeVersionProperty(node))),
takeUntil(this.onDestroy$)
).subscribe((node) => this.onNodeUpdated(node));
this.viewUtilService.viewerTypeChange.pipe(takeUntil(this.onDestroy$)).subscribe((type: string) => {
this.viewerType = type;
});
this.viewUtilService.urlFileContentChange.pipe(takeUntil(this.onDestroy$)).subscribe((content: string) => {
this.urlFileContent = content;
});
this.closeOverlayManager();
this.cacheTypeForContent = '';
}
private getNodeVersionProperty(node: Node): string {
return node?.properties['cm:versionLabel'] ?? '';
}
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
private onNodeUpdated(node: Node) {
if (node && node.id === this.nodeId) {
this.cacheTypeForContent = 'no-cache';
this.generateCacheBusterNumber();
this.isLoading = true;
this.setUpNodeFile(node).then(() => {
this.isLoading = false;
});
}
}
ngOnChanges() {
if (this.showViewer) {
if (!this.isSourceDefined()) {
throw new Error('A content source attribute value is missing.');
}
this.isLoading = true;
if (this.blobFile) {
this.setUpBlobData();
this.isLoading = false;
} else if (this.urlFile) {
this.setUpUrlFile();
this.isLoading = false;
} else if (this.nodeId) {
this.setupNode();
} else if (this.sharedLinkId) {
this.setupSharedLink();
}
}
}
private setupSharedLink() {
this.allowGoBack = false;
this.sharedLinksApi.getSharedLink(this.sharedLinkId).then(
(sharedLinkEntry: SharedLinkEntry) => {
this.setUpSharedLinkFile(sharedLinkEntry);
this.isLoading = false;
},
() => {
this.isLoading = false;
this.logService.error('This sharedLink does not exist');
this.invalidSharedLink.next();
});
}
private setupNode() {
this.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] }).then(
(node: NodeEntry) => {
this.nodeEntry = node;
if (this.versionId) {
this.versionsApi.getVersion(this.nodeId, this.versionId).then(
(version: VersionEntry) => {
this.versionEntry = version;
this.setUpNodeFile(node.entry, version.entry).then(() => {
this.isLoading = false;
});
}
);
} else {
this.setUpNodeFile(node.entry).then(() => {
this.isLoading = false;
this.cdr.detectChanges();
});
}
},
() => {
this.isLoading = false;
this.logService.error('This node does not exist');
}
);
}
private setUpBlobData() {
this.fileTitle = this.getDisplayName('Unknown');
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.scrollTop();
}
private setUpUrlFile() {
const filenameFromUrl = this.getFilenameFromUrl(this.urlFile);
this.fileTitle = this.getDisplayName(filenameFromUrl);
this.extension = this.getFileExtension(filenameFromUrl);
this.urlFileContent = this.urlFile;
this.fileName = this.displayName;
this.viewerType = this.urlFileViewer || this.getViewerType(this.extension, this.mimeType);
this.extensionChange.emit(this.extension);
this.scrollTop();
}
private async setUpNodeFile(nodeData: Node, versionData?: Version): Promise<void> {
this.readOnly = !this.contentService.hasAllowableOperations(nodeData, 'update');
if (versionData && versionData.content) {
this.mimeType = versionData.content.mimeType;
} else if (nodeData.content) {
this.mimeType = nodeData.content.mimeType;
}
this.fileTitle = this.getDisplayName(versionData ? versionData.name : nodeData.name);
const currentFileVersion = this.nodeEntry?.entry?.properties && this.nodeEntry.entry.properties['cm:versionLabel'] ?
encodeURI(this.nodeEntry?.entry?.properties['cm:versionLabel']) : encodeURI('1.0');
this.urlFileContent = versionData ? this.contentApi.getVersionContentUrl(this.nodeId, versionData.id) :
this.contentApi.getContentUrl(this.nodeId);
this.urlFileContent = this.cacheBusterNumber ? this.urlFileContent + '&' + currentFileVersion + '&' + this.cacheBusterNumber :
this.urlFileContent + '&' + currentFileVersion;
this.extension = this.getFileExtension(versionData ? versionData.name : nodeData.name);
this.fileName = versionData ? versionData.name : nodeData.name;
this.viewerType = this.getViewerType(this.extension, this.mimeType);
if (this.viewerType === 'unknown') {
if (versionData) {
await this.viewUtilService.displayNodeRendition(nodeData.id, versionData.id);
} else {
await this.viewUtilService.displayNodeRendition(nodeData.id);
}
}
this.extensionChange.emit(this.extension);
this.sidebarRightTemplateContext.node = nodeData;
this.sidebarLeftTemplateContext.node = nodeData;
this.scrollTop();
}
private getViewerType(extension: string, mimeType: string): string {
let viewerType = this.getViewerTypeByExtension(extension);
if (viewerType === 'unknown') {
viewerType = this.getViewerTypeByMimeType(mimeType);
}
return viewerType;
}
private setUpSharedLinkFile(details: any) {
this.mimeType = details.entry.content.mimeType;
this.fileTitle = this.getDisplayName(details.entry.name);
this.extension = this.getFileExtension(details.entry.name);
this.fileName = details.entry.name;
this.urlFileContent = this.contentApi.getSharedLinkContentUrl(this.sharedLinkId, false);
this.viewerType = this.getViewerType(this.extension, this.mimeType);
if (this.viewerType === 'unknown') {
this.displaySharedLinkRendition(this.sharedLinkId);
}
this.extensionChange.emit(this.extension);
}
toggleSidebar() {
this.showRightSidebar = !this.showRightSidebar;
if (this.showRightSidebar && this.nodeId) {
this.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] })
.then((nodeEntry: NodeEntry) => {
this.sidebarRightTemplateContext.node = nodeEntry.entry;
});
}
}
toggleLeftSidebar() {
this.showLeftSidebar = !this.showLeftSidebar;
if (this.showRightSidebar && this.nodeId) {
this.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] })
.then((nodeEntry: NodeEntry) => {
this.sidebarLeftTemplateContext.node = nodeEntry.entry;
});
}
}
private getDisplayName(name) {
return this.displayName || name;
}
scrollTop() {
window.scrollTo(0, 1);
}
getViewerTypeByMimeType(mimeType: string) {
if (mimeType) {
mimeType = mimeType.toLowerCase();
const editorTypes = Object.keys(this.mimeTypes);
for (const type of editorTypes) {
if (this.mimeTypes[type].indexOf(mimeType) >= 0) {
return type;
}
}
}
return 'unknown';
}
getViewerTypeByExtension(extension: string): string {
if (extension) {
extension = extension.toLowerCase();
}
if (this.isExternalViewer()) {
return 'external';
}
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() {
this.close();
}
onNavigateBeforeClick(event: MouseEvent | KeyboardEvent) {
this.navigateBefore.next(event);
}
onNavigateNextClick(event: MouseEvent | KeyboardEvent) {
this.navigateNext.next(event);
}
/**
* close the viewer
*/
close() {
if (this.otherMenu) {
this.otherMenu.hidden = false;
}
this.showViewer = false;
this.showViewerChange.emit(this.showViewer);
}
/**
* get File name from url
*
* @param url - url file
*/
getFilenameFromUrl(url: string): string {
const anchor = url.indexOf('#');
const query = url.indexOf('?');
const end = Math.min(
anchor > 0 ? anchor : url.length,
query > 0 ? query : url.length);
return url.substring(url.lastIndexOf('/', end) + 1, end);
}
/**
* Get file extension from the string.
* Supports the URL formats like:
* http://localhost/test.jpg?cache=1000
* http://localhost/test.jpg#cache=1000
*
* @param fileName - file name
*/
getFileExtension(fileName: string): string {
if (fileName) {
const match = fileName.match(/\.([^\./\?\#]+)($|\?|\#)/);
return match ? match[1] : null;
}
return null;
}
private isExternalViewer(): boolean {
return !!this.viewerExtensions.find(ext => ext.fileExtension === '*');
}
isCustomViewerExtension(extension: string): boolean {
const extensions = this.externalExtensions || [];
if (extension && extensions.length > 0) {
extension = extension.toLowerCase();
return extensions.flat().indexOf(extension) >= 0;
}
return false;
}
/**
* Keyboard event listener
*
* @param event
*/
@HostListener('document:keyup', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
if (event && event.defaultPrevented) {
return;
}
const key = event.keyCode;
// Left arrow
if (key === 37 && this.canNavigateBefore) {
event.preventDefault();
this.onNavigateBeforeClick(event);
}
// Right arrow
if (key === 39 && this.canNavigateNext) {
event.preventDefault();
this.onNavigateNextClick(event);
}
// Ctrl+F
if (key === 70 && event.ctrlKey) {
event.preventDefault();
this.enterFullScreen();
}
}
printContent() {
if (this.allowPrint) {
const args = new BaseEvent();
this.print.next(args);
if (!args.defaultPrevented) {
this.viewUtilService.printFileGeneric(this.nodeId, this.mimeType);
}
}
}
/**
* Triggers full screen mode with a main content area displayed.
*/
enterFullScreen(): void {
if (this.allowFullScreen) {
const container = this.el.nativeElement.querySelector('.adf-viewer__fullscreen-container');
if (container) {
if (container.requestFullscreen) {
container.requestFullscreen();
} else if (container.webkitRequestFullscreen) {
container.webkitRequestFullscreen();
} else if (container.mozRequestFullScreen) {
container.mozRequestFullScreen();
} else if (container.msRequestFullscreen) {
container.msRequestFullscreen();
}
}
}
}
private async displaySharedLinkRendition(sharedId: string) {
try {
const rendition: RenditionEntry = await this.sharedLinksApi.getSharedLinkRendition(sharedId, 'pdf');
if (rendition.entry.status.toString() === 'CREATED') {
this.viewerType = 'pdf';
this.urlFileContent = this.contentApi.getSharedLinkRenditionUrl(sharedId, 'pdf');
}
} catch (error) {
this.logService.error(error);
try {
const rendition: RenditionEntry = await this.sharedLinksApi.getSharedLinkRendition(sharedId, 'imgpreview');
if (rendition.entry.status.toString() === 'CREATED') {
this.viewerType = 'image';
this.urlFileContent = this.contentApi.getSharedLinkRenditionUrl(sharedId, 'imgpreview');
}
} catch (renditionError) {
this.logService.error(renditionError);
}
}
}
checkExtensions(extensionAllowed) {
if (typeof extensionAllowed === 'string') {
return this.extension.toLowerCase() === extensionAllowed.toLowerCase();
} else if (extensionAllowed.length > 0) {
return extensionAllowed.find((currentExtension) => this.extension.toLowerCase() === currentExtension.toLowerCase());
}
}
onSubmitFile(newImageBlob: Blob) {
if (this?.nodeEntry?.entry?.id && !this.readOnly) {
const newImageFile: File = new File([newImageBlob], this?.nodeEntry?.entry?.name, { type: this?.nodeEntry?.entry?.content?.mimeType });
const newFile = new FileModel(
newImageFile,
{
majorVersion: false,
newVersion: true,
parentId: this?.nodeEntry?.entry?.parentId,
nodeType: this?.nodeEntry?.entry?.content?.mimeType
},
this?.nodeEntry?.entry?.id
);
this.uploadService.addToQueue(...[newFile]);
this.uploadService.uploadFilesInTheQueue();
}
}
onUnsupportedFile() {
this.viewerType = 'unknown';
}
private closeOverlayManager() {
this.dialog.afterOpened.pipe(
skipWhile(() => !this.overlayMode),
takeUntil(this.onDestroy$)
).subscribe(() => this.shouldCloseViewer = false);
this.dialog.afterAllClosed.pipe(
skipWhile(() => !this.overlayMode),
takeUntil(this.onDestroy$)
).subscribe(() => this.shouldCloseViewer = true);
this.keyDown$.pipe(
skipWhile(() => !this.overlayMode),
filter((e: KeyboardEvent) => e.keyCode === 27),
takeUntil(this.onDestroy$)
).subscribe((event: KeyboardEvent) => {
event.preventDefault();
if (this.shouldCloseViewer) {
this.close();
}
});
}
private generateCacheBusterNumber() {
this.cacheBusterNumber = Date.now();
}
}
@@ -1,69 +0,0 @@
/*!
* @license
* Copyright 2019 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 { ChangeDetectorRef, ElementRef } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { ViewerComponent } from '../components/viewer.component';
import { ViewerExtensionDirective } from './viewer-extension.directive';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
describe('ExtensionViewerDirective', () => {
let extensionViewerDirective: ViewerExtensionDirective;
class MockElementRef extends ElementRef {
constructor() {
super(null);
}
}
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule
],
providers: [
{ provide: Location, useClass: SpyLocation },
ViewerExtensionDirective,
{provide: ElementRef, useClass: MockElementRef},
ViewerComponent,
{ provide: ChangeDetectorRef, useValue: { detectChanges: () => {} } }
]
});
beforeEach(() => {
extensionViewerDirective = TestBed.inject(ViewerExtensionDirective);
extensionViewerDirective.templateModel = {template: '', isVisible: false};
});
it('is defined', () => {
expect(extensionViewerDirective).toBeDefined();
});
it('if the file in the viewer has an extension handled by this extension isVisible should be true', () => {
extensionViewerDirective.supportedExtensions = ['xls', 'sts'];
expect(extensionViewerDirective.isVisible('xls')).toBeTruthy();
});
it('if the file in the viewer not has an extension handled by this extension isVisible should be false', () => {
extensionViewerDirective.supportedExtensions = ['xls', 'sts'];
expect(extensionViewerDirective.isVisible('png')).not.toBeTruthy();
});
});
@@ -1,83 +0,0 @@
/*!
* @license
* Copyright 2019 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 { AfterContentInit, ContentChild, Directive, Input, TemplateRef, OnDestroy } from '@angular/core';
import { ViewerComponent } from '../components/viewer.component';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Directive({
selector: 'adf-viewer-extension'
})
export class ViewerExtensionDirective implements AfterContentInit, OnDestroy {
@ContentChild(TemplateRef)
template: any;
@Input()
urlFileContent: string;
@Input()
extension: string;
@Input()
supportedExtensions: string[];
templateModel: any;
private onDestroy$ = new Subject<boolean>();
constructor(private viewerComponent: ViewerComponent) {
}
ngAfterContentInit() {
this.templateModel = { template: this.template, isVisible: false };
this.viewerComponent.extensionTemplates.push(this.templateModel);
this.viewerComponent.extensionChange
.pipe(takeUntil(this.onDestroy$))
.subscribe(fileExtension => {
this.templateModel.isVisible = this.isVisible(fileExtension);
});
if (this.supportedExtensions instanceof Array) {
this.supportedExtensions.forEach((extension) => {
this.viewerComponent.externalExtensions.push(extension);
});
}
}
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
/**
* check if the current extension in the viewer is compatible with this extension checking against supportedExtensions
*/
isVisible(fileExtension): boolean {
let supportedExtension: string;
if (this.supportedExtensions && (this.supportedExtensions instanceof Array)) {
supportedExtension = this.supportedExtensions.find((extension) => extension.toLowerCase() === fileExtension);
}
return !!supportedExtension;
}
}
-18
View File
@@ -1,18 +0,0 @@
/*!
* @license
* Copyright 2019 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.
*/
export * from './public-api';
-23
View File
@@ -1,23 +0,0 @@
/*!
* @license
* Copyright 2019 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.
*/
export interface Track {
src: string;
label?: string;
kind?: string;
srclang?: string;
}
-40
View File
@@ -1,40 +0,0 @@
/*!
* @license
* Copyright 2019 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.
*/
export * from './services/view-util.service';
export * from './components/viewer.component';
export * from './components/img-viewer.component';
export * from './components/media-player.component';
export * from './components/pdf-viewer-password-dialog';
export * from './components/pdf-viewer.component';
export * from './components/pdf-viewer-thumbnails.component';
export * from './components/pdf-viewer-thumb.component';
export * from './components/pdf-viewer-thumbnails.component';
export * from './components/txt-viewer.component';
export * from './components/unknown-format/unknown-format.component';
export * from './components/viewer-more-actions.component';
export * from './components/viewer-open-with.component';
export * from './components/viewer-sidebar.component';
export * from './components/viewer-toolbar.component';
export * from './components/viewer-toolbar-actions.component';
export * from './components/viewer.component';
export * from './directives/viewer-extension.directive';
export * from './viewer.module';
export * from './models/viewer.model';
@@ -1,169 +0,0 @@
/* tslint:disable:adf-license-banner */
/* Copyright 2012 Mozilla Foundation
*
* 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 { Injectable } from '@angular/core';
/**
*
* RenderingQueueServices rendering of the views for pages and thumbnails.
*
*/
@Injectable()
export class RenderingQueueServices {
renderingStates = {
INITIAL: 0,
RUNNING: 1,
PAUSED: 2,
FINISHED: 3
};
CLEANUP_TIMEOUT: number = 30000;
pdfViewer: any = null;
pdfThumbnailViewer: any = null;
onIdle: any = null;
highestPriorityPage: any = null;
idleTimeout: any = null;
printing: any = false;
isThumbnailViewEnabled: any = false;
/**
* @param pdfViewer
*/
setViewer(pdfViewer) {
this.pdfViewer = pdfViewer;
}
/**
* @param pdfThumbnailViewer
*/
setThumbnailViewer(pdfThumbnailViewer) {
this.pdfThumbnailViewer = pdfThumbnailViewer;
}
/**
* @param view
*/
isHighestPriority(view: any): boolean {
return this.highestPriorityPage === view.renderingId;
}
renderHighestPriority(currentlyVisiblePages) {
if (this.idleTimeout) {
clearTimeout(this.idleTimeout);
this.idleTimeout = null;
}
// Pages have a higher priority than thumbnails, so check them first.
if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
return;
}
// No pages needed rendering so check thumbnails.
if (this.pdfThumbnailViewer && this.isThumbnailViewEnabled) {
if (this.pdfThumbnailViewer.forceRendering()) {
return;
}
}
if (this.printing) {
// If printing is currently ongoing do not reschedule cleanup.
return;
}
if (this.onIdle) {
this.idleTimeout = setTimeout(this.onIdle.bind(this), this.CLEANUP_TIMEOUT);
}
}
getHighestPriority(visible, views, scrolledDown) {
// The state has changed figure out which page has the highest priority to
// render next (if any).
// Priority:
// 1 visible pages
// 2 if last scrolled down page after the visible pages
// 2 if last scrolled up page before the visible pages
const visibleViews = visible.views;
const numVisible = visibleViews.length;
if (numVisible === 0) {
return false;
}
for (let i = 0; i < numVisible; ++i) {
const view = visibleViews[i].view;
if (!this.isViewFinished(view)) {
return view;
}
}
// All the visible views have rendered, try to render next/previous pages.
if (scrolledDown) {
const nextPageIndex = visible.last.id;
// ID's start at 1 so no need to add 1.
if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) {
return views[nextPageIndex];
}
} else {
const previousPageIndex = visible.first.id - 2;
if (views[previousPageIndex] && !this.isViewFinished(views[previousPageIndex])) {
return views[previousPageIndex];
}
}
// Everything that needs to be rendered has been.
return null;
}
/**
* @param view
*/
isViewFinished(view): boolean {
return view.renderingState === this.renderingStates.FINISHED;
}
/**
* Render a page or thumbnail view. This calls the appropriate function
* based on the views state. If the view is already rendered it will return
* false.
*
* @param view
*/
renderView(view: any) {
const state = view.renderingState;
switch (state) {
case this.renderingStates.FINISHED:
return false;
case this.renderingStates.PAUSED:
this.highestPriorityPage = view.renderingId;
view.resume();
break;
case this.renderingStates.RUNNING:
this.highestPriorityPage = view.renderingId;
break;
case this.renderingStates.INITIAL:
this.highestPriorityPage = view.renderingId;
const continueRendering = function() {
this.renderHighestPriority();
}.bind(this);
view.draw().then(continueRendering, continueRendering);
break;
default:
break;
}
return true;
}
}
@@ -1,346 +0,0 @@
/*!
* @license
* Copyright 2019 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 { Injectable } from '@angular/core';
import { ContentApi, RenditionEntry, RenditionPaging, RenditionsApi, VersionsApi } from '@alfresco/js-api';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service';
import { Subject } from 'rxjs';
import { Track } from '../models/viewer.model';
import { TranslationService } from '../../services/translation.service';
@Injectable({
providedIn: 'root'
})
export class ViewUtilService {
static TARGET = '_new';
/**
* Content groups based on categorization of files that can be viewed in the web browser. This
* implementation or grouping is tied to the definition the ng component: ViewerComponent
*/
// tslint:disable-next-line:variable-name
static ContentGroup = {
IMAGE: 'image',
MEDIA: 'media',
PDF: 'pdf',
TEXT: 'text'
};
/**
* The name of the rendition with the media subtitles in the supported format
*/
/* tslint:disable-next-line */
static SUBTITLES_RENDITION_NAME = 'webvtt';
/**
* Based on ViewerComponent Implementation, this value is used to determine how many times we try
* to get the rendition of a file for preview, or printing.
*/
maxRetries = 5;
/**
* Mime-type grouping based on the ViewerComponent.
*/
private mimeTypes = {
text: ['text/plain', 'text/csv', 'text/xml', 'text/html', 'application/x-javascript'],
pdf: ['application/pdf'],
image: ['image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/svg+xml'],
media: ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/ogg', 'audio/wav']
};
/**
* Timeout used for setInterval.
*/
TRY_TIMEOUT: number = 10000;
/**
* Subscribers needed for ViewerComponent to update the viewerType and urlFileContent.
*/
viewerTypeChange: Subject<string> = new Subject<string>();
urlFileContentChange: Subject<string> = new Subject<string>();
_renditionsApi: RenditionsApi;
get renditionsApi(): RenditionsApi {
this._renditionsApi = this._renditionsApi ?? new RenditionsApi(this.apiService.getInstance());
return this._renditionsApi;
}
_contentApi: ContentApi;
get contentApi(): ContentApi {
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
return this._contentApi;
}
_versionsApi: VersionsApi;
get versionsApi(): VersionsApi {
this._versionsApi = this._versionsApi ?? new VersionsApi(this.apiService.getInstance());
return this._versionsApi;
}
constructor(private apiService: AlfrescoApiService,
private logService: LogService,
private translateService: TranslationService) {
}
/**
* This method takes a url to trigger the print dialog against, and the type of artifact that it
* is.
* This URL should be one that can be rendered in the browser, for example PDF, Image, or Text
*/
printFile(url: string, type: string): void {
const pwa = window.open(url, ViewUtilService.TARGET);
if (pwa) {
// Because of the way chrome focus and close image window vs. pdf preview window
if (type === ViewUtilService.ContentGroup.IMAGE) {
pwa.onfocus = () => {
setTimeout(() => {
pwa.close();
}, 500);
};
}
pwa.onload = () => {
pwa.print();
};
}
}
/**
* Launch the File Print dialog from anywhere other than the preview service, which resolves the
* rendition of the object that can be printed from a web browser.
* These are: images, PDF files, or PDF rendition of files.
* We also force PDF rendition for TEXT type objects, otherwise the default URL is to download.
* TODO there are different TEXT type objects, (HTML, plaintext, xml, etc. we should determine how these are handled)
*/
printFileGeneric(objectId: string, mimeType: string): void {
const nodeId = objectId;
const type: string = this.getViewerTypeByMimeType(mimeType);
this.getRendition(nodeId, ViewUtilService.ContentGroup.PDF)
.then((value) => {
const url: string = this.getRenditionUrl(nodeId, type, (!!value));
const printType = (type === ViewUtilService.ContentGroup.PDF
|| type === ViewUtilService.ContentGroup.TEXT)
? ViewUtilService.ContentGroup.PDF : type;
this.printFile(url, printType);
})
.catch((err) => {
this.logService.error('Error with Printing');
this.logService.error(err);
});
}
getRenditionUrl(nodeId: string, type: string, renditionExists: boolean): string {
return (renditionExists && type !== ViewUtilService.ContentGroup.IMAGE) ?
this.contentApi.getRenditionUrl(nodeId, ViewUtilService.ContentGroup.PDF) :
this.contentApi.getContentUrl(nodeId, false);
}
private async waitRendition(nodeId: string, renditionId: string, retries: number): Promise<RenditionEntry> {
const rendition = await this.renditionsApi.getRendition(nodeId, renditionId);
if (this.maxRetries < retries) {
const status = rendition.entry.status.toString();
if (status === 'CREATED') {
return rendition;
} else {
retries += 1;
await this.wait(1000);
return this.waitRendition(nodeId, renditionId, retries);
}
}
return Promise.resolve(null);
}
getViewerTypeByMimeType(mimeType: string): string {
if (mimeType) {
mimeType = mimeType.toLowerCase();
const editorTypes = Object.keys(this.mimeTypes);
for (const type of editorTypes) {
if (this.mimeTypes[type].indexOf(mimeType) >= 0) {
return type;
}
}
}
return 'unknown';
}
wait(ms: number): Promise<any> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async getRendition(nodeId: string, renditionId: string): Promise<RenditionEntry> {
const renditionPaging: RenditionPaging = await this.renditionsApi.listRenditions(nodeId);
let rendition: RenditionEntry = renditionPaging.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
if (rendition) {
const status = rendition.entry.status.toString();
if (status === 'NOT_CREATED') {
try {
await this.renditionsApi.createRendition(nodeId, { id: renditionId });
rendition = await this.waitRendition(nodeId, renditionId, 0);
} catch (err) {
this.logService.error(err);
}
}
}
return new Promise<RenditionEntry>((resolve) => resolve(rendition));
}
async displayNodeRendition(nodeId: string, versionId?: string) {
try {
const rendition = versionId ? await this.resolveNodeRendition(nodeId, 'pdf', versionId) :
await this.resolveNodeRendition(nodeId, 'pdf');
if (rendition) {
const renditionId = rendition.entry.id;
if (renditionId === 'pdf') {
this.viewerTypeChange.next('pdf');
} else if (renditionId === 'imgpreview') {
this.viewerTypeChange.next('image');
}
const urlFileContent = versionId ? this.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) :
this.contentApi.getRenditionUrl(nodeId, renditionId);
this.urlFileContentChange.next(urlFileContent);
}
} catch (err) {
this.logService.error(err);
}
}
private async resolveNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise<RenditionEntry> {
renditionId = renditionId.toLowerCase();
const supportedRendition: RenditionPaging = versionId ? await this.versionsApi.listVersionRenditions(nodeId, versionId) :
await this.renditionsApi.listRenditions(nodeId);
let rendition: RenditionEntry = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
if (!rendition) {
renditionId = 'imgpreview';
rendition = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
}
if (rendition) {
const status: string = rendition.entry.status.toString();
if (status === 'NOT_CREATED') {
try {
if (versionId) {
await this.versionsApi.createVersionRendition(nodeId, versionId, { id: renditionId }).then(() => {
this.viewerTypeChange.next('in_creation');
});
} else {
await this.renditionsApi.createRendition(nodeId, { id: renditionId }).then(() => {
this.viewerTypeChange.next('in_creation');
});
}
try {
rendition = versionId ? await this.waitNodeRendition(nodeId, renditionId, versionId) : await this.waitNodeRendition(nodeId, renditionId);
} catch (e) {
this.viewerTypeChange.next('error_in_creation');
rendition = null;
}
} catch (err) {
this.logService.error(err);
}
}
}
return rendition;
}
private async waitNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise<RenditionEntry> {
let currentRetry: number = 0;
return new Promise<RenditionEntry>((resolve, reject) => {
const intervalId = setInterval(() => {
currentRetry++;
if (this.maxRetries >= currentRetry) {
if (versionId) {
this.versionsApi.getVersionRendition(nodeId, versionId, renditionId).then((rendition: RenditionEntry) => {
const status: string = rendition.entry.status.toString();
if (status === 'CREATED') {
this.handleNodeRendition(nodeId, renditionId, versionId);
clearInterval(intervalId);
return resolve(rendition);
}
}, () => reject());
} else {
this.renditionsApi.getRendition(nodeId, renditionId).then((rendition: RenditionEntry) => {
const status: string = rendition.entry.status.toString();
if (status === 'CREATED') {
this.handleNodeRendition(nodeId, renditionId);
clearInterval(intervalId);
return resolve(rendition);
}
}, () => reject());
}
} else {
clearInterval(intervalId);
return reject();
}
}, this.TRY_TIMEOUT);
});
}
private async handleNodeRendition(nodeId: string, renditionId: string, versionId?: string) {
if (renditionId === 'pdf') {
this.viewerTypeChange.next('pdf');
} else if (renditionId === 'imgpreview') {
this.viewerTypeChange.next('image');
}
const urlFileContent = versionId ? this.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) :
this.contentApi.getRenditionUrl(nodeId, renditionId);
this.urlFileContentChange.next(urlFileContent);
}
async generateMediaTracks(nodeId: string): Promise<Track[]> {
return this.isRenditionAvailable(nodeId, ViewUtilService.SUBTITLES_RENDITION_NAME)
.then((value) => {
const tracks = [];
if (value) {
tracks.push({
kind: 'subtitles',
src: this.contentApi.getRenditionUrl(nodeId, ViewUtilService.SUBTITLES_RENDITION_NAME),
label: this.translateService.instant('ADF_VIEWER.SUBTITLES')
});
}
return tracks;
})
.catch((err) => {
this.logService.error('Error while retrieving ' + ViewUtilService.SUBTITLES_RENDITION_NAME + ' rendition');
this.logService.error(err);
return [];
});
}
private async isRenditionAvailable(nodeId: string, renditionId: string): Promise<boolean> {
const renditionPaging: RenditionPaging = await this.renditionsApi.listRenditions(nodeId);
const rendition: RenditionEntry = renditionPaging.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
return rendition?.entry?.status?.toString() === 'CREATED' || false;
}
}
-97
View File
@@ -1,97 +0,0 @@
/*!
* @license
* Copyright 2019 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 { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ExtensionsModule } from '@alfresco/adf-extensions';
import { MaterialModule } from '../material.module';
import { ToolbarModule } from '../toolbar/toolbar.module';
import { PipeModule } from '../pipes/pipe.module';
import { ImgViewerComponent } from './components/img-viewer.component';
import { MediaPlayerComponent } from './components/media-player.component';
import { PdfViewerComponent } from './components/pdf-viewer.component';
import { PdfPasswordDialogComponent } from './components/pdf-viewer-password-dialog';
import { PdfThumbComponent } from './components/pdf-viewer-thumb.component';
import { PdfThumbListComponent } from './components/pdf-viewer-thumbnails.component';
import { TxtViewerComponent } from './components/txt-viewer.component';
import { UnknownFormatComponent } from './components/unknown-format/unknown-format.component';
import { ViewerMoreActionsComponent } from './components/viewer-more-actions.component';
import { ViewerOpenWithComponent } from './components/viewer-open-with.component';
import { ViewerSidebarComponent } from './components/viewer-sidebar.component';
import { ViewerToolbarComponent } from './components/viewer-toolbar.component';
import { ViewerComponent } from './components/viewer.component';
import { ViewerExtensionDirective } from './directives/viewer-extension.directive';
import { ViewerToolbarActionsComponent } from './components/viewer-toolbar-actions.component';
import { DirectiveModule } from '../directives/directive.module';
import { A11yModule } from '@angular/cdk/a11y';
@NgModule({
imports: [
CommonModule,
MaterialModule,
TranslateModule,
FormsModule,
ReactiveFormsModule,
ToolbarModule,
PipeModule,
FlexLayoutModule,
DirectiveModule,
A11yModule,
ExtensionsModule
],
declarations: [
PdfPasswordDialogComponent,
ViewerComponent,
ImgViewerComponent,
TxtViewerComponent,
MediaPlayerComponent,
PdfViewerComponent,
PdfThumbComponent,
PdfThumbListComponent,
ViewerExtensionDirective,
UnknownFormatComponent,
ViewerToolbarComponent,
ViewerSidebarComponent,
ViewerOpenWithComponent,
ViewerMoreActionsComponent,
ViewerToolbarActionsComponent
],
exports: [
ViewerComponent,
ImgViewerComponent,
TxtViewerComponent,
MediaPlayerComponent,
PdfViewerComponent,
PdfPasswordDialogComponent,
PdfThumbComponent,
PdfThumbListComponent,
ViewerExtensionDirective,
UnknownFormatComponent,
ViewerToolbarComponent,
ViewerSidebarComponent,
ViewerOpenWithComponent,
ViewerMoreActionsComponent,
ViewerToolbarActionsComponent
]
})
export class ViewerModule {
}