diff --git a/docs/extending.md b/docs/extending.md index d713d2236..aa3d5a023 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -817,6 +817,14 @@ on how to register your own entries to be re-used at runtime. | disabled | Toggles disabled state. Can be assigned from other plugins. | | order | The order of the element. | +#### Tab components + +Every component you assign for the tab content receives the following additional properties at runtime: + +| Name | Type | Description | +| --- | --- | --- | +| node | MinimalNodeEntryEntity | Node entry to be displayed. | + ### Toolbar The toolbar extension point is represented by an array of Content Action references. @@ -920,6 +928,7 @@ declared in the `rules` section: Viewer component in ACA supports the following extension points: +* Content Viewers * `More` toolbar actions * `Open With` actions @@ -931,6 +940,7 @@ Viewer component in ACA supports the following extension points: "features": { "viewer": { + "content": [], "toolbar:": [], "openWith": [] } @@ -938,6 +948,46 @@ Viewer component in ACA supports the following extension points: } ``` +#### Content View + +You can provide custom components that render particular type of the content based on extensions. + +```json +{ + "$schema": "../../../extension.schema.json", + "$version": "1.0.0", + "$name": "plugin1", + + "features": { + "viewer": { + "content": [ + { + "id": "app.viewer.pdf", + "fileExtension": "pdf", + "component": "app.components.tabs.metadata" + }, + { + "id": "app.viewer.docx", + "fileExtension": "docx", + "component": "app.components.tabs.comments" + } + ] + } + } +} +``` + +In the example above we replace `PDF` view with the `metadata` tab +and `DOCX` view with the `comments` tab. + +Every custom component receives the following properties at runtime: + +| Name | Type | Description | +| --- | --- | --- | +| node | MinimalNodeEntryEntity | Node entry to be displayed. | +| url | string | File content URL. | +| extension | string | File name extension. | + #### Toolbar actions The ADF Viewer component allows providing custom entries for the `More` menu button on the toolbar. diff --git a/extension.schema.json b/extension.schema.json index 26c888340..346411af4 100644 --- a/extension.schema.json +++ b/extension.schema.json @@ -268,6 +268,32 @@ } } } + }, + "viewerExtensionRef": { + "type": "object", + "required": ["id", "component", "fileExtension"], + "properties": { + "id": { + "description": "Unique identifier for the navigation group", + "type": "string" + }, + "component": { + "description": "Component id", + "type": "string" + }, + "fileExtension": { + "description": "Target file extension", + "type": "string" + }, + "order": { + "description": "Group order", + "type": "number" + }, + "disabled": { + "description": "Toggles the disabled state", + "type": "boolean" + } + } } }, @@ -337,6 +363,12 @@ "type": "array", "items": { "$ref": "#/definitions/contentActionRef" }, "minItems": 1 + }, + "content": { + "description": "Viewer content extensions", + "type": "array", + "items": { "$ref": "#/definitions/viewerExtensionRef" }, + "minItems": 1 } } }, diff --git a/src/app/components/preview/preview-extension.component.ts b/src/app/components/preview/preview-extension.component.ts new file mode 100644 index 000000000..9c43a806b --- /dev/null +++ b/src/app/components/preview/preview-extension.component.ts @@ -0,0 +1,105 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { + Component, + Input, + ComponentRef, + OnInit, + ComponentFactoryResolver, + ViewChild, + ViewContainerRef, + OnDestroy, + OnChanges +} from '@angular/core'; +import { ExtensionService } from '../../extensions/extension.service'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; + +@Component({ + selector: 'app-preview-extension', + template: `
` +}) +export class PreviewExtensionComponent implements OnInit, OnChanges, OnDestroy { + @ViewChild('content', { read: ViewContainerRef }) + content: ViewContainerRef; + + @Input() + id: string; + + @Input() + url: string; + + @Input() + extension: string; + + @Input() + node: MinimalNodeEntryEntity; + + private componentRef: ComponentRef; + + constructor( + private extensions: ExtensionService, + private componentFactoryResolver: ComponentFactoryResolver + ) {} + + ngOnInit() { + if (!this.id) { + return; + } + + const componentType = this.extensions.getComponentById(this.id); + if (componentType) { + const factory = this.componentFactoryResolver.resolveComponentFactory( + componentType + ); + if (factory) { + this.content.clear(); + this.componentRef = this.content.createComponent(factory, 0); + this.updateInstance(); + } + } + } + + ngOnChanges() { + this.updateInstance(); + } + + ngOnDestroy() { + if (this.componentRef) { + this.componentRef.destroy(); + this.componentRef = null; + } + } + + private updateInstance() { + if (this.componentRef && this.componentRef.instance) { + const instance = this.componentRef.instance; + + instance.node = this.node; + instance.url = this.url; + instance.extension = this.extension; + } + } +} diff --git a/src/app/components/preview/preview.component.html b/src/app/components/preview/preview.component.html index 075bdbec7..21ddd4a80 100644 --- a/src/app/components/preview/preview.component.html +++ b/src/app/components/preview/preview.component.html @@ -28,5 +28,19 @@ + + + + + + + + + + diff --git a/src/app/components/preview/preview.component.ts b/src/app/components/preview/preview.component.ts index 915f41b93..0ec099ad1 100644 --- a/src/app/components/preview/preview.component.ts +++ b/src/app/components/preview/preview.component.ts @@ -35,6 +35,7 @@ import { ExtensionService } from '../../extensions/extension.service'; import { ContentManagementService } from '../../services/content-management.service'; import { ContentActionRef } from '../../extensions/action.extensions'; import { ViewUtilService } from './view-util.service'; +import { ViewerExtensionRef } from '../../extensions/viewer.extensions'; @Component({ selector: 'app-preview', @@ -55,6 +56,7 @@ export class PreviewComponent extends PageComponent implements OnInit { nextNodeId: string; navigateMultiple = false; openWith: Array = []; + contentExtensions: Array = []; constructor( private contentApi: ContentApiService, @@ -97,6 +99,7 @@ export class PreviewComponent extends PageComponent implements OnInit { }); this.openWith = this.extensions.openWithActions; + this.contentExtensions = this.extensions.viewerContentExtensions; } /** diff --git a/src/app/components/preview/preview.module.ts b/src/app/components/preview/preview.module.ts index 2e979d979..c6eb3a32a 100644 --- a/src/app/components/preview/preview.module.ts +++ b/src/app/components/preview/preview.module.ts @@ -35,6 +35,7 @@ import { PreviewComponent } from './preview.component'; import { ViewUtilService } from './view-util.service'; import * as pdfjsLib from 'pdfjs-dist'; +import { PreviewExtensionComponent } from './preview-extension.component'; pdfjsLib.PDFJS.workerSrc = 'pdf.worker.js'; pdfjsLib.PDFJS.disableFontFace = true; @@ -57,6 +58,7 @@ const routes: Routes = [ ], declarations: [ PreviewComponent, + PreviewExtensionComponent ], providers: [ ViewUtilService diff --git a/src/app/extensions/extension.config.ts b/src/app/extensions/extension.config.ts index 51a5acb2d..6fb4aed11 100644 --- a/src/app/extensions/extension.config.ts +++ b/src/app/extensions/extension.config.ts @@ -28,6 +28,7 @@ import { RouteRef } from './routing.extensions'; import { RuleRef } from './rule.extensions'; import { ActionRef, ContentActionRef } from './action.extensions'; import { SidebarTabRef } from './sidebar.extensions'; +import { ViewerExtensionRef } from './viewer.extensions'; export interface ExtensionConfig { $name: string; @@ -43,6 +44,7 @@ export interface ExtensionConfig { viewer?: { openWith?: Array; toolbar?: Array; + content?: Array; }; navbar?: Array; sidebar?: Array; diff --git a/src/app/extensions/extension.service.ts b/src/app/extensions/extension.service.ts index 10475b703..c335ca164 100644 --- a/src/app/extensions/extension.service.ts +++ b/src/app/extensions/extension.service.ts @@ -39,6 +39,7 @@ import * as core from './evaluators/core.evaluators'; import { NodePermissionService } from '../services/node-permission.service'; import { SidebarTabRef } from './sidebar.extensions'; import { ProfileResolver } from '../services/profile.resolver'; +import { ViewerExtensionRef } from './viewer.extensions'; @Injectable() export class ExtensionService implements RuleContext { @@ -56,6 +57,7 @@ export class ExtensionService implements RuleContext { toolbarActions: Array = []; viewerToolbarActions: Array = []; + viewerContentExtensions: Array = []; contextMenuActions: Array = []; openWithActions: Array = []; createActions: Array = []; @@ -136,6 +138,7 @@ export class ExtensionService implements RuleContext { this.routes = this.loadRoutes(config); this.toolbarActions = this.loadToolbarActions(config); this.viewerToolbarActions = this.loadViewerToolbarActions(config); + this.viewerContentExtensions = this.loadViewerContentExtensions(config); this.contextMenuActions = this.loadContextMenuActions(config); this.openWithActions = this.loadViewerOpenWith(config); this.createActions = this.loadCreateActions(config); @@ -187,6 +190,15 @@ export class ExtensionService implements RuleContext { return []; } + protected loadViewerContentExtensions(config: ExtensionConfig): Array { + if (config && config.features && config.features.viewer) { + return (config.features.viewer.content || []) + .filter(entry => !entry.disabled) + .sort(this.sortByOrder); + } + return []; + } + protected loadContextMenuActions(config: ExtensionConfig): Array { if (config && config.features && config.features.contextMenu) { return (config.features.contextMenu || []) diff --git a/src/app/extensions/viewer.extensions.ts b/src/app/extensions/viewer.extensions.ts new file mode 100644 index 000000000..aff7d1d2e --- /dev/null +++ b/src/app/extensions/viewer.extensions.ts @@ -0,0 +1,33 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export interface ViewerExtensionRef { + id: string; + fileExtension: string; + component: string; + + disabled?: boolean; + order?: number; +} diff --git a/src/assets/app.extensions.json b/src/assets/app.extensions.json index 3cd71936a..78c062239 100644 --- a/src/assets/app.extensions.json +++ b/src/assets/app.extensions.json @@ -725,6 +725,20 @@ "visible": "app.toolbar.permissions" } } + ], + "content": [ + { + "id": "app.viewer.pdf", + "disabled": true, + "fileExtension": "pdf", + "component": "app.components.tabs.metadata" + }, + { + "id": "app.viewer.docx", + "disabled": true, + "fileExtension": "docx", + "component": "app.components.tabs.comments" + } ] }, "sidebar": [ diff --git a/src/assets/extension.schema.json b/src/assets/extension.schema.json index 26c888340..346411af4 100644 --- a/src/assets/extension.schema.json +++ b/src/assets/extension.schema.json @@ -268,6 +268,32 @@ } } } + }, + "viewerExtensionRef": { + "type": "object", + "required": ["id", "component", "fileExtension"], + "properties": { + "id": { + "description": "Unique identifier for the navigation group", + "type": "string" + }, + "component": { + "description": "Component id", + "type": "string" + }, + "fileExtension": { + "description": "Target file extension", + "type": "string" + }, + "order": { + "description": "Group order", + "type": "number" + }, + "disabled": { + "description": "Toggles the disabled state", + "type": "boolean" + } + } } }, @@ -337,6 +363,12 @@ "type": "array", "items": { "$ref": "#/definitions/contentActionRef" }, "minItems": 1 + }, + "content": { + "description": "Viewer content extensions", + "type": "array", + "items": { "$ref": "#/definitions/viewerExtensionRef" }, + "minItems": 1 } } },