[ACA-3394] Generally available file preview feature for extensions (#1496)

* Adding general purpose preview overlay route

Preparation for file preview feature

Remove unnecessary preview root route and ability to use loadChildren

Extract RouterExtensionService

Remove loadChildren support and use component instead

Cover RouterExtensionService with unit tests

Fix tests

Fix build

Fix rebase issue

Add generally available PluginPreviewAction

Add data option to child routes and navigateBackAsClose option for the preview component

Support plain mode preview

Fix linting

Update to latest alpha of ADF

* Adding documentation

* Rebase fix

* Update to latest adf
This commit is contained in:
Popovics András 2020-08-07 18:24:38 +02:00 committed by GitHub
parent ac6cfdb5b6
commit cd1252cb94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 560 additions and 233 deletions

View File

@ -20,6 +20,7 @@ The components are used to create custom:
| app.toolbar.toggleFavoriteLibrary | ToggleFavoriteLibraryComponent | The toolbar button component that toggles Favorite library state for the selection. | | app.toolbar.toggleFavoriteLibrary | ToggleFavoriteLibraryComponent | The toolbar button component that toggles Favorite library state for the selection. |
| app.toolbar.toggleJoinLibrary | ToggleJoinLibraryComponent | The toolbar button component that toggles Join/Cancel Join request for the selected library | | app.toolbar.toggleJoinLibrary | ToggleJoinLibraryComponent | The toolbar button component that toggles Join/Cancel Join request for the selected library |
| app.toolbar.viewNode | ViewNodeComponent | Action component to view files | | app.toolbar.viewNode | ViewNodeComponent | Action component to view files |
| app.components.preview | PreviewComponent | Preview feature which can be used by plugins. For more info see the [tutorials](/extending/tutorials) about the Preview. |
See [Registration](/extending/registration) section for more details See [Registration](/extending/registration) section for more details
on how to register your own entries to be re-used at runtime. on how to register your own entries to be re-used at runtime.

View File

@ -18,6 +18,18 @@ To create a new route, populate the `routes` section with the corresponding entr
"path": "ext/bin", "path": "ext/bin",
"layout": "app.layout.main", "layout": "app.layout.main",
"component": "your.component.id", "component": "your.component.id",
"children": [
{
"id": "plugin1.routes.bin.preview",
"path": "preview/:nodeId",
"component": "app.components.preview",
"data": {
"navigateBackAsClose": true,
"simplestMode": true
},
"outlet": "viewer"
}
],
"parentRoute": "your-parent-route" "parentRoute": "your-parent-route"
} }
] ]
@ -34,7 +46,8 @@ To create a new route, populate the `routes` section with the corresponding entr
| layout | The layout [component](/extending/components) to use for the route. | | layout | The layout [component](/extending/components) to use for the route. |
| auth | List of [authentication guards](#authentication-guards). Defaults to `[ "app.auth" ]`. | | auth | List of [authentication guards](#authentication-guards). Defaults to `[ "app.auth" ]`. |
| data | Custom property bag to carry with the route. | | data | Custom property bag to carry with the route. |
| parentRoute | The path that the route will become child of | | children | List of child routes of the injected route. [More info](#dynamically-injected-routes-with-their-children) |
| parentRoute | The path that the route will become child of. See more info about and its limitations under the [Child routes](#child-routes) section |
Use the `app.layout.main` value for the `layout` property to get the default application layout, Use the `app.layout.main` value for the `layout` property to get the default application layout,
with header, navigation sidebar and main content area. with header, navigation sidebar and main content area.
@ -72,6 +85,8 @@ Defaults to the `['app.auth']` value.
## Child Routes ## Child Routes
### Injecting child routes under top-level routes: `parentRoute`
Extensions may register a routes that are children of some existing application routes. Extensions may register a routes that are children of some existing application routes.
Imagine the situation when application has the following route structure: Imagine the situation when application has the following route structure:
@ -109,6 +124,18 @@ so giving you an option for nested linking: `/files/my-path`.
> For the time being, you can provide child entries only for the root (top-level) routes. > For the time being, you can provide child entries only for the root (top-level) routes.
### Dynamically injected routes with their children
For a dynamically created route, we can define the children property as well, which contain the child routes of the mainly injected route. For the time being, for a child route, the following properties are supported and translated to Angular's Router configuration:
| Property | Description |
| - | - |
| **id** | Unique identifier. |
| **path** | Runtime path of the route. |
| **component** | The main [component](/extending/components) to use for the route. |
| data | Custom property bag to carry with the route. |
| outlet | Router outlet's name. Especially useful when using the PluginPreviewAction within a plugin |
## Authentication Guards ## Authentication Guards
Below is the list of the authentication guards main application registers on startup. Below is the list of the authentication guards main application registers on startup.

View File

@ -259,3 +259,67 @@ Update the `src/assets/app.extensions.json` file, and insert a new entry to the
``` ```
Now, once you run the application, you should see an extra button that invokes your dialog on every click. Now, once you run the application, you should see an extra button that invokes your dialog on every click.
### File preview from a plugin with custom route
There might be scenarios where you build a plugin with a custom route, and from that route you might want to preview a file within an overlay.
When having a plugin's entry point in a custom route, using the `/view` root-level application routes for previewing a file might be contradictory, since hitting any of these urls results a navigation away from the original route implying a reload of the original route's entry component when closing the preview panel (navigating back).
#### Example
Let's say you have a custom plugin with which you can start a process with any of your files. The plugin registers a custom route (`start-process`) with its entry component, where the user can start a process.
In this component the user can fill in a form with different values for text fields and selectboxes and select a file. But for file selection, we would like to provide a preview functionality (with the `PreviewComponent` provided by the core application) to let the user be sure that the right file was selected. Obviously having a form filled in values (but not saved) means, that we don't want to loose our filled in data just because we are previewing a file. Because of this we would like the file preview to be opened in an overlay mode. The core application has one overlay region already defined for this reason, called `viewer`. This is the named router outlet we need to target without route change.
#### Solution
In our plugin we need to do the following steps:
##### Registering the custom route in the plugin.json
We need to add the custom route with our entry component and its child route for the preview:
```json
{
...
"routes": [{
"id": "start-process",
"path": "start-process",
"parentRoute": "",
"layout": "app.layout.main",
// The component we register to be our entry point for this particular route
"component": "myplugin.components.start-process",
"children": [
{
"id": "start-process-preview",
// It can be accessed on the "/start-process(viewer:preview/nodeId)" route
"path": "preview/:nodeId",
"component": "app.components.preview",
"data": {
// Using history.back() when closing the preview
"navigateBackAsClose": true,
// Disabling complex action and buttons for the preview
"simplestMode": true
},
// We would like to target that named router outlet which is used for the viewer overlay
"outlet": "viewer"
}
]
}]
...
```
##### Dispatching the right action within our component to open the file preview
```ts
import { PluginPreviewAction } from '@alfresco/aca-shared/store';
@Component({...})
export class StartProcessComponent {
...
onFilePreview({ nodeId }) {
this.store.dispatch(new PluginPreviewAction('start-process-cloud', nodeId));
}
}
```

49
package-lock.json generated
View File

@ -5,12 +5,12 @@
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@alfresco/adf-cli": { "@alfresco/adf-cli": {
"version": "3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6", "version": "3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac",
"resolved": "https://registry.npmjs.org/@alfresco/adf-cli/-/adf-cli-3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-cli/-/adf-cli-3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac.tgz",
"integrity": "sha512-xpAHyi1BnFtNtUxGTHJ+iaBehqn5K+wzbJA4mIwaCigJ0kVwUlZsl0o/RNVrv5MBcEKfXXOqw+DdMwO/zf33dw==", "integrity": "sha512-CyYT/+/X1nKJ92VFd60CcSDHiLH0Y2FztGRAhOkcIfL/x0RAXp8OLXrSXImwsJVAcKeIR+WJZiy9sAACndElyw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@alfresco/js-api": "3.10.0-8e063ceb7b7d71ab3001b50fc8d0a79a1a8797c5", "@alfresco/js-api": "3.10.0-23b97d354151ced90eb2fc68f9d57cad852376d7",
"commander": "^4.0.0", "commander": "^4.0.0",
"ejs": "^2.6.1", "ejs": "^2.6.1",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
@ -20,17 +20,6 @@
"spdx-license-list": "^5.0.0" "spdx-license-list": "^5.0.0"
}, },
"dependencies": { "dependencies": {
"@alfresco/js-api": {
"version": "3.10.0-8e063ceb7b7d71ab3001b50fc8d0a79a1a8797c5",
"resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-3.10.0-8e063ceb7b7d71ab3001b50fc8d0a79a1a8797c5.tgz",
"integrity": "sha512-2ogpnobx5TwWmZy7FO4L5Z1ZE1rVWDxtiRF6Jl1prmHML8GWY3GKV7uTOEQvDX569RRYo/fzsdiwExYipNr1QQ==",
"dev": true,
"requires": {
"event-emitter": "^0.3.5",
"minimatch": "3.0.4",
"superagent": "^5.1.2"
}
},
"commander": { "commander": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@ -40,42 +29,42 @@
} }
}, },
"@alfresco/adf-content-services": { "@alfresco/adf-content-services": {
"version": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", "version": "3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac",
"resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac.tgz",
"integrity": "sha512-3QSUoV1wya4wTGeIPaMnIvERMm6EmXvD/PVWkU4NZc9H+c4pEWDU2gaQisFoAA/OTWQnliBA7lt602lUWOtWlg==", "integrity": "sha512-37lZG0s9WfyZYvuwiA/JZPO8iHWarRPUHujQbPPCGhRBUqCteix/7Qj2QXkF5aqjkkLpN4tAej/OyU18edYc2Q==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }
}, },
"@alfresco/adf-core": { "@alfresco/adf-core": {
"version": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", "version": "3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac",
"resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac.tgz",
"integrity": "sha512-gKq7nwtck2tPywARF/TL/KqJKfJ7+8cwaGDLU7uri5PbbLJ1l7Abew2rhebckMa6kMd+FUpHu4qJDkLELAfEng==", "integrity": "sha512-B2LQ6OU/qT8lhDbX4t6ZMa/zSTYDyse9VuzJwzAC2s0ppLcypp6AzNNeuTMdlyJyLEPXviSXVIAjxT1DJvMFbg==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }
}, },
"@alfresco/adf-extensions": { "@alfresco/adf-extensions": {
"version": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", "version": "3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac",
"resolved": "https://registry.npmjs.org/@alfresco/adf-extensions/-/adf-extensions-3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-extensions/-/adf-extensions-3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac.tgz",
"integrity": "sha512-W9W3CTsUcwcuXd0ZgPpOiOYrnMhonqlFb1zxqlgTLs5c0KRovsvtyDVEGN3Uv4Fmllc484HfoJVE/6qvvZvBVw==", "integrity": "sha512-DIC1QKnOaVugsNr2+nqH8BhfspN6/1MEovIf4IERuc142viGyDpGxkgaTzF/qDnYMBoAzDf1x2Qfz0y97g+HWw==",
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }
}, },
"@alfresco/adf-testing": { "@alfresco/adf-testing": {
"version": "3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6", "version": "3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac",
"resolved": "https://registry.npmjs.org/@alfresco/adf-testing/-/adf-testing-3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6.tgz", "resolved": "https://registry.npmjs.org/@alfresco/adf-testing/-/adf-testing-3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac.tgz",
"integrity": "sha512-uxWkZWReVavmnv3sFtheDIt1/H4Wv3PZkbSLqQMeq+neWxa1qWJ5BuYaI/zVGLc2dqU0mqVlZupNUnGFbR1bVQ==", "integrity": "sha512-4Pj8G8QXmyuLJir8CP9G57SprZfIxfwY3L3JWrQmwLLFTQdw+pEDlrNAD42yvUfSN42g+RvFs3zZTxVPhb7GwA==",
"dev": true, "dev": true,
"requires": { "requires": {
"tslib": "^2.0.0" "tslib": "^2.0.0"
} }
}, },
"@alfresco/js-api": { "@alfresco/js-api": {
"version": "3.10.0-42769ecb372a1b7f6841e1971bb63f9f0fc78753", "version": "3.10.0-23b97d354151ced90eb2fc68f9d57cad852376d7",
"resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-3.10.0-42769ecb372a1b7f6841e1971bb63f9f0fc78753.tgz", "resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-3.10.0-23b97d354151ced90eb2fc68f9d57cad852376d7.tgz",
"integrity": "sha512-k8xByKGltU3T5othhHjrGQ0kLRQ8zTkiMr+VYb2s2C4aSrq/nMoTbJc47F/f6EU549XmvZuNGCQGTUSQlpo3bA==", "integrity": "sha512-EY8H6gz0T6RCCGh1SjHhklgEd/ulXhEGpu0V2MMTfhHI59kOzyZCGswBnPtbdI7p8Gwnx8oo2tzoSoddm0FzHA==",
"requires": { "requires": {
"event-emitter": "^0.3.5", "event-emitter": "^0.3.5",
"minimatch": "3.0.4", "minimatch": "3.0.4",

View File

@ -16,6 +16,7 @@
"build": "npm run validate-config && npm run build.app -- --prod", "build": "npm run validate-config && npm run build.app -- --prod",
"build.e2e": "npm run build.app -- --prod --configuration=e2e", "build.e2e": "npm run build.app -- --prod --configuration=e2e",
"test": "ng test app --code-coverage", "test": "ng test app --code-coverage",
"unit": "ng test --browsers=Chrome --watch",
"test:ci": "npm run build.extensions && ng test adf-office-services-ext --watch=false && ng test app --code-coverage --watch=false", "test:ci": "npm run build.extensions && ng test adf-office-services-ext --watch=false && ng test app --code-coverage --watch=false",
"lint": "ng lint && npm run spellcheck && npm run e2e.typecheck", "lint": "ng lint && npm run spellcheck && npm run e2e.typecheck",
"update-webdriver": "./scripts/update-webdriver.sh", "update-webdriver": "./scripts/update-webdriver.sh",
@ -32,10 +33,10 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@alfresco/adf-content-services": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", "@alfresco/adf-content-services": "3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac",
"@alfresco/adf-core": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", "@alfresco/adf-core": "3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac",
"@alfresco/adf-extensions": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", "@alfresco/adf-extensions": "3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac",
"@alfresco/js-api": "3.10.0-42769ecb372a1b7f6841e1971bb63f9f0fc78753", "@alfresco/js-api": "3.10.0-23b97d354151ced90eb2fc68f9d57cad852376d7",
"@angular-custom-builders/lite-serve": "0.2.2", "@angular-custom-builders/lite-serve": "0.2.2",
"@angular/animations": "10.0.4", "@angular/animations": "10.0.4",
"@angular/cdk": "^10.0.2", "@angular/cdk": "^10.0.2",
@ -66,8 +67,8 @@
"zone.js": "~0.10.2" "zone.js": "~0.10.2"
}, },
"devDependencies": { "devDependencies": {
"@alfresco/adf-cli": "3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6", "@alfresco/adf-cli": "3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac",
"@alfresco/adf-testing": "3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6", "@alfresco/adf-testing": "3.10.0-89b37067ff6b1ccde8af1a1611828ee61da0bcac",
"@angular-devkit/build-angular": "~0.1000.4", "@angular-devkit/build-angular": "~0.1000.4",
"@angular-devkit/build-ng-packagr": "~0.1000.5", "@angular-devkit/build-ng-packagr": "~0.1000.5",
"@angular/cli": "^10.0.5", "@angular/cli": "^10.0.5",

View File

@ -37,7 +37,6 @@ import {
reduceEmptyMenus, reduceEmptyMenus,
ExtensionService, ExtensionService,
ExtensionConfig, ExtensionConfig,
ComponentRegisterService,
NavBarGroupRef NavBarGroupRef
} from '@alfresco/adf-extensions'; } from '@alfresco/adf-extensions';
import { AppConfigService } from '@alfresco/adf-core'; import { AppConfigService } from '@alfresco/adf-core';
@ -46,7 +45,6 @@ describe('AppExtensionService', () => {
let service: AppExtensionService; let service: AppExtensionService;
let store: Store<AppStore>; let store: Store<AppStore>;
let extensions: ExtensionService; let extensions: ExtensionService;
let components: ComponentRegisterService;
let appConfigService: AppConfigService; let appConfigService: AppConfigService;
beforeEach(() => { beforeEach(() => {
@ -58,7 +56,6 @@ describe('AppExtensionService', () => {
store = TestBed.inject(Store); store = TestBed.inject(Store);
service = TestBed.inject(AppExtensionService); service = TestBed.inject(AppExtensionService);
extensions = TestBed.inject(ExtensionService); extensions = TestBed.inject(ExtensionService);
components = TestBed.inject(ComponentRegisterService);
}); });
const applyConfig = (config: ExtensionConfig) => { const applyConfig = (config: ExtensionConfig) => {
@ -227,97 +224,6 @@ describe('AppExtensionService', () => {
}); });
}); });
describe('components', () => {
let component1;
beforeEach(() => {
component1 = {};
components.setComponents({
'component-1': component1
});
});
it('should fetch registered component', () => {
const component = service.getComponentById('component-1');
expect(component).toEqual(component1);
});
it('should not fetch registered component', () => {
const component = service.getComponentById('missing');
expect(component).toBeFalsy();
});
});
describe('routes', () => {
let component1, component2;
let guard1;
beforeEach(() => {
applyConfig({
$id: 'test',
$name: 'test',
$version: '1.0.0',
$license: 'MIT',
$vendor: 'Good company',
$runtime: '1.5.0',
routes: [
{
id: 'aca:routes/about',
path: 'ext/about',
component: 'aca:components/about',
layout: 'aca:layouts/main',
auth: ['aca:auth'],
data: {
title: 'Custom About'
}
}
]
});
component1 = {};
component2 = {};
components.setComponents({
'aca:components/about': component1,
'aca:layouts/main': component2
});
guard1 = {};
extensions.authGuards['aca:auth'] = guard1;
});
it('should load routes from the config', () => {
expect(extensions.routes.length).toBe(1);
});
it('should find a route by id', () => {
const route = extensions.getRouteById('aca:routes/about');
expect(route).toBeTruthy();
expect(route.path).toBe('ext/about');
});
it('should not find a route by id', () => {
const route = extensions.getRouteById('some-route');
expect(route).toBeFalsy();
});
it('should build application routes', () => {
const routes = service.getApplicationRoutes();
expect(routes.length).toBe(1);
const route = routes[0];
expect(route.path).toBe('ext/about');
expect(route.component).toEqual(component2);
expect(route.canActivateChild).toEqual([guard1]);
expect(route.canActivate).toEqual([guard1]);
expect(route.children.length).toBe(1);
expect(route.children[0].path).toBe('');
expect(route.children[0].component).toEqual(component1);
});
});
describe('content actions', () => { describe('content actions', () => {
it('should load content actions from the config', () => { it('should load content actions from the config', () => {
applyConfig({ applyConfig({

View File

@ -23,7 +23,7 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { Injectable, Type } from '@angular/core'; import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { MatIconRegistry } from '@angular/material/icon'; import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
@ -53,7 +53,7 @@ import { AppConfigService, AuthenticationService, LogService } from '@alfresco/a
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { RepositoryInfo, NodeEntry } from '@alfresco/js-api'; import { RepositoryInfo, NodeEntry } from '@alfresco/js-api';
import { ViewerRules } from '../models/viewer.rules'; import { ViewerRules } from '../models/viewer.rules';
import { SettingsGroupRef, ExtensionRoute } from '../models/types'; import { SettingsGroupRef } from '../models/types';
import { NodePermissionService } from '../services/node-permission.service'; import { NodePermissionService } from '../services/node-permission.service';
@Injectable({ @Injectable({
@ -62,11 +62,6 @@ import { NodePermissionService } from '../services/node-permission.service';
export class AppExtensionService implements RuleContext { export class AppExtensionService implements RuleContext {
private _references = new BehaviorSubject<ExtensionRef[]>([]); private _references = new BehaviorSubject<ExtensionRef[]>([]);
defaults = {
layout: 'app.layout.main',
auth: ['app.auth']
};
headerActions: Array<ContentActionRef> = []; headerActions: Array<ContentActionRef> = [];
toolbarActions: Array<ContentActionRef> = []; toolbarActions: Array<ContentActionRef> = [];
viewerToolbarActions: Array<ContentActionRef> = []; viewerToolbarActions: Array<ContentActionRef> = [];
@ -318,31 +313,6 @@ export class AppExtensionService implements RuleContext {
return this.sidebarTabs.filter((action) => this.filterVisible(action)); return this.sidebarTabs.filter((action) => this.filterVisible(action));
} }
getComponentById(id: string): Type<{}> {
return this.extensions.getComponentById(id);
}
getApplicationRoutes(): Array<ExtensionRoute> {
return this.extensions.routes.map((route) => {
const guards = this.extensions.getAuthGuards(route.auth && route.auth.length > 0 ? route.auth : this.defaults.auth);
return {
path: route.path,
component: this.getComponentById(route.layout || this.defaults.layout),
canActivateChild: guards,
canActivate: guards,
parentRoute: route.parentRoute,
children: [
{
path: '',
component: this.getComponentById(route.component),
data: route.data
}
]
};
});
}
getCreateActions(): Array<ContentActionRef> { getCreateActions(): Array<ContentActionRef> {
return this.createActions return this.createActions
.filter((action) => this.filterVisible(action)) .filter((action) => this.filterVisible(action))

View File

@ -0,0 +1,283 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
*/
import { TestBed } from '@angular/core/testing';
import { LibTestingModule } from '../testing/lib-testing-module';
import { RouterExtensionService } from './router.extension.service';
import { ExtensionService } from '@alfresco/adf-extensions';
import { Router } from '@angular/router';
import { Type } from '@angular/core';
describe('RouterExtensionService', () => {
let extensionService: ExtensionService;
let service: RouterExtensionService;
let router: Router;
let component1, component2, component3, layoutComponent;
let guard1, guard2, guard3;
beforeEach(() => {
component1 = { name: 'component-1' };
component2 = { name: 'component-2' };
component3 = { name: 'component-3' };
layoutComponent = { name: 'layoutComponent' };
guard1 = { name: 'guard1' };
guard2 = { name: 'guard2' };
guard3 = { name: 'guard3' };
TestBed.configureTestingModule({
imports: [LibTestingModule],
providers: [
{
provide: ExtensionService,
useValue: {
routes: [],
getAuthGuards: (authKeys) => {
const authMapping = {
'app.auth': guard1,
'ext.auth1': guard2,
'ext.auth2': guard3
};
return authKeys.map((authKey) => authMapping[authKey]);
},
getComponentById: (componentKey) => {
const componentMapping = {
'ext:components/about': component1,
'ext:components/settings': component2,
'ext:components/info': component3,
'app.layout.main': layoutComponent
};
return componentMapping[componentKey];
}
}
}
]
});
extensionService = TestBed.get(ExtensionService);
service = TestBed.get(RouterExtensionService);
router = TestBed.get(Router);
router.config = [
{ path: 'login', component: {} as Type<any> },
{ path: 'settings', component: {} as Type<any> },
{ path: 'custom', children: [] },
{
path: '',
children: [
{ path: 'child-route1', component: {} as Type<any> },
{ path: 'child-route2', component: {} as Type<any> }
]
}
];
});
describe('getApplicationRoutes', () => {
function getDummyRoute(overrides) {
return {
id: 'aca:routes/about',
path: 'ext/about',
component: 'ext:components/about',
layout: 'aca:layouts/main',
auth: ['aca:auth'],
data: { title: 'Custom About' },
...overrides
};
}
it('should calculate path properly', () => {
extensionService.routes = [getDummyRoute({ path: 'aca:routes/about' })];
expect(service.getApplicationRoutes().length).toBe(1);
expect(service.getApplicationRoutes()[0].path).toBe('aca:routes/about');
});
it('should calculate parentRoute properly', () => {
extensionService.routes = [getDummyRoute({ parentRoute: 'parent-1' })];
expect(service.getApplicationRoutes()[0].parentRoute).toBe('parent-1');
});
it('should calculate the "component" to default layout, if no "layout" defined for the route', () => {
extensionService.routes = [getDummyRoute({ layout: undefined })];
expect(service.getApplicationRoutes()[0].component).toBe(layoutComponent);
});
it('should calculate the "component" to the registered component matching the "layout" value of the route', () => {
extensionService.routes = [getDummyRoute({ layout: 'ext:components/about' })];
expect(service.getApplicationRoutes()[0].component).toBe(component1);
});
it('should calculate the "canActivateChild" and "canActivate" to default auth guard, if no "auth" defined for the route', () => {
extensionService.routes = [getDummyRoute({ auth: undefined })];
expect(service.getApplicationRoutes()[0].canActivateChild).toEqual([guard1]);
expect(service.getApplicationRoutes()[0].canActivate).toEqual([guard1]);
});
it('should calculate the "canActivateChild" and "canActivate" to default auth guard, if "auth" is defined as [] for the route', () => {
extensionService.routes = [getDummyRoute({ auth: [] })];
expect(service.getApplicationRoutes()[0].canActivateChild).toEqual([guard1]);
expect(service.getApplicationRoutes()[0].canActivate).toEqual([guard1]);
});
it('should calculate the "canActivateChild" and "canActivate" to the registered guard(s) matching the "auth" value of the route', () => {
extensionService.routes = [getDummyRoute({ auth: ['ext.auth1', 'ext.auth2'] })];
expect(service.getApplicationRoutes()[0].canActivateChild).toEqual([guard2, guard3]);
expect(service.getApplicationRoutes()[0].canActivate).toEqual([guard2, guard3]);
});
it('should calculate the main path and data of "children" with the component and data of the route', () => {
const routeData = {};
extensionService.routes = [getDummyRoute({ component: 'ext:components/about', data: routeData })];
expect(service.getApplicationRoutes()[0].children[0].path).toBe('');
expect(service.getApplicationRoutes()[0].children[0].component).toBe(component1);
expect(service.getApplicationRoutes()[0].children[0].data).toBe(routeData);
});
it('should calculate the "children"-s with the "children" value of the route', () => {
extensionService.routes = [
getDummyRoute({
component: 'ext:components/about',
children: [
{
path: 'child-path1',
outlet: 'outlet1',
component: 'ext:components/settings'
},
{
path: 'child-path2',
component: 'ext:components/info'
}
]
})
];
expect(service.getApplicationRoutes()[0].children[0].path).toBe('child-path1');
expect(service.getApplicationRoutes()[0].children[0].component).toBe(component2);
expect(service.getApplicationRoutes()[0].children[0].outlet).toBe('outlet1');
expect(service.getApplicationRoutes()[0].children[1].path).toBe('child-path2');
expect(service.getApplicationRoutes()[0].children[1].component).toBe(component3);
expect(service.getApplicationRoutes()[0].children[1].outlet).toBe(undefined);
expect(service.getApplicationRoutes()[0].children[2].path).toBe('');
expect(service.getApplicationRoutes()[0].children[2].component).toBe(component1);
});
it('should transform more routes, not just one', () => {
extensionService.routes = [getDummyRoute({ path: 'aca:routes/about' }), getDummyRoute({ path: 'aca:routes/login' })];
expect(service.getApplicationRoutes().length).toBe(2);
expect(service.getApplicationRoutes()[0].path).toBe('aca:routes/about');
expect(service.getApplicationRoutes()[1].path).toBe('aca:routes/login');
});
});
describe('mapExtensionRoutes', () => {
it('should prepend routes without parent', () => {
const route1 = {
id: 'aca:routes/about',
path: 'ext/about',
component: 'ext:components/about'
};
extensionService.routes = [route1];
service.mapExtensionRoutes();
expect(router.config.length).toBe(5);
expect(router.config[0].path).toBe(route1.path);
expect(router.config[1].path).toBe('login');
});
it('should add routes to the right parent in reverse order (only one level deep searching)', () => {
const parentRoutePath = '';
const parentRoute = router.config.find((routeConfig) => routeConfig.path === parentRoutePath);
const route1 = {
id: 'aca:routes/about',
path: 'dynamic-extension-route',
component: 'ext:components/about',
parentRoute: parentRoutePath
};
const route2 = {
id: 'aca:routes/info',
path: 'dynamic-extension-route2',
component: 'ext:components/info',
parentRoute: parentRoutePath
};
extensionService.routes = [route1, route2];
expect(parentRoute.children.length).toBe(2);
expect(parentRoute.children[0].path).toBe('child-route1');
service.mapExtensionRoutes();
expect(router.config.length).toBe(4);
expect(parentRoute.children.length).toBe(4);
expect(parentRoute.children[0].path).toBe(route2.path);
expect(parentRoute.children[1].path).toBe(route1.path);
expect(parentRoute.children[2].path).toBe('child-route1');
});
it('should remove plugin related properties from the route when adding to the router config of Angular', () => {
const parentRoutePath = '';
const parentRoute = router.config.find((routeConfig) => routeConfig.path === parentRoutePath);
const route1 = {
id: 'aca:routes/about',
path: 'dynamic-extension-route',
component: 'ext:components/about',
parentRoute: parentRoutePath
};
extensionService.routes = [route1];
service.mapExtensionRoutes();
expect(parentRoute.children[0].path).toBe(route1.path);
expect((parentRoute.children[0] as any).parentRoute).toBe(undefined);
expect((parentRoute.children[0] as any).component).toBe(undefined);
});
it('should NOT add routes at all if parent can not be found (only one level deep searching)', () => {
const parentRoutePath = 'not-existing';
const route1 = {
id: 'aca:routes/about',
path: 'dynamic-extension-route',
component: 'ext:components/about',
parentRoute: parentRoutePath
};
extensionService.routes = [route1];
function invalidParentAddition() {
service.mapExtensionRoutes();
}
expect(invalidParentAddition).not.toThrow();
});
});
});

View File

@ -0,0 +1,108 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 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 <http://www.gnu.org/licenses/>.
*/
import { Injectable, Type } from '@angular/core';
import { ExtensionService } from '@alfresco/adf-extensions';
import { ExtensionRoute } from '../models/types';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class RouterExtensionService {
defaults = {
layout: 'app.layout.main',
auth: ['app.auth']
};
constructor(private router: Router, protected extensions: ExtensionService) {}
mapExtensionRoutes() {
const routesWithoutParent = [];
this.getApplicationRoutes().forEach((extensionRoute: ExtensionRoute) => {
if (this.extensionRouteHasChild(extensionRoute)) {
const parentRoute = this.findRoute(extensionRoute.parentRoute);
if (parentRoute) {
this.convertExtensionRouteToRoute(extensionRoute);
parentRoute.children.unshift(extensionRoute);
}
} else {
routesWithoutParent.push(extensionRoute);
}
});
this.router.config.unshift(...routesWithoutParent);
}
public getApplicationRoutes(): Array<ExtensionRoute> {
return this.extensions.routes.map((route) => {
const guards = this.extensions.getAuthGuards(route.auth && route.auth.length > 0 ? route.auth : this.defaults.auth);
return {
path: route.path,
component: this.getComponentById(route.layout || this.defaults.layout),
canActivateChild: guards,
canActivate: guards,
parentRoute: route.parentRoute,
children: [
...(route['children']
? route['children'].map(({ path, component, outlet, data }) => {
return {
path,
outlet,
data,
component: this.getComponentById(component)
};
})
: []),
{
path: '',
component: this.getComponentById(route.component),
data: route.data
}
]
};
});
}
private getComponentById(id: string): Type<{}> {
return this.extensions.getComponentById(id);
}
private extensionRouteHasChild(route: ExtensionRoute): boolean {
return route.parentRoute !== undefined;
}
private convertExtensionRouteToRoute(extensionRoute: ExtensionRoute) {
delete extensionRoute.parentRoute;
delete extensionRoute.component;
}
private findRoute(parentRoute) {
const routeIndex = this.router.config.findIndex((route) => route.path === parentRoute);
return this.router.config[routeIndex];
}
}

View File

@ -44,6 +44,7 @@ export * from './lib/services/app.service';
export * from './lib/services/content-api.service'; export * from './lib/services/content-api.service';
export * from './lib/services/node-permission.service'; export * from './lib/services/node-permission.service';
export * from './lib/services/app.extension.service'; export * from './lib/services/app.extension.service';
export * from './lib/services/router.extension.service';
export * from './lib/components/generic-error/generic-error.component'; export * from './lib/components/generic-error/generic-error.component';
export * from './lib/components/generic-error/generic-error.module'; export * from './lib/components/generic-error/generic-error.module';

View File

@ -31,7 +31,8 @@ export enum ViewerActionTypes {
ViewNode = 'VIEW_NODE', ViewNode = 'VIEW_NODE',
ViewNodeVersion = 'VIEW_NODE_VERSION', ViewNodeVersion = 'VIEW_NODE_VERSION',
FullScreen = 'FULLSCREEN_VIEWER', FullScreen = 'FULLSCREEN_VIEWER',
ClosePreview = 'CLOSE_PREVIEW' ClosePreview = 'CLOSE_PREVIEW',
PluginPreview = 'PLUGIN_PREVIEW'
} }
export interface ViewNodeExtras { export interface ViewNodeExtras {
@ -67,3 +68,9 @@ export class ClosePreviewAction implements Action {
readonly type = ViewerActionTypes.ClosePreview; readonly type = ViewerActionTypes.ClosePreview;
constructor(public payload?: MinimalNodeEntity) {} constructor(public payload?: MinimalNodeEntity) {}
} }
export class PluginPreviewAction implements Action {
readonly type = ViewerActionTypes.PluginPreview;
constructor(public pluginRoute: string, public nodeId: string) {}
}

View File

@ -28,7 +28,6 @@ import { SetInitialStateAction } from '@alfresco/aca-shared/store';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { mockRoutesWithoutParentRoute, mockRoutesWithParentRoute } from './mock/extension-routes.mock';
describe('AppComponent', () => { describe('AppComponent', () => {
let component: AppComponent; let component: AppComponent;
@ -105,31 +104,4 @@ describe('AppComponent', () => {
expect(storeMock.dispatch['calls'].argsFor(0)[0].payload).toBe('APP.MESSAGES.UPLOAD.ERROR.GENERIC'); expect(storeMock.dispatch['calls'].argsFor(0)[0].payload).toBe('APP.MESSAGES.UPLOAD.ERROR.GENERIC');
}); });
}); });
describe('Routing Configuration', () => {
it('Should extension route be included as child of the defined parent path', () => {
component.mapExtensionRoutes(mockRoutesWithParentRoute);
expect(router.config[0]).toEqual({
path: 'fake-path',
children: [
{
path: 'extension-path',
canActivate: ['fake-guard'],
canActivateChild: ['fake-guard']
}
]
});
});
it('Should extension route be included as root entry when there is no parent path defined', () => {
component.mapExtensionRoutes(mockRoutesWithoutParentRoute);
expect(router.config[0]).toEqual({
component: null,
path: 'extension-path',
canActivate: ['fake-guard'],
canActivateChild: ['fake-guard']
});
});
});
}); });

View File

@ -48,7 +48,7 @@ import {
SetRepositoryInfoAction SetRepositoryInfoAction
} from '@alfresco/aca-shared/store'; } from '@alfresco/aca-shared/store';
import { filter, takeUntil } from 'rxjs/operators'; import { filter, takeUntil } from 'rxjs/operators';
import { AppExtensionService, AppService, ContentApiService, ExtensionRoute } from '@alfresco/aca-shared'; import { RouterExtensionService, AppService, ContentApiService } from '@alfresco/aca-shared';
import { DiscoveryEntry, GroupEntry, Group } from '@alfresco/js-api'; import { DiscoveryEntry, GroupEntry, Group } from '@alfresco/js-api';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { INITIAL_APP_STATE } from './store/initial-state'; import { INITIAL_APP_STATE } from './store/initial-state';
@ -71,7 +71,7 @@ export class AppComponent implements OnInit, OnDestroy {
private alfrescoApiService: AlfrescoApiService, private alfrescoApiService: AlfrescoApiService,
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
private uploadService: UploadService, private uploadService: UploadService,
private extensions: AppExtensionService, private routerExtensionService: RouterExtensionService,
private contentApi: ContentApiService, private contentApi: ContentApiService,
private appService: AppService, private appService: AppService,
private sharedLinksApiService: SharedLinksApiService, private sharedLinksApiService: SharedLinksApiService,
@ -112,8 +112,7 @@ export class AppComponent implements OnInit, OnDestroy {
this.store.dispatch(new SetCurrentUrlAction(router.url)); this.store.dispatch(new SetCurrentUrlAction(router.url));
}); });
const extensionRoutes = this.extensions.getApplicationRoutes(); this.routerExtensionService.mapExtensionRoutes();
this.mapExtensionRoutes(extensionRoutes);
this.uploadService.fileUploadError.subscribe((error) => this.onFileUploadedError(error)); this.uploadService.fileUploadError.subscribe((error) => this.onFileUploadedError(error));
@ -140,30 +139,6 @@ export class AppComponent implements OnInit, OnDestroy {
}); });
} }
private extensionRouteHasChild(route: ExtensionRoute): boolean {
return route.parentRoute !== undefined;
}
private convertExtensionRouteToRoute(extensionRoute: ExtensionRoute) {
delete extensionRoute.parentRoute;
delete extensionRoute.component;
}
mapExtensionRoutes(extensionRoutes: ExtensionRoute[]) {
const routesWithoutParent = [];
extensionRoutes.forEach((extensionRoute: ExtensionRoute) => {
if (this.extensionRouteHasChild(extensionRoute)) {
const routeIndex = this.router.config.findIndex((route) => route.path === extensionRoute.parentRoute);
this.convertExtensionRouteToRoute(extensionRoute);
this.router.config[routeIndex].children.unshift(extensionRoute);
} else {
routesWithoutParent.push(extensionRoute);
}
});
this.router.config.unshift(...routesWithoutParent);
}
private async loadUserProfile() { private async loadUserProfile() {
const groupsEntries: GroupEntry[] = await this.groupService.listAllGroupMembershipsForPerson('-me-', { maxItems: 250 }); const groupsEntries: GroupEntry[] = await this.groupService.listAllGroupMembershipsForPerson('-me-', { maxItems: 250 });

View File

@ -27,10 +27,8 @@
</ng-container> </ng-container>
</adf-viewer-open-with> </adf-viewer-open-with>
<adf-viewer-toolbar-actions> <adf-viewer-toolbar-actions *ngIf="!simplestMode">
<ng-container <ng-container *ngFor="let action of viewerToolbarActions; trackBy: trackByActionId">
*ngFor="let action of viewerToolbarActions; trackBy: trackByActionId"
>
<aca-toolbar-action [actionRef]="action"></aca-toolbar-action> <aca-toolbar-action [actionRef]="action"></aca-toolbar-action>
</ng-container> </ng-container>
</adf-viewer-toolbar-actions> </adf-viewer-toolbar-actions>

View File

@ -24,6 +24,7 @@
*/ */
import { Component, OnInit, OnDestroy, ViewEncapsulation, HostListener } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewEncapsulation, HostListener } from '@angular/core';
import { Location } from '@angular/common';
import { ActivatedRoute, Router, UrlTree, UrlSegmentGroup, UrlSegment, PRIMARY_OUTLET } from '@angular/router'; import { ActivatedRoute, Router, UrlTree, UrlSegmentGroup, UrlSegment, PRIMARY_OUTLET } from '@angular/router';
import { debounceTime, map, takeUntil } from 'rxjs/operators'; import { debounceTime, map, takeUntil } from 'rxjs/operators';
import { UserPreferencesService, ObjectUtils, UploadService, AlfrescoApiService } from '@alfresco/adf-core'; import { UserPreferencesService, ObjectUtils, UploadService, AlfrescoApiService } from '@alfresco/adf-core';
@ -57,6 +58,8 @@ export class PreviewComponent extends PageComponent implements OnInit, OnDestroy
openWith: Array<ContentActionRef> = []; openWith: Array<ContentActionRef> = [];
contentExtensions: Array<ViewerExtensionRef> = []; contentExtensions: Array<ViewerExtensionRef> = [];
showRightSide = false; showRightSide = false;
navigateBackAsClose = false;
simplestMode = false;
recentFileFilters = [ recentFileFilters = [
'TYPE:"content"', 'TYPE:"content"',
@ -92,6 +95,7 @@ export class PreviewComponent extends PageComponent implements OnInit, OnDestroy
private apiService: AlfrescoApiService, private apiService: AlfrescoApiService,
private uploadService: UploadService, private uploadService: UploadService,
private actions$: Actions, private actions$: Actions,
private location: Location,
store: Store<AppStore>, store: Store<AppStore>,
extensions: AppExtensionService, extensions: AppExtensionService,
content: ContentManagementService content: ContentManagementService
@ -111,6 +115,8 @@ export class PreviewComponent extends PageComponent implements OnInit, OnDestroy
this.previewLocation = this.router.url.substr(0, this.router.url.indexOf('/', 1)).replace(/\//g, ''); this.previewLocation = this.router.url.substr(0, this.router.url.indexOf('/', 1)).replace(/\//g, '');
const routeData = this.route.snapshot.data; const routeData = this.route.snapshot.data;
this.navigateBackAsClose = !!routeData.navigateBackAsClose;
this.simplestMode = !!routeData.simplestMode;
if (routeData.navigateMultiple) { if (routeData.navigateMultiple) {
this.navigateMultiple = true; this.navigateMultiple = true;
@ -202,16 +208,18 @@ export class PreviewComponent extends PageComponent implements OnInit, OnDestroy
} }
navigateToFileLocation(shouldNavigate: boolean) { navigateToFileLocation(shouldNavigate: boolean) {
const shouldSkipNavigation = this.routesSkipNavigation.includes(this.previewLocation);
if (shouldNavigate) { if (shouldNavigate) {
const route = this.getNavigationCommands(this.previewLocation); if (this.navigateBackAsClose) {
this.location.back();
} else {
const shouldSkipNavigation = this.routesSkipNavigation.includes(this.previewLocation);
const route = this.getNavigationCommands(this.previewLocation);
if (!shouldSkipNavigation && this.folderId) { if (!shouldSkipNavigation && this.folderId) {
route.push(this.folderId); route.push(this.folderId);
}
this.router.navigate(route);
} }
this.router.navigate(route);
} }
} }

View File

@ -54,6 +54,7 @@ import { LanguagePickerComponent } from '../components/common/language-picker/la
import { LogoutComponent } from '../components/common/logout/logout.component'; import { LogoutComponent } from '../components/common/logout/logout.component';
import { CurrentUserComponent } from '../components/current-user/current-user.component'; import { CurrentUserComponent } from '../components/current-user/current-user.component';
import { AppExtensionService, ExtensionsDataLoaderGuard } from '@alfresco/aca-shared'; import { AppExtensionService, ExtensionsDataLoaderGuard } from '@alfresco/aca-shared';
import { PreviewComponent } from '../components/preview/preview.component';
export function setupExtensions(service: AppExtensionService): Function { export function setupExtensions(service: AppExtensionService): Function {
return () => service.load(); return () => service.load();
@ -90,6 +91,7 @@ export class CoreExtensionsModule {
'app.components.tabs.library.metadata': LibraryMetadataTabComponent, 'app.components.tabs.library.metadata': LibraryMetadataTabComponent,
'app.components.tabs.comments': CommentsTabComponent, 'app.components.tabs.comments': CommentsTabComponent,
'app.components.tabs.versions': VersionsTabComponent, 'app.components.tabs.versions': VersionsTabComponent,
'app.components.preview': PreviewComponent,
'app.toolbar.toggleInfoDrawer': ToggleInfoDrawerComponent, 'app.toolbar.toggleInfoDrawer': ToggleInfoDrawerComponent,
'app.toolbar.toggleFavorite': ToggleFavoriteComponent, 'app.toolbar.toggleFavorite': ToggleFavoriteComponent,
'app.toolbar.toggleFavoriteLibrary': ToggleFavoriteLibraryComponent, 'app.toolbar.toggleFavoriteLibrary': ToggleFavoriteLibraryComponent,

View File

@ -25,7 +25,7 @@
import { Effect, Actions, ofType } from '@ngrx/effects'; import { Effect, Actions, ofType } from '@ngrx/effects';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { map, take } from 'rxjs/operators'; import { map, take, tap } from 'rxjs/operators';
import { import {
AppStore, AppStore,
ViewerActionTypes, ViewerActionTypes,
@ -34,7 +34,8 @@ import {
getCurrentFolder, getCurrentFolder,
getAppSelection, getAppSelection,
FullscreenViewerAction, FullscreenViewerAction,
ViewNodeVersionAction ViewNodeVersionAction,
PluginPreviewAction
} from '@alfresco/aca-shared/store'; } from '@alfresco/aca-shared/store';
import { Router, UrlTree, UrlSegmentGroup, PRIMARY_OUTLET, UrlSegment } from '@angular/router'; import { Router, UrlTree, UrlSegmentGroup, PRIMARY_OUTLET, UrlSegment } from '@angular/router';
import { Store, createSelector } from '@ngrx/store'; import { Store, createSelector } from '@ngrx/store';
@ -145,6 +146,20 @@ export class ViewerEffects {
}) })
); );
pluginPreview$ = this.actions$.pipe(
ofType<PluginPreviewAction>(ViewerActionTypes.PluginPreview),
tap((action) => {
this.router.navigate([
action.pluginRoute,
{
outlets: {
viewer: ['preview', action.nodeId]
}
}
]);
})
);
private displayPreview(nodeId: string, parentId: string) { private displayPreview(nodeId: string, parentId: string) {
if (!nodeId) { if (!nodeId) {
return; return;