From 5596738703873a69aa1768e0657390c485f93c84 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 20 Jul 2018 15:21:37 -0400 Subject: [PATCH] [ACA-1542] Print file (#518) * Add Services and Behaviors to support print file dialog, which opens a browser renderable version of the file with the print dialog; after which, closes the window when the print dialog is closed. --- cspell.json | 3 +- src/app/app.module.ts | 4 +- .../components/preview/preview.component.html | 2 + .../components/preview/preview.component.ts | 6 + src/app/services/view-util.service.ts | 183 ++++++++++++++++++ src/app/testing/app-testing.module.ts | 4 +- 6 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 src/app/services/view-util.service.ts diff --git a/cspell.json b/cspell.json index 7296ea871..aee8e8720 100644 --- a/cspell.json +++ b/cspell.json @@ -37,7 +37,8 @@ "tooltips", "unindent", "exif", - "cardview" + "cardview", + "webm" ], "dictionaries": [ "html", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 49cdd7502..6f8d97b72 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -84,6 +84,7 @@ import { NodePermissionsDialogComponent } from './dialogs/node-permissions/node- import { NodePermissionsDirective } from './directives/node-permissions.directive'; import { PermissionsManagerComponent } from './components/permission-manager/permissions-manager.component'; import { AppRouteReuseStrategy } from './app.routes.strategy'; +import { ViewUtilService} from './services/view-util.service'; import { ExtensionService } from './extensions/extension.service'; export function setupExtensionServiceFactory(service: ExtensionService): Function { @@ -169,7 +170,8 @@ export function setupExtensionServiceFactory(service: ExtensionService): Functio useFactory: setupExtensionServiceFactory, deps: [ExtensionService], multi: true - } + }, + ViewUtilService ], entryComponents: [ LibraryDialogComponent, diff --git a/src/app/components/preview/preview.component.html b/src/app/components/preview/preview.component.html index 4497904a1..f4500aa7a 100644 --- a/src/app/components/preview/preview.component.html +++ b/src/app/components/preview/preview.component.html @@ -4,9 +4,11 @@ [fileNodeId]="nodeId" [allowNavigate]="navigateMultiple" [allowSidebar]="true" + [allowPrint] ="true" [canNavigateBefore]="previousNodeId" [canNavigateNext]="nextNodeId" [overlayMode]="true" + (print) = "printFile($event)" (showViewerChange)="onVisibilityChanged($event)" (navigateBefore)="onNavigateBefore()" (navigateNext)="onNavigateNext()"> diff --git a/src/app/components/preview/preview.component.ts b/src/app/components/preview/preview.component.ts index 9affe0674..572e29dca 100644 --- a/src/app/components/preview/preview.component.ts +++ b/src/app/components/preview/preview.component.ts @@ -33,6 +33,7 @@ import { PageComponent } from '../page.component'; import { ContentApiService } from '../../services/content-api.service'; import { ExtensionService } from '../../extensions/extension.service'; import { ContentManagementService } from '../../services/content-management.service'; +import { ViewUtilService } from '../../services/view-util.service'; import { ContentActionRef } from '../../extensions/action.extensions'; @Component({ selector: 'app-preview', @@ -59,6 +60,7 @@ export class PreviewComponent extends PageComponent implements OnInit { private preferences: UserPreferencesService, private route: ActivatedRoute, private router: Router, + private viewUtils: ViewUtilService, store: Store, extensions: ExtensionService, content: ContentManagementService) { @@ -341,6 +343,10 @@ export class PreviewComponent extends PageComponent implements OnInit { this.onVisibilityChanged(false); } + printFile(event: any) { + this.viewUtils.printFileGeneric(this.nodeId, this.node.content.mimeType); + } + private getNavigationCommands(url: string): any[] { const urlTree: UrlTree = this.router.parseUrl(url); const urlSegmentGroup: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; diff --git a/src/app/services/view-util.service.ts b/src/app/services/view-util.service.ts new file mode 100644 index 000000000..64e7f33f1 --- /dev/null +++ b/src/app/services/view-util.service.ts @@ -0,0 +1,183 @@ +import {Injectable} from '@angular/core'; +import {AlfrescoApiService, LogService} from '@alfresco/adf-core'; +import {RenditionEntry} from 'alfresco-js-api'; +import {ContentApiService} from './content-api.service'; + +@Injectable() +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 + */ + public 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. + * @type {number} + */ + 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'] + }; + + constructor(private apiService: AlfrescoApiService, + private contentApi: ContentApiService, + 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 + * @param {string} url + * @param {string} type + */ + public printFile(url: string, type: string) { + const pwa = window.open(url, ViewUtilService.TARGET); + // 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(); + }; + } else { + pwa.onload = () => { + pwa.print(); + pwa.onfocus = () => { + setTimeout( () => { + pwa.close(); + }, 10); + }; + }; + } + } + + /** + * 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) + * @param {string} objectId + * @param {string} objectType + */ + public printFileGeneric(objectId: string, mimeType: string) { + 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); + }); + } + + public getRenditionUrl(nodeId: string, type: string, renditionExists: boolean): string { + return (renditionExists && type !== ViewUtilService.ContentGroup.IMAGE) ? + this.apiService.contentApi.getRenditionUrl(nodeId, ViewUtilService.ContentGroup.PDF) : + this.contentApi.getContentUrl(nodeId, false); + } + + /** + * From ViewerComponent + * @param {string} nodeId + * @param {string} renditionId + * @param {number} retries + * @returns {Promise} + */ + 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 await this.waitRendition(nodeId, renditionId, retries); + } + } + } + + + /** + * From ViewerComponent + * @param {string} mimeType + * @returns {string} + */ + 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'; + } + + /** + * From ViewerComponent + * @param {number} ms + * @returns {Promise} + */ + public wait(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + /** + * From ViewerComponent + * @param {string} nodeId + * @returns {string} + */ + public async getRendition(nodeId: string, renditionId: string): Promise { + const supported = await this.apiService.renditionsApi.getRenditions(nodeId); + let rendition = supported.list.entries.find(obj => obj.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)); + } + +} diff --git a/src/app/testing/app-testing.module.ts b/src/app/testing/app-testing.module.ts index 3648b3fe6..6a4422636 100644 --- a/src/app/testing/app-testing.module.ts +++ b/src/app/testing/app-testing.module.ts @@ -60,6 +60,7 @@ import { NodeActionsService } from '../services/node-actions.service'; import { NodePermissionService } from '../services/node-permission.service'; import { ContentApiService } from '../services/content-api.service'; import { ExtensionService } from '../extensions/extension.service'; +import {ViewUtilService} from '../services/view-util.service'; @NgModule({ imports: [ @@ -112,7 +113,8 @@ import { ExtensionService } from '../extensions/extension.service'; NodeActionsService, NodePermissionService, ContentApiService, - ExtensionService + ExtensionService, + ViewUtilService ] }) export class AppTestingModule {}