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
}
}
},