/*! * @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 { RenditionEntry, RenditionPaging } from '@alfresco/js-api'; import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { LogService } from '../../services/log.service'; import { Subject } from 'rxjs'; @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' }; /** * 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 = new Subject(); urlFileContentChange: Subject = new Subject(); constructor(private apiService: AlfrescoApiService, private logService: LogService) { } /** * 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 ? true : false)); 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.apiService.contentApi.getRenditionUrl(nodeId, ViewUtilService.ContentGroup.PDF) : this.apiService.contentApi.getContentUrl(nodeId, false); } private async waitRendition(nodeId: string, renditionId: string, retries: number): Promise { const rendition = await this.apiService.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 { return new Promise((resolve) => setTimeout(resolve, ms)); } async getRendition(nodeId: string, renditionId: string): Promise { const renditionPaging: RenditionPaging = await this.apiService.renditionsApi.getRenditions(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.apiService.renditionsApi.createRendition(nodeId, { id: renditionId }); rendition = await this.waitRendition(nodeId, renditionId, 0); } catch (err) { this.logService.error(err); } } } return new Promise((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.apiService.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) : this.apiService.contentApi.getRenditionUrl(nodeId, renditionId); this.urlFileContentChange.next(urlFileContent); } } catch (err) { this.logService.error(err); } } private async resolveNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise { renditionId = renditionId.toLowerCase(); const supportedRendition: RenditionPaging = versionId ? await this.apiService.versionsApi.listVersionRenditions(nodeId, versionId) : await this.apiService.renditionsApi.getRenditions(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.apiService.versionsApi.createVersionRendition(nodeId, versionId, { id: renditionId }).then(() => { this.viewerTypeChange.next('in_creation'); }); } else { await this.apiService.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 { let currentRetry: number = 0; return new Promise((resolve, reject) => { const intervalId = setInterval(() => { currentRetry++; if (this.maxRetries >= currentRetry) { if (versionId) { this.apiService.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); } }, () => { return reject(); }); } else { this.apiService.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); } }, () => { return 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.apiService.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) : this.apiService.contentApi.getRenditionUrl(nodeId, renditionId); this.urlFileContentChange.next(urlFileContent); } }