/*! * @license * Copyright 2016 Alfresco Software, Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Location } from '@angular/common'; import { Component, ContentChild, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges, TemplateRef, ViewEncapsulation } from '@angular/core'; import { MinimalNodeEntryEntity } from 'alfresco-js-api'; import { BaseEvent } from '../../events'; import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { LogService } from '../../services/log.service'; import { RenditionsService } from '../../services/renditions.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'; @Component({ selector: 'adf-viewer', templateUrl: './viewer.component.html', styleUrls: ['./viewer.component.scss'], host: { 'class': 'adf-viewer' }, encapsulation: ViewEncapsulation.None }) export class ViewerComponent implements OnChanges { @ContentChild(ViewerToolbarComponent) toolbar: ViewerToolbarComponent; @ContentChild(ViewerSidebarComponent) sidebar: ViewerSidebarComponent; @ContentChild(ViewerOpenWithComponent) mnuOpenWith: ViewerOpenWithComponent; @ContentChild(ViewerMoreActionsComponent) mnuMoreActions: ViewerMoreActionsComponent; @Input() urlFile = ''; @Input() urlFileViewer: string = null; @Input() blobFile: Blob; @Input() fileNodeId: string = null; @Input() sharedLinkId: string = null; @Input() overlayMode = false; @Input() showViewer = true; @Input() showToolbar = true; @Input() displayName: string; @Input() allowGoBack = true; @Input() allowDownload = true; @Input() allowPrint = false; @Input() allowShare = false; @Input() allowSidebar = false; @Input() showSidebar = false; @Input() sidebarPosition = 'right'; @Input() sidebarTemplate: TemplateRef = null; @Input() mimeType: string; @Input() fileName: string; @Input() downloadUrl: string = null; @Output() goBack = new EventEmitter>(); @Output() download = new EventEmitter>(); @Output() print = new EventEmitter>(); @Output() share = new EventEmitter>(); @Output() showViewerChange = new EventEmitter(); @Output() extensionChange = new EventEmitter(); viewerType = 'unknown'; isLoading = false; node: MinimalNodeEntryEntity; extensionTemplates: { template: TemplateRef, isVisible: boolean }[] = []; externalExtensions: string[] = []; urlFileContent: string; otherMenu: any; extension: string; sidebarTemplateContext: { node: MinimalNodeEntryEntity } = { node: null }; private extensions = { image: ['png', 'jpg', 'jpeg', 'gif', 'bpm'], media: ['wav', 'mp4', 'mp3', 'webm', 'ogg'], text: ['txt', 'xml', 'js', 'html', 'json'], pdf: ['pdf'] }; private mimeTypes = [ { mimeType: 'application/x-javascript', type: 'text' }, { mimeType: 'application/pdf', type: 'pdf' } ]; constructor(private apiService: AlfrescoApiService, private logService: LogService, private location: Location, private renditionService: RenditionsService) { } isSourceDefined(): boolean { return (this.urlFile || this.blobFile || this.fileNodeId || this.sharedLinkId) ? true : false; } ngOnChanges(changes: SimpleChanges) { if (this.showViewer) { if (!this.isSourceDefined()) { throw new Error('A content source attribute value is missing.'); } return new Promise((resolve, reject) => { if (this.blobFile) { this.displayName = this.getDisplayName('Unknown'); this.isLoading = true; this.mimeType = this.blobFile.type; this.viewerType = this.getViewerTypeByMimeType(this.mimeType); this.allowDownload = false; // TODO: wrap blob into the data url and allow downloading this.extensionChange.emit(this.mimeType); this.isLoading = false; this.scrollTop(); resolve(); } else if (this.urlFile) { this.isLoading = true; let filenameFromUrl = this.getFilenameFromUrl(this.urlFile); this.displayName = this.getDisplayName(filenameFromUrl); this.extension = this.getFileExtension(filenameFromUrl); this.urlFileContent = this.urlFile; this.downloadUrl = this.urlFile; this.fileName = this.displayName; this.viewerType = this.urlFileViewer || this.getViewerTypeByExtension(this.extension); if (this.viewerType === 'unknown') { this.viewerType = this.getViewerTypeByMimeType(this.mimeType); } this.extensionChange.emit(this.extension); this.isLoading = false; this.scrollTop(); resolve(); } else if (this.fileNodeId) { this.isLoading = true; this.apiService.getInstance().nodes.getNodeInfo(this.fileNodeId).then( (data: MinimalNodeEntryEntity) => { this.mimeType = data.content.mimeType; this.displayName = this.getDisplayName(data.name); this.urlFileContent = this.apiService.getInstance().content.getContentUrl(data.id); this.extension = this.getFileExtension(data.name); this.fileName = data.name; this.downloadUrl = this.apiService.getInstance().content.getContentUrl(data.id, true); this.viewerType = this.getViewerTypeByExtension(this.extension); if (this.viewerType === 'unknown') { this.viewerType = this.getViewerTypeByMimeType(this.mimeType); } if (this.viewerType === 'unknown') { this.displayNodeRendition(data.id); } else { this.isLoading = false; } this.extensionChange.emit(this.extension); this.sidebarTemplateContext.node = data; this.scrollTop(); resolve(); }, (error) => { this.isLoading = false; reject(error); this.logService.error('This node does not exist'); } ); } else if (this.sharedLinkId) { this.isLoading = true; this.apiService.sharedLinksApi.getSharedLink(this.sharedLinkId).then(details => { this.mimeType = details.entry.content.mimeType; this.displayName = this.getDisplayName(details.entry.name); this.extension = this.getFileExtension(details.entry.name); this.fileName = details.entry.name; this.urlFileContent = this.apiService.contentApi.getSharedLinkContentUrl(this.sharedLinkId, false); this.downloadUrl = this.apiService.contentApi.getSharedLinkContentUrl(this.sharedLinkId, true); this.viewerType = this.getViewerTypeByMimeType(this.mimeType); if (this.viewerType === 'unknown') { this.viewerType = this.getViewerTypeByExtension(this.extension); } if (this.viewerType === 'unknown') { this.displaySharedLinkRendition(this.sharedLinkId); } else { this.isLoading = false; } this.extensionChange.emit(this.extension); this.isLoading = false; resolve(); }); } }); } } toggleSidebar() { this.showSidebar = !this.showSidebar; if (this.showSidebar && this.fileNodeId) { this.apiService.getInstance().nodes.getNodeInfo(this.fileNodeId) .then((data: MinimalNodeEntryEntity) => { this.sidebarTemplateContext.node = data; }); } } private getDisplayName(name) { return this.displayName || name; } scrollTop() { window.scrollTo(0, 1); } getViewerTypeByMimeType(mimeType: string) { if (mimeType) { mimeType = mimeType.toLowerCase(); if (mimeType.startsWith('image/')) { return 'image'; } if (mimeType.startsWith('text/')) { return 'text'; } if (mimeType.startsWith('video/')) { return 'media'; } if (mimeType.startsWith('audio/')) { return 'media'; } const registered = this.mimeTypes.find(t => t.mimeType === mimeType); if (registered) { return registered.type; } } return 'unknown'; } getViewerTypeByExtension(extension: string) { if (extension) { extension = extension.toLowerCase(); } if (this.isCustomViewerExtension(extension)) { return 'custom'; } if (this.extensions.image.indexOf(extension) >= 0) { return 'image'; } if (this.extensions.media.indexOf(extension) >= 0) { return 'media'; } if (this.extensions.text.indexOf(extension) >= 0) { return 'text'; } if (this.extensions.pdf.indexOf(extension) >= 0) { return 'pdf'; } return 'unknown'; } onBackButtonClick() { if (this.overlayMode) { this.close(); } else { const event = new BaseEvent(); this.goBack.next(event); if (!event.defaultPrevented) { this.location.back(); } } } /** * 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 {string} url - url file * @returns {string} name file */ getFilenameFromUrl(url: string): string { let anchor = url.indexOf('#'); let query = url.indexOf('?'); let end = Math.min( anchor > 0 ? anchor : url.length, query > 0 ? query : url.length); return url.substring(url.lastIndexOf('/', end) + 1, end); } /** * Get file extension from the string. * Supports the URL formats like: * http://localhost/test.jpg?cache=1000 * http://localhost/test.jpg#cache=1000 * * @param {string} fileName - file name * @returns {string} file name extension */ getFileExtension(fileName: string): string { if (fileName) { const match = fileName.match(/\.([^\./\?\#]+)($|\?|\#)/); return match ? match[1] : null; } return null; } isCustomViewerExtension(extension: string): boolean { const extensions = this.externalExtensions || []; if (extension && extensions.length > 0) { extension = extension.toLowerCase(); return extensions.indexOf(extension) >= 0; } return false; } /** * Litener Keyboard Event * @param {KeyboardEvent} event */ @HostListener('document:keydown', ['$event']) handleKeyboardEvent(event: KeyboardEvent) { let key = event.keyCode; if (key === 27 && this.overlayMode) { // esc this.close(); } } downloadContent() { if (this.allowDownload && this.downloadUrl && this.fileName) { const args = new BaseEvent(); this.download.next(args); if (!args.defaultPrevented) { const link = document.createElement('a'); link.style.display = 'none'; link.download = this.fileName; link.href = this.downloadUrl; document.body.appendChild(link); link.click(); document.body.removeChild(link); } } } printContent() { if (this.allowPrint) { const args = new BaseEvent(); this.print.next(args); } } shareContent() { if (this.allowShare) { const args = new BaseEvent(); this.share.next(args); } } private async displayNodeRendition(nodeId: string) { this.isLoading = true; try { const rendition = await this.apiService.renditionsApi.getRendition(nodeId, 'pdf'); const status = rendition.entry.status.toString(); if (status === 'CREATED') { this.viewerType = 'pdf'; this.urlFileContent = this.apiService.contentApi.getRenditionUrl(nodeId, 'pdf'); } else if (status === 'NOT_CREATED') { try { await this.renditionService.convert(nodeId, 'pdf').toPromise(); this.viewerType = 'pdf'; this.urlFileContent = this.apiService.contentApi.getRenditionUrl(nodeId, 'pdf'); } catch (error) { this.logService.error(error); } } } catch (error) { this.logService.error(error); try { const imagePreview = await this.apiService.renditionsApi.getRendition(nodeId, 'imgpreview'); if (imagePreview.entry.status.toString() === 'CREATED') { this.viewerType = 'image'; this.urlFileContent = this.apiService.contentApi.getRenditionUrl(nodeId, 'imgpreview'); } } catch (error) { this.logService.error(error); } } this.isLoading = false; } private async displaySharedLinkRendition(sharedId: string) { this.isLoading = true; try { const rendition = await this.apiService.renditionsApi.getSharedLinkRendition(sharedId, 'pdf'); if (rendition.entry.status.toString() === 'CREATED') { this.viewerType = 'pdf'; this.urlFileContent = this.apiService.contentApi.getSharedLinkRenditionUrl(sharedId, 'pdf'); } } catch (error) { this.logService.error(error); try { const rendition = await this.apiService.renditionsApi.getSharedLinkRendition(sharedId, 'imgpreview'); if (rendition.entry.status.toString() === 'CREATED') { this.viewerType = 'image'; this.urlFileContent = this.apiService.contentApi.getSharedLinkRenditionUrl(sharedId, 'imgpreview'); } } catch (error) { this.logService.error(error); } } this.isLoading = false; } }