[ADF-1534] Automatic PDF rendering for unsupported types. (#2313)

* move viewer dialog to a separate folder

* make dialog take full screen size

* automatic PDF conversion

* mime type icon in the title

* [ADF-1530] correct plain text viewer resolving

* separate lightweigh pdf view for the dialog
This commit is contained in:
Denys Vuika 2017-09-11 10:08:44 +01:00 committed by Popovics András
parent 3ba93a6840
commit 1cf2db48ac
15 changed files with 1007 additions and 89 deletions

View File

@ -122,6 +122,7 @@ import { NodePermissionDirective } from './src/directives/node-permission.direct
import { UploadDirective } from './src/directives/upload.directive'; import { UploadDirective } from './src/directives/upload.directive';
import { FileSizePipe } from './src/pipes/file-size.pipe'; import { FileSizePipe } from './src/pipes/file-size.pipe';
import { MimeTypeIconPipe } from './src/pipes/mime-type-icon.pipe';
import { HighlightPipe } from './src/pipes/text-highlight.pipe'; import { HighlightPipe } from './src/pipes/text-highlight.pipe';
import { TimeAgoPipe } from './src/pipes/time-ago.pipe'; import { TimeAgoPipe } from './src/pipes/time-ago.pipe';
@ -193,7 +194,8 @@ export function providers() {
SharedLinksApiService, SharedLinksApiService,
SitesApiService, SitesApiService,
DiscoveryApiService, DiscoveryApiService,
HighlightTransformService HighlightTransformService,
MomentDateAdapter
]; ];
} }
@ -212,6 +214,15 @@ export function obsoleteMdlDirectives() {
]; ];
} }
export function pipes() {
return [
FileSizePipe,
HighlightPipe,
TimeAgoPipe,
MimeTypeIconPipe
];
}
export function createTranslateLoader(http: Http, logService: LogService) { export function createTranslateLoader(http: Http, logService: LogService) {
return new AlfrescoTranslateLoader(http, logService); return new AlfrescoTranslateLoader(http, logService);
} }
@ -240,6 +251,7 @@ export function createTranslateLoader(http: Http, logService: LogService) {
], ],
declarations: [ declarations: [
...obsoleteMdlDirectives(), ...obsoleteMdlDirectives(),
...pipes(),
UploadDirective, UploadDirective,
NodePermissionDirective, NodePermissionDirective,
HighlightDirective, HighlightDirective,
@ -251,16 +263,12 @@ export function createTranslateLoader(http: Http, logService: LogService) {
InfoDrawerTitleDirective, InfoDrawerTitleDirective,
InfoDrawerButtonsDirective, InfoDrawerButtonsDirective,
InfoDrawerContentDirective, InfoDrawerContentDirective,
FileSizePipe,
HighlightPipe,
TimeAgoPipe,
CreateFolderDialogComponent, CreateFolderDialogComponent,
DownloadZipDialogComponent DownloadZipDialogComponent
], ],
providers: [ providers: [
...providers(), ...providers(),
...deprecatedProviders(), ...deprecatedProviders(),
MomentDateAdapter,
{ {
provide: TRANSLATION_PROVIDER, provide: TRANSLATION_PROVIDER,
multi: true, multi: true,
@ -284,14 +292,12 @@ export function createTranslateLoader(http: Http, logService: LogService) {
PaginationModule, PaginationModule,
ToolbarModule, ToolbarModule,
...obsoleteMdlDirectives(), ...obsoleteMdlDirectives(),
...pipes(),
UploadDirective, UploadDirective,
NodePermissionDirective, NodePermissionDirective,
HighlightDirective, HighlightDirective,
DataColumnComponent, DataColumnComponent,
DataColumnListComponent, DataColumnListComponent,
FileSizePipe,
HighlightPipe,
TimeAgoPipe,
CreateFolderDialogComponent, CreateFolderDialogComponent,
DownloadZipDialogComponent, DownloadZipDialogComponent,
InfoDrawerComponent, InfoDrawerComponent,

View File

@ -0,0 +1,31 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Pipe, PipeTransform } from '@angular/core';
import { ThumbnailService } from '../services/thumbnail.service';
@Pipe({
name: 'adfMimeTypeIcon'
})
export class MimeTypeIconPipe implements PipeTransform {
constructor(private thumbnailService: ThumbnailService) { }
transform(text: string): string {
return this.thumbnailService.getMimeTypeIcon(text);
}
}

View File

@ -16,7 +16,7 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AlfrescoApi } from 'alfresco-js-api'; import { AlfrescoApi, ContentApi, NodesApi, RenditionsApi } from 'alfresco-js-api';
import * as alfrescoApi from 'alfresco-js-api'; import * as alfrescoApi from 'alfresco-js-api';
import { AppConfigService } from './app-config.service'; import { AppConfigService } from './app-config.service';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
@ -26,10 +26,22 @@ export class AlfrescoApiService {
private alfrescoApi: AlfrescoApi; private alfrescoApi: AlfrescoApi;
public getInstance(): AlfrescoApi { getInstance(): AlfrescoApi {
return this.alfrescoApi; return this.alfrescoApi;
} }
get contentApi(): ContentApi {
return this.getInstance().content;
}
get nodesApi(): NodesApi {
return this.getInstance().nodes;
}
get renditionsApi(): RenditionsApi {
return this.getInstance().core.renditionsApi;
}
constructor(private appConfig: AppConfigService, constructor(private appConfig: AppConfigService,
private storage: StorageService) { private storage: StorageService) {

View File

@ -16,9 +16,9 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { RenditionEntry, RenditionPaging } from 'alfresco-js-api';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
import { AlfrescoApiService } from './alfresco-api.service'; import { AlfrescoApiService } from './alfresco-api.service';
import { LogService } from './log.service';
/** /**
* RenditionsService * RenditionsService
@ -28,52 +28,57 @@ import { LogService } from './log.service';
@Injectable() @Injectable()
export class RenditionsService { export class RenditionsService {
constructor(private apiService: AlfrescoApiService, constructor(private apiService: AlfrescoApiService) {
private logService: LogService) {
} }
isRenditionAvailable(nodeId: string, encoding: string) { isRenditionAvailable(nodeId: string, encoding: string): Observable<boolean> {
return Observable.create((observer) => { return Observable.create((observer) => {
this.getRendition(nodeId, encoding).subscribe((res) => { this.getRendition(nodeId, encoding).subscribe(
(res) => {
let isAvailable = true; let isAvailable = true;
if (res.entry.status === 'NOT_CREATED') { if (res.entry.status.toString() === 'NOT_CREATED') {
isAvailable = false; isAvailable = false;
} }
observer.next(isAvailable); observer.next(isAvailable);
observer.complete(); observer.complete();
}, () => { },
() => {
observer.next(false); observer.next(false);
observer.complete(); observer.complete();
}); }
);
}); });
} }
isConversionPossible(nodeId: string, encoding: string) { isConversionPossible(nodeId: string, encoding: string): Observable<boolean> {
return Observable.create((observer) => { return Observable.create((observer) => {
this.getRendition(nodeId, encoding).subscribe(() => { this.getRendition(nodeId, encoding).subscribe(
() => {
observer.next(true); observer.next(true);
observer.complete(); observer.complete();
}, () => { },
() => {
observer.next(false); observer.next(false);
observer.complete(); observer.complete();
}); }
);
}); });
} }
getRendition(nodeId: string, encoding: string) { getRenditionUrl(nodeId: string, encoding: string): string {
return Observable.fromPromise(this.apiService.getInstance().core.renditionsApi.getRendition(nodeId, encoding)) return this.apiService.contentApi.getRenditionUrl(nodeId, 'pdf');
.catch(err => this.handleError(err));
} }
getRenditionsListByNodeId(nodeId: string) { getRendition(nodeId: string, encoding: string): Observable<RenditionEntry> {
return Observable.fromPromise(this.apiService.getInstance().core.renditionsApi.getRenditions(nodeId)) return Observable.fromPromise(this.apiService.renditionsApi.getRendition(nodeId, encoding));
.catch(err => this.handleError(err));
} }
createRendition(nodeId: string, encoding: string) { getRenditionsListByNodeId(nodeId: string): Observable<RenditionPaging> {
return Observable.fromPromise(this.apiService.getInstance().core.renditionsApi.createRendition(nodeId, {id: encoding})) return Observable.fromPromise(this.apiService.renditionsApi.getRenditions(nodeId));
.catch(err => this.handleError(err)); }
createRendition(nodeId: string, encoding: string): Observable<{}> {
return Observable.fromPromise(this.apiService.renditionsApi.createRendition(nodeId, {id: encoding}));
} }
convert(nodeId: string, encoding: string, pollingInterval: number = 1000) { convert(nodeId: string, encoding: string, pollingInterval: number = 1000) {
@ -85,12 +90,7 @@ export class RenditionsService {
return Observable.interval(interval) return Observable.interval(interval)
.switchMap(() => this.getRendition(nodeId, encoding)) .switchMap(() => this.getRendition(nodeId, encoding))
.takeWhile((data) => { .takeWhile((data) => {
return (data.entry.status !== 'CREATED'); return (data.entry.status.toString() !== 'CREATED');
}); });
} }
private handleError(error: any): Observable<any> {
this.logService.error(error);
return Observable.throw(error || 'Server error');
}
} }

View File

@ -26,15 +26,16 @@ import { MediaPlayerComponent } from './src/components/mediaPlayer.component';
import { NotSupportedFormatComponent } from './src/components/notSupportedFormat.component'; import { NotSupportedFormatComponent } from './src/components/notSupportedFormat.component';
import { PdfViewerComponent } from './src/components/pdfViewer.component'; import { PdfViewerComponent } from './src/components/pdfViewer.component';
import { TxtViewerComponent } from './src/components/txtViewer.component'; import { TxtViewerComponent } from './src/components/txtViewer.component';
import { ViewerDialogComponent } from './src/components/viewer-dialog.component'; import { PdfViewComponent } from './src/components/viewer-dialog/pdf-view/pdf-view.component';
import { ViewerDialogComponent } from './src/components/viewer-dialog/viewer-dialog.component';
import { ViewerComponent } from './src/components/viewer.component'; import { ViewerComponent } from './src/components/viewer.component';
import { ExtensionViewerDirective } from './src/directives/extension-viewer.directive'; import { ExtensionViewerDirective } from './src/directives/extension-viewer.directive';
import { RenderingQueueServices } from './src/services/rendering-queue.services'; import { RenderingQueueServices } from './src/services/rendering-queue.services';
import { ViewerService } from './src/services/viewer.service'; import { ViewerService } from './src/services/viewer.service';
export { ViewerDialogComponent } from './src/components/viewer-dialog.component'; export { ViewerDialogComponent } from './src/components/viewer-dialog/viewer-dialog.component';
export { ViewerDialogSettings } from './src/components/viewer-dialog.settings'; export { ViewerDialogSettings } from './src/components/viewer-dialog/viewer-dialog.settings';
export { ViewerService } from './src/services/viewer.service'; export { ViewerService } from './src/services/viewer.service';
export const VIEWER_DIRECTIVES: any[] = [ export const VIEWER_DIRECTIVES: any[] = [
@ -45,7 +46,8 @@ export const VIEWER_DIRECTIVES: any[] = [
NotSupportedFormatComponent, NotSupportedFormatComponent,
PdfViewerComponent, PdfViewerComponent,
ExtensionViewerDirective, ExtensionViewerDirective,
ViewerDialogComponent ViewerDialogComponent,
PdfViewComponent
]; ];
@NgModule({ @NgModule({

View File

@ -17,7 +17,6 @@
import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { ContentService, RenditionsService } from 'ng2-alfresco-core'; import { ContentService, RenditionsService } from 'ng2-alfresco-core';
import { AlfrescoApiService } from 'ng2-alfresco-core';
const DEFAULT_CONVERSION_ENCODING = 'pdf'; const DEFAULT_CONVERSION_ENCODING = 'pdf';
@ -54,9 +53,7 @@ export class NotSupportedFormatComponent implements OnInit, OnDestroy {
constructor( constructor(
private contentService: ContentService, private contentService: ContentService,
private renditionsService: RenditionsService, private renditionsService: RenditionsService) {}
private apiService: AlfrescoApiService
) {}
/** /**
* Checks for available renditions if the nodeId is present * Checks for available renditions if the nodeId is present
@ -86,11 +83,12 @@ export class NotSupportedFormatComponent implements OnInit, OnDestroy {
checkRendition(encoding: string = DEFAULT_CONVERSION_ENCODING): void { checkRendition(encoding: string = DEFAULT_CONVERSION_ENCODING): void {
this.renditionsService.getRendition(this.nodeId, encoding) this.renditionsService.getRendition(this.nodeId, encoding)
.subscribe( .subscribe(
(response: any) => { (response) => {
if (response.entry.status === 'NOT_CREATED') { const status = response.entry.status.toString();
if (status === 'NOT_CREATED') {
this.convertible = true; this.convertible = true;
this.displayable = false; this.displayable = false;
} else if (response.entry.status === 'CREATED') { } else if (status === 'CREATED') {
this.convertible = false; this.convertible = false;
this.displayable = true; this.displayable = true;
} }
@ -119,7 +117,7 @@ export class NotSupportedFormatComponent implements OnInit, OnDestroy {
* Show the PDF rendition of the node * Show the PDF rendition of the node
*/ */
showPDF(): void { showPDF(): void {
this.renditionUrl = this.apiService.getInstance().content.getRenditionUrl(this.nodeId, DEFAULT_CONVERSION_ENCODING); this.renditionUrl = this.renditionsService.getRenditionUrl(this.nodeId, DEFAULT_CONVERSION_ENCODING);
this.isConversionStarted = false; this.isConversionStarted = false;
this.isConversionFinished = true; this.isConversionFinished = true;
} }

View File

@ -0,0 +1,69 @@
<div id="viewer-pdf-container" class="viewer-pdf-container" (window:resize)="onResize()">
<div id="viewer-viewerPdf" class="pdfViewer">
<div *ngIf="isLoading" class="adf-pdf-view__loading-screen">
<h2>Loading</h2>
<div>
<md-spinner></md-spinner>
</div>
</div>
</div>
</div>
<ng-container *ngIf="showToolbar">
<div class="viewer-toolbar-pagination">
<div
id="viewer-previous-page-button"
aria-label="arrow left"
class="button-page left"
tabindex="0"
(click)="previousPage()">
<md-icon>keyboard_arrow_left</md-icon>
</div>
<div class="viewer-page-counter left" >
<input #page
class="viewer-pagenumber-input left"
type="text"
pattern="-?[0-9]*(\.[0-9]+)?"
value="{{ displayPage }}"
(keyup.enter)="inputPage(page.value)">
<div class="left viewer-total-pages">/ {{ totalPages }}</div>
</div>
<div
id="viewer-next-page-button"
aria-label="arrow right"
class="button-page left"
tabindex="0"
(click)="nextPage()">
<md-icon>keyboard_arrow_right</md-icon>
</div>
</div>
<div class="viewer-toolbar-command">
<div
id="viewer-scale-page-button"
aria-label="zoom out map"
class="button-page left"
tabindex="0"
(click)="pageFit()">
<md-icon>zoom_out_map</md-icon>
</div>
<div
id="viewer-zoom-in-button"
aria-label="zoom in"
class="button-page left"
tabindex="0"
(click)="zoomIn()">
<md-icon>zoom_in</md-icon>
</div>
<div
id="viewer-zoom-out-button"
aria-label="zoom out"
class="button-page left"
tabindex="0"
(click)="zoomOut()">
<md-icon>zoom_out</md-icon>
</div>
</div>
</ng-container>

View File

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

View File

@ -0,0 +1,386 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, HostListener, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { LogService } from 'ng2-alfresco-core';
import { RenderingQueueServices } from '../../../services/rendering-queue.services';
declare let PDFJS: any;
@Component({
selector: 'adf-pdf-view',
templateUrl: 'pdf-view.component.html',
styleUrls: [ 'pdf-view.component.scss' ],
providers: [ RenderingQueueServices ],
host: { 'class': 'adf-pdf-view' },
encapsulation: ViewEncapsulation.None
})
export class PdfViewComponent implements OnInit {
@Input()
fileUrl: string;
page: number;
displayPage: number;
totalPages: number;
loadingPercent: number;
showToolbar = false;
isLoading = false;
private currentPdfDocument: any;
private pdfViewer: any;
private currentScaleMode: string = 'auto';
private currentScale: number;
private MAX_AUTO_SCALE: number = 1.25;
private DEFAULT_SCALE_DELTA: number = 1.1;
private MIN_SCALE: number = 0.25;
private MAX_SCALE: number = 10.0;
constructor(private renderingQueueServices: RenderingQueueServices,
private logService: LogService) {
}
ngOnInit() {
if (this.fileUrl) {
this.render(this.fileUrl);
}
}
private render(src) {
return new Promise((resolve, reject) => {
this.isLoading = true;
const loadingTask = this.getPDFJS().getDocument(src);
loadingTask.onProgress = (progressData) => {
let level = progressData.loaded / progressData.total;
this.loadingPercent = Math.round(level * 100);
};
loadingTask.then(
(pdfDocument) => {
this.currentPdfDocument = pdfDocument;
this.totalPages = pdfDocument.numPages;
this.page = 1;
this.displayPage = 1;
this.initPDFViewer(this.currentPdfDocument);
this.currentPdfDocument.getPage(1).then(
() => {
this.scalePage('auto');
this.showToolbar = true;
this.isLoading = false;
resolve();
},
(error) => {
this.isLoading = false;
reject(error);
}
);
},
(error) => {
this.isLoading = false;
reject(error);
}
);
});
}
/**
* return the PDFJS global object (exist to facilitate the mock of PDFJS in the test)
*
* @returns {PDFJS}
*/
getPDFJS() {
return PDFJS;
}
initPDFViewer(pdfDocument: any) {
let documentContainer = document.getElementById('viewer-pdf-container');
let viewer: any = document.getElementById('viewer-viewerPdf');
window.document.addEventListener('scroll', (event) => {
this.watchScroll(event.target);
}, true);
this.pdfViewer = new PDFJS.PDFViewer({
container: documentContainer,
viewer: viewer,
renderingQueue: this.renderingQueueServices
});
this.renderingQueueServices.setViewer(this.pdfViewer);
this.pdfViewer.setDocument(pdfDocument);
}
/**
* Method to scale the page current support implementation
*
* @param {string} scaleMode - new scale mode
*/
scalePage(scaleMode) {
this.currentScaleMode = scaleMode;
if (this.pdfViewer) {
let viewerContainer = document.getElementById('viewer-main-container');
let documentContainer = document.getElementById('viewer-pdf-container');
let widthContainer;
let heigthContainer;
if (viewerContainer && viewerContainer.clientWidth <= documentContainer.clientWidth) {
widthContainer = viewerContainer.clientWidth;
heigthContainer = viewerContainer.clientHeight;
} else {
widthContainer = documentContainer.clientWidth;
heigthContainer = documentContainer.clientHeight;
}
let currentPage = this.pdfViewer._pages[this.pdfViewer._currentPageNumber - 1];
let padding = 20;
let pageWidthScale = (widthContainer - padding) / currentPage.width * currentPage.scale;
let pageHeightScale = (heigthContainer - padding) / currentPage.width * currentPage.scale;
let scale;
switch (this.currentScaleMode) {
case 'page-actual':
scale = 1;
break;
case 'page-width':
scale = pageWidthScale;
break;
case 'page-height':
scale = pageHeightScale;
break;
case 'page-fit':
scale = Math.min(pageWidthScale, pageHeightScale);
break;
case 'auto':
let horizontalScale;
if (this.isLandscape) {
horizontalScale = Math.min(pageHeightScale, pageWidthScale);
} else {
horizontalScale = pageWidthScale;
}
scale = Math.min(this.MAX_AUTO_SCALE, horizontalScale);
break;
default:
this.logService.error('pdfViewSetScale: \'' + scaleMode + '\' is an unknown zoom value.');
return;
}
this.setScaleUpdatePages(scale);
}
}
/**
* Update all the pages with the newScale scale
*
* @param {number} newScale - new scale page
*/
setScaleUpdatePages(newScale: number) {
if (!this.isSameScale(this.currentScale, newScale)) {
this.currentScale = newScale;
this.pdfViewer._pages.forEach(function (currentPage) {
currentPage.update(newScale);
});
this.pdfViewer.update();
}
}
/**
* method to check if the request scale of the page is the same for avoid unuseful re-rendering
*
* @param {number} oldScale - old scale page
* @param {number} newScale - new scale page
*
* @returns {boolean}
*/
isSameScale(oldScale: number, newScale: number) {
return (newScale === oldScale);
}
/**
* method to check if is a land scape view
*
* @param {number} width
* @param {number} height
*
* @returns {boolean}
*/
isLandscape(width: number, height: number) {
return (width > height);
}
/**
* Method triggered when the page is resized
*/
onResize() {
this.scalePage(this.currentScaleMode);
}
/**
* toggle the fit page pdf
*/
pageFit() {
if (this.currentScaleMode !== 'page-fit') {
this.scalePage('page-fit');
} else {
this.scalePage('auto');
}
}
/**
* zoom in page pdf
*
* @param {number} ticks
*/
zoomIn(ticks: number) {
let newScale: any = this.currentScale;
do {
newScale = (newScale * this.DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.ceil(newScale * 10) / 10;
newScale = Math.min(this.MAX_SCALE, newScale);
} while (--ticks > 0 && newScale < this.MAX_SCALE);
this.currentScaleMode = 'auto';
this.setScaleUpdatePages(newScale);
}
/**
* zoom out page pdf
*
* @param {number} ticks
*/
zoomOut(ticks: number) {
let newScale: any = this.currentScale;
do {
newScale = (newScale / this.DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.floor(newScale * 10) / 10;
newScale = Math.max(this.MIN_SCALE, newScale);
} while (--ticks > 0 && newScale > this.MIN_SCALE);
this.currentScaleMode = 'auto';
this.setScaleUpdatePages(newScale);
}
/**
* load the previous page
*/
previousPage() {
if (this.pdfViewer && this.page > 1) {
this.page--;
this.displayPage = this.page;
this.pdfViewer.currentPageNumber = this.page;
}
}
/**
* load the next page
*/
nextPage() {
if (this.pdfViewer && this.page < this.totalPages) {
this.page++;
this.displayPage = this.page;
this.pdfViewer.currentPageNumber = this.page;
}
}
/**
* load the page in input
*
* @param {string} page - page to load
*/
inputPage(page: string) {
let pageInput = parseInt(page, 10);
if (!isNaN(pageInput) && pageInput > 0 && pageInput <= this.totalPages) {
this.page = pageInput;
this.displayPage = this.page;
this.pdfViewer.currentPageNumber = this.page;
} else {
this.displayPage = this.page;
}
}
/**
* Litener Scroll Event
*
* @param {any} target
*/
watchScroll(target) {
let outputPage = this.getVisibleElement(target);
if (outputPage) {
this.page = outputPage.id;
this.displayPage = this.page;
}
}
/**
* find out what elements are visible within a scroll pane
*
* @param {any} target
*
* @returns {Object} page
*/
getVisibleElement(target) {
return this.pdfViewer._pages.find((page) => {
return this.isOnScreen(page, target);
});
}
/**
* check if a page is visible
*
* @param {any} page
* @param {any} target
*
* @returns {boolean}
*/
isOnScreen(page: any, target: any) {
let viewport: any = {};
viewport.top = target.scrollTop;
viewport.bottom = viewport.top + target.scrollHeight;
let bounds: any = {};
bounds.top = page.div.offsetTop;
bounds.bottom = bounds.top + page.viewport.height;
return ((bounds.top <= viewport.bottom) && (bounds.bottom >= viewport.top));
}
/**
* Litener Keyboard Event
* @param {KeyboardEvent} event
*/
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
let key = event.keyCode;
if (key === 39) { // right arrow
this.nextPage();
} else if (key === 37) {// left arrow
this.previousPage();
}
}
}

View File

@ -3,7 +3,10 @@
<button md-icon-button [md-dialog-close]="true" mdTooltip="Close and go back"> <button md-icon-button [md-dialog-close]="true" mdTooltip="Close and go back">
<md-icon>arrow_back</md-icon> <md-icon>arrow_back</md-icon>
</button> </button>
<span>
<img class="adf-viewer-dialog__mimetype-icon" [src]="fileMimeType | adfMimeTypeIcon">
<span>{{ fileName }}</span> <span>{{ fileName }}</span>
</span>
</adf-toolbar-title> </adf-toolbar-title>
<button md-button [mdMenuTriggerFor]="mnuOpenWith"> <button md-button [mdMenuTriggerFor]="mnuOpenWith">
@ -74,7 +77,18 @@
</adf-toolbar> </adf-toolbar>
<md-dialog-content> <md-dialog-content>
<ng-container [ngSwitch]="viewerType">
<ng-container *ngIf="isLoading">
<div class="adf-viewer-dialog__loading-screen">
<h2>Loading</h2>
<div>
<md-spinner></md-spinner>
</div>
</div>
</ng-container>
<ng-container *ngIf="!isLoading" [ngSwitch]="viewerType">
<ng-container *ngSwitchCase="'image'"> <ng-container *ngSwitchCase="'image'">
<div class="adf-viewer-dialog__image-view"> <div class="adf-viewer-dialog__image-view">
@ -89,10 +103,10 @@
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'pdf'"> <ng-container *ngSwitchCase="'pdf'">
<adf-pdf-viewer class="adf-viewer-dialog__pdf-view" <adf-pdf-view
[showToolbar]="true" class="adf-viewer-dialog__pdf-view"
[urlFile]="fileUrl"> [fileUrl]="fileUrl">
</adf-pdf-viewer> </adf-pdf-view>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'video'"> <ng-container *ngSwitchCase="'video'">

View File

@ -2,8 +2,8 @@
.mat-dialog-container { .mat-dialog-container {
padding: 0; padding: 0;
max-width: none; max-width: none;
width: 99vw; width: 100vw;
height: 99vh; height: 100vh;
} }
} }
@ -11,10 +11,28 @@
.mat-dialog-content { .mat-dialog-content {
display: flex; display: flex;
max-height: 90vh; max-height: 100vh;
justify-content: center; justify-content: center;
} }
&__mimetype-icon {
width: 24px;
height: 24px;
}
&__loading-screen {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 85vh;
.md-spinner {
margin: 0 auto;
}
}
&__info-drawer { &__info-drawer {
width: 350px; width: 350px;
display: block; display: block;
@ -46,7 +64,7 @@
text-align: center; text-align: center;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
height: 90vh; height: 95vh;
img { img {
max-width: 100%; max-width: 100%;
object-fit: contain; object-fit: contain;
@ -83,9 +101,7 @@
&__pdf-view { &__pdf-view {
.viewer-pdf-container { .viewer-pdf-container {
top: 52px; top: 50px;
left: 7px;
right: 7px;
border: 1px solid lightgray; border: 1px solid lightgray;
background-color: lightgray; background-color: lightgray;
} }

View File

@ -18,6 +18,7 @@
import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core'; import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core';
import { Http, Response } from '@angular/http'; import { Http, Response } from '@angular/http';
import { MD_DIALOG_DATA, MdDialogRef } from '@angular/material'; import { MD_DIALOG_DATA, MdDialogRef } from '@angular/material';
import { RenditionsService } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
import { ViewerDialogSettings } from './viewer-dialog.settings'; import { ViewerDialogSettings } from './viewer-dialog.settings';
@ -42,9 +43,13 @@ export class ViewerDialogComponent implements OnInit {
unknownFormatIcon = 'wifi_tethering'; unknownFormatIcon = 'wifi_tethering';
unknownFormatText = 'Document preview could not be loaded.'; unknownFormatText = 'Document preview could not be loaded.';
isLoading: boolean = false;
viewerType: string = null; viewerType: string = null;
asText: Observable<string>; asText: Observable<string>;
private nodeId: string;
private types = [ private types = [
{ mimeType: 'application/x-javascript', type: 'text' }, { mimeType: 'application/x-javascript', type: 'text' },
{ mimeType: 'application/pdf', type: 'pdf' } { mimeType: 'application/pdf', type: 'pdf' }
@ -52,11 +57,13 @@ export class ViewerDialogComponent implements OnInit {
constructor(private dialogRef: MdDialogRef<ViewerDialogComponent>, constructor(private dialogRef: MdDialogRef<ViewerDialogComponent>,
@Inject(MD_DIALOG_DATA) settings: ViewerDialogSettings, @Inject(MD_DIALOG_DATA) settings: ViewerDialogSettings,
private http: Http) { private http: Http,
private renditionService: RenditionsService) {
this.fileUrl = settings.fileUrl; this.fileUrl = settings.fileUrl;
this.fileName = settings.fileName; this.fileName = settings.fileName;
this.fileMimeType = settings.fileMimeType; this.fileMimeType = settings.fileMimeType;
this.downloadUrl = settings.downloadUrl; this.downloadUrl = settings.downloadUrl;
this.nodeId = settings.nodeId;
} }
ngOnInit() { ngOnInit() {
@ -65,6 +72,10 @@ export class ViewerDialogComponent implements OnInit {
if (this.viewerType !== 'unknown') { if (this.viewerType !== 'unknown') {
this.allowInfoDrawer = true; this.allowInfoDrawer = true;
} else {
if (this.nodeId) {
this.displayAsPdf(this.nodeId);
}
} }
} }
@ -117,4 +128,43 @@ export class ViewerDialogComponent implements OnInit {
close() { close() {
this.dialogRef.close(true); this.dialogRef.close(true);
} }
private displayAsPdf(nodeId: string) {
this.isLoading = true;
this.renditionService.getRendition(nodeId, 'pdf').subscribe(
(response) => {
const status = response.entry.status.toString();
if (status === 'CREATED') {
this.isLoading = false;
this.showRenditionPdf(nodeId);
} else if (status === 'NOT_CREATED') {
this.renditionService.convert(nodeId, 'pdf').subscribe({
complete: () => {
this.isLoading = false;
this.showRenditionPdf(nodeId);
},
error: (error) => {
this.isLoading = false;
console.log(error);
}
});
} else {
this.isLoading = false;
}
},
(err) => {
this.isLoading = false;
console.log(err);
}
);
}
private showRenditionPdf(nodeId: string) {
if (nodeId) {
this.viewerType = 'pdf';
this.fileUrl = this.renditionService.getRenditionUrl(nodeId, 'pdf');
}
}
} }

View File

@ -20,4 +20,6 @@ export interface ViewerDialogSettings {
fileMimeType?: string; fileMimeType?: string;
fileName?: string; fileName?: string;
downloadUrl?: string; downloadUrl?: string;
nodeId?: string;
} }

View File

@ -52,7 +52,7 @@
</adf-media-player> </adf-media-player>
</ng-container> </ng-container>
<ng-container *ngIf="isText()"> <ng-container *ngIf="isText() && !isExternalSupportedExtension()">
<adf-txt-viewer [urlFile]="urlFileContent" [blobFile]="blobFile"></adf-txt-viewer> <adf-txt-viewer [urlFile]="urlFileContent" [blobFile]="blobFile"></adf-txt-viewer>
</ng-container> </ng-container>

View File

@ -20,8 +20,8 @@ import { MdDialog } from '@angular/material';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { AlfrescoApiService } from 'ng2-alfresco-core'; import { AlfrescoApiService } from 'ng2-alfresco-core';
import { ViewerDialogComponent } from './../components/viewer-dialog.component'; import { ViewerDialogComponent } from './../components/viewer-dialog/viewer-dialog.component';
import { ViewerDialogSettings } from './../components/viewer-dialog.settings'; import { ViewerDialogSettings } from './../components/viewer-dialog/viewer-dialog.settings';
@Injectable() @Injectable()
export class ViewerService { export class ViewerService {
@ -30,21 +30,14 @@ export class ViewerService {
private apiService: AlfrescoApiService) { private apiService: AlfrescoApiService) {
} }
private get contentApi() {
return this.apiService.getInstance().content;
}
private get nodesApi() {
return this.apiService.getInstance().nodes;
}
showViewerForNode(node: MinimalNodeEntryEntity): Promise<boolean> { showViewerForNode(node: MinimalNodeEntryEntity): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>((resolve, reject) => {
const settings: ViewerDialogSettings = { const settings: ViewerDialogSettings = {
fileName: node.name, fileName: node.name,
fileMimeType: node.content.mimeType, fileMimeType: node.content.mimeType,
fileUrl: this.contentApi.getContentUrl(node.id, false), fileUrl: this.apiService.contentApi.getContentUrl(node.id, false),
downloadUrl: this.contentApi.getContentUrl(node.id, true) downloadUrl: this.apiService.contentApi.getContentUrl(node.id, true),
nodeId: node.id
}; };
const dialogRef = this.dialog.open(ViewerDialogComponent, { const dialogRef = this.dialog.open(ViewerDialogComponent, {
@ -60,7 +53,7 @@ export class ViewerService {
showViewerForNodeId(nodeId: string): Promise<boolean> { showViewerForNodeId(nodeId: string): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>((resolve, reject) => {
this.nodesApi.getNode(nodeId).then( this.apiService.nodesApi.getNode(nodeId).then(
(node: MinimalNodeEntity) => { (node: MinimalNodeEntity) => {
if (node && node.entry && node.entry.isFile) { if (node && node.entry && node.entry.isFile) {
return this.showViewerForNode(node.entry); return this.showViewerForNode(node.entry);