mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
[ACA-1508] extensions: wave 1 (#480)
* initial structure scaffold * core extensions module * simple navbar composition * allow using app routes instead of registered * migrate to new navbar setup * remove commented out tests * populate toolbar * evaluate expressions * redirect to url from toolbar * populate "open with" viewer menu * update test setup * experimental flag for extensions * test fixes * fix tests * code improvements, order support * improve routing management * populate "create" menu * extra dictionaries for spellcheck * allow disabling extension content * support file/folder targets for toolbar actions * add safety check * navigate directly * toolbar actions for all pages * support route data * "experimental" flag for "create" menu extensions * code fixes
This commit is contained in:
parent
3e123bee62
commit
e75042aa46
@ -40,6 +40,8 @@
|
|||||||
"cardview"
|
"cardview"
|
||||||
],
|
],
|
||||||
"dictionaries": [
|
"dictionaries": [
|
||||||
"html"
|
"html",
|
||||||
|
"en-gb",
|
||||||
|
"en_US"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
import { browser, protractor } from 'protractor';
|
import { browser, protractor } from 'protractor';
|
||||||
import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages';
|
import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages';
|
||||||
import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS } from '../../configs';
|
import { SITE_VISIBILITY, SITE_ROLES, SIDEBAR_LABELS, APP_ROUTES } from '../../configs';
|
||||||
import { RepoClient } from '../../utilities/repo-client/repo-client';
|
import { RepoClient } from '../../utilities/repo-client/repo-client';
|
||||||
import { Utils } from '../../utilities/utils';
|
import { Utils } from '../../utilities/utils';
|
||||||
|
|
||||||
@ -384,7 +384,8 @@ describe('Toolbar actions - multiple selection : ', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(done => {
|
beforeEach(done => {
|
||||||
page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES)
|
// page.sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES)
|
||||||
|
browser.get(APP_ROUTES.SHARED_FILES)
|
||||||
.then(() => dataTable.waitForHeader())
|
.then(() => dataTable.waitForHeader())
|
||||||
.then(done);
|
.then(done);
|
||||||
});
|
});
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
"libraries": false,
|
"libraries": false,
|
||||||
"comments": false,
|
"comments": false,
|
||||||
"cardview": false,
|
"cardview": false,
|
||||||
"share": false
|
"share": false,
|
||||||
|
"extensions": false
|
||||||
},
|
},
|
||||||
"headerColor": "#2196F3",
|
"headerColor": "#2196F3",
|
||||||
"languagePicker": false,
|
"languagePicker": false,
|
||||||
@ -31,65 +32,196 @@
|
|||||||
"preserveState": true,
|
"preserveState": true,
|
||||||
"expandedSidenav": true
|
"expandedSidenav": true
|
||||||
},
|
},
|
||||||
"navigation": {
|
"extensions": {
|
||||||
"main": [
|
"external": [
|
||||||
{
|
"plugin1.json",
|
||||||
"icon": "folder",
|
"plugin2.json"
|
||||||
"label": "APP.BROWSE.PERSONAL.SIDENAV_LINK.LABEL",
|
|
||||||
"title": "APP.BROWSE.PERSONAL.SIDENAV_LINK.TOOLTIP",
|
|
||||||
"disabled": false,
|
|
||||||
"route": {
|
|
||||||
"url": "/personal-files"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon": "group_work",
|
|
||||||
"label": "APP.BROWSE.LIBRARIES.SIDENAV_LINK.LABEL",
|
|
||||||
"title": "APP.BROWSE.LIBRARIES.SIDENAV_LINK.TOOLTIP",
|
|
||||||
"disabled": false,
|
|
||||||
"route": {
|
|
||||||
"url": "/libraries"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"secondary": [
|
"core": {
|
||||||
{
|
"routes": [
|
||||||
"icon": "people",
|
{
|
||||||
"label": "APP.BROWSE.SHARED.SIDENAV_LINK.LABEL",
|
"id": "aca:routes/about",
|
||||||
"title": "APP.BROWSE.SHARED.SIDENAV_LINK.TOOLTIP",
|
"path": "ext/about",
|
||||||
"disabled": false,
|
"component": "aca:components/about",
|
||||||
"route": {
|
"layout": "aca:layouts/main",
|
||||||
"url": "/shared"
|
"auth":[ "aca:auth" ],
|
||||||
|
"data": {
|
||||||
|
"title": "Custom About"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"actions": [
|
||||||
"icon": "schedule",
|
{
|
||||||
"label": "APP.BROWSE.RECENT.SIDENAV_LINK.LABEL",
|
"id": "aca:actions/info",
|
||||||
"title": "APP.BROWSE.RECENT.SIDENAV_LINK.TOOLTIP",
|
"type": "SNACKBAR_INFO",
|
||||||
"disabled": false,
|
"payload": "I'm a nice little popup raised by extension."
|
||||||
"route": {
|
},
|
||||||
"url": "/recent-files"
|
{
|
||||||
|
"id": "aca:actions/error",
|
||||||
|
"type": "SNACKBAR_ERROR",
|
||||||
|
"payload": "Aw, Snap!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aca:actions/node-name",
|
||||||
|
"type": "SNACKBAR_INFO",
|
||||||
|
"payload": "$('Action for ' + context.selection.first.entry.name)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aca:actions/settings",
|
||||||
|
"type": "NAVIGATE_URL",
|
||||||
|
"payload": "/settings"
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"features": {
|
||||||
"icon": "star",
|
"create": [
|
||||||
"label": "APP.BROWSE.FAVORITES.SIDENAV_LINK.LABEL",
|
{
|
||||||
"title": "APP.BROWSE.FAVORITES.SIDENAV_LINK.TOOLTIP",
|
"id": "aca:create/action1",
|
||||||
"disabled": false,
|
"order": 100,
|
||||||
"route": {
|
"icon": "build",
|
||||||
"url": "/favorites"
|
"title": "Error",
|
||||||
}
|
"action": "aca:actions/error"
|
||||||
},
|
}
|
||||||
{
|
],
|
||||||
"icon": "delete",
|
"navigation": {
|
||||||
"label": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.LABEL",
|
"aca:main": [
|
||||||
"title": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.TOOLTIP",
|
{
|
||||||
"disabled": false,
|
"id": "aca/personal-files",
|
||||||
"route": {
|
"order": 100,
|
||||||
"url": "/trashcan"
|
"icon": "folder",
|
||||||
|
"title": "APP.BROWSE.PERSONAL.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.PERSONAL.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "personal-files"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aca/libraries",
|
||||||
|
"order": 101,
|
||||||
|
"icon": "group_work",
|
||||||
|
"title": "APP.BROWSE.LIBRARIES.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.LIBRARIES.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "libraries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aca:secondary": [
|
||||||
|
{
|
||||||
|
"id": "aca/shared",
|
||||||
|
"order": 100,
|
||||||
|
"icon": "people",
|
||||||
|
"title": "APP.BROWSE.SHARED.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.SHARED.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "shared"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aca/recent-files",
|
||||||
|
"order": 101,
|
||||||
|
"icon": "schedule",
|
||||||
|
"title": "APP.BROWSE.RECENT.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.RECENT.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "recent-files"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aca/favorites",
|
||||||
|
"order": 102,
|
||||||
|
"icon": "star",
|
||||||
|
"title": "APP.BROWSE.FAVORITES.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.FAVORITES.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "favorites"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aca/trashcan",
|
||||||
|
"order": 103,
|
||||||
|
"icon": "delete",
|
||||||
|
"title": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.LABEL",
|
||||||
|
"description": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.TOOLTIP",
|
||||||
|
"route": "trashcan"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"aca:demo": [
|
||||||
|
{
|
||||||
|
"disabled": true,
|
||||||
|
"id": "aca:demo/link1",
|
||||||
|
"order": 100,
|
||||||
|
"icon": "build",
|
||||||
|
"title": "About (native)",
|
||||||
|
"description": "Uses native application route",
|
||||||
|
"route": "about"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled": true,
|
||||||
|
"id": "aca:demo/link2",
|
||||||
|
"order": 100,
|
||||||
|
"icon": "build",
|
||||||
|
"title": "About (custom)",
|
||||||
|
"description": "Uses custom defined route",
|
||||||
|
"route": "aca:routes/about"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"viewer": {
|
||||||
|
"open-with": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"id": "aca:viewer/action1",
|
||||||
|
"order": 100,
|
||||||
|
"icon": "build",
|
||||||
|
"title": "Snackbar",
|
||||||
|
"action": "aca:actions/info"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"id": "aca:action1",
|
||||||
|
"order": 100,
|
||||||
|
"title": "Info",
|
||||||
|
"icon": "build",
|
||||||
|
"target": {
|
||||||
|
"type": "folder",
|
||||||
|
"permissions": ["one", "two"],
|
||||||
|
"action": "aca:actions/info"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"id": "aca:action2",
|
||||||
|
"order": 101,
|
||||||
|
"title": "Node name",
|
||||||
|
"icon": "feedback",
|
||||||
|
"target": {
|
||||||
|
"type": "folder",
|
||||||
|
"permissions": ["one", "two"],
|
||||||
|
"action": "aca:actions/node-name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"id": "aca:action3",
|
||||||
|
"order": 101,
|
||||||
|
"title": "Settings",
|
||||||
|
"icon": "settings_applications",
|
||||||
|
"target": {
|
||||||
|
"type": "folder",
|
||||||
|
"permissions": ["one", "two"],
|
||||||
|
"action": "aca:actions/settings"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled": false,
|
||||||
|
"id": "aca:action4",
|
||||||
|
"order": 101,
|
||||||
|
"title": "Error",
|
||||||
|
"icon": "report_problem",
|
||||||
|
"target": {
|
||||||
|
"type": "file",
|
||||||
|
"permissions": ["one", "two"],
|
||||||
|
"action": "aca:actions/error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
},
|
||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
|
@ -37,6 +37,7 @@ import {
|
|||||||
SetLanguagePickerAction,
|
SetLanguagePickerAction,
|
||||||
SetSharedUrlAction
|
SetSharedUrlAction
|
||||||
} from './store/actions';
|
} from './store/actions';
|
||||||
|
import { ExtensionService } from './extensions/extension.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -51,7 +52,8 @@ export class AppComponent implements OnInit {
|
|||||||
private store: Store<AppStore>,
|
private store: Store<AppStore>,
|
||||||
private config: AppConfigService,
|
private config: AppConfigService,
|
||||||
private alfrescoApiService: AlfrescoApiService,
|
private alfrescoApiService: AlfrescoApiService,
|
||||||
private authenticationService: AuthenticationService) {
|
private authenticationService: AuthenticationService,
|
||||||
|
private extensions: ExtensionService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -83,6 +85,12 @@ export class AppComponent implements OnInit {
|
|||||||
|
|
||||||
pageTitle.setTitle(data.title || '');
|
pageTitle.setTitle(data.title || '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.extensions.init();
|
||||||
|
|
||||||
|
this.router.config.unshift(
|
||||||
|
...this.extensions.getApplicationRoutes()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadAppSettings() {
|
private loadAppSettings() {
|
||||||
|
@ -82,6 +82,9 @@ import { DocumentListDirective } from './directives/document-list.directive';
|
|||||||
import { MaterialModule } from './material.module';
|
import { MaterialModule } from './material.module';
|
||||||
import { ExperimentalDirective } from './directives/experimental.directive';
|
import { ExperimentalDirective } from './directives/experimental.directive';
|
||||||
import { ContentApiService } from './services/content-api.service';
|
import { ContentApiService } from './services/content-api.service';
|
||||||
|
import { ExtensionsModule } from './extensions.module';
|
||||||
|
import { ExtensionService } from './extensions/extension.service';
|
||||||
|
import { CoreExtensionsModule } from './extensions/core.extensions';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -96,7 +99,9 @@ import { ContentApiService } from './services/content-api.service';
|
|||||||
MaterialModule,
|
MaterialModule,
|
||||||
CoreModule,
|
CoreModule,
|
||||||
ContentModule,
|
ContentModule,
|
||||||
AppStoreModule
|
AppStoreModule,
|
||||||
|
CoreExtensionsModule,
|
||||||
|
ExtensionsModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
@ -155,7 +160,8 @@ import { ContentApiService } from './services/content-api.service';
|
|||||||
NodePermissionService,
|
NodePermissionService,
|
||||||
ProfileResolver,
|
ProfileResolver,
|
||||||
ExperimentalGuard,
|
ExperimentalGuard,
|
||||||
ContentApiService
|
ContentApiService,
|
||||||
|
ExtensionService
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
NodeVersionsDialogComponent
|
NodeVersionsDialogComponent
|
||||||
|
@ -10,7 +10,15 @@
|
|||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||||
|
<ng-container *ifExperimental="'extensions'">
|
||||||
|
<button *ngFor="let entry of actions"
|
||||||
|
mat-icon-button
|
||||||
|
color="primary"
|
||||||
|
title="{{ entry.title | translate }}"
|
||||||
|
(click)="runAction(entry.target.action)">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
@ -37,6 +37,7 @@ import { NodePermissionService } from '../../common/services/node-permission.ser
|
|||||||
import { AppStore } from '../../store/states/app.state';
|
import { AppStore } from '../../store/states/app.state';
|
||||||
import { PageComponent } from '../page.component';
|
import { PageComponent } from '../page.component';
|
||||||
import { ContentApiService } from '../../services/content-api.service';
|
import { ContentApiService } from '../../services/content-api.service';
|
||||||
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './favorites.component.html'
|
templateUrl: './favorites.component.html'
|
||||||
@ -45,11 +46,12 @@ export class FavoritesComponent extends PageComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
store: Store<AppStore>,
|
store: Store<AppStore>,
|
||||||
|
extensions: ExtensionService,
|
||||||
private contentApi: ContentApiService,
|
private contentApi: ContentApiService,
|
||||||
private content: ContentManagementService,
|
private content: ContentManagementService,
|
||||||
public permission: NodePermissionService
|
public permission: NodePermissionService
|
||||||
) {
|
) {
|
||||||
super(store);
|
super(store, extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -13,6 +13,15 @@
|
|||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||||
|
<ng-container *ifExperimental="'extensions'">
|
||||||
|
<button *ngFor="let entry of actions"
|
||||||
|
mat-icon-button
|
||||||
|
color="primary"
|
||||||
|
title="{{ entry.title | translate }}"
|
||||||
|
(click)="runAction(entry.target.action)">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
<button
|
<button
|
||||||
color="primary"
|
color="primary"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
|
@ -36,6 +36,7 @@ import { NodePermissionService } from '../../common/services/node-permission.ser
|
|||||||
import { AppStore } from '../../store/states/app.state';
|
import { AppStore } from '../../store/states/app.state';
|
||||||
import { PageComponent } from '../page.component';
|
import { PageComponent } from '../page.component';
|
||||||
import { ContentApiService } from '../../services/content-api.service';
|
import { ContentApiService } from '../../services/content-api.service';
|
||||||
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './files.component.html'
|
templateUrl: './files.component.html'
|
||||||
@ -54,8 +55,9 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
|
|||||||
private uploadService: UploadService,
|
private uploadService: UploadService,
|
||||||
private contentManagementService: ContentManagementService,
|
private contentManagementService: ContentManagementService,
|
||||||
private browsingFilesService: BrowsingFilesService,
|
private browsingFilesService: BrowsingFilesService,
|
||||||
public permission: NodePermissionService) {
|
public permission: NodePermissionService,
|
||||||
super(store);
|
extensions: ExtensionService) {
|
||||||
|
super(store, extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -34,9 +34,9 @@ import {
|
|||||||
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||||
import { ShareDataTableAdapter } from '@alfresco/adf-content-services';
|
import { ShareDataTableAdapter } from '@alfresco/adf-content-services';
|
||||||
import { LibrariesComponent } from './libraries.component';
|
import { LibrariesComponent } from './libraries.component';
|
||||||
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
|
||||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||||
import { ContentApiService } from '../../services/content-api.service';
|
import { ContentApiService } from '../../services/content-api.service';
|
||||||
|
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||||
|
|
||||||
describe('LibrariesComponent', () => {
|
describe('LibrariesComponent', () => {
|
||||||
let fixture: ComponentFixture<LibrariesComponent>;
|
let fixture: ComponentFixture<LibrariesComponent>;
|
||||||
|
@ -34,6 +34,7 @@ import { DeleteLibraryAction } from '../../store/actions';
|
|||||||
import { SiteEntry } from 'alfresco-js-api';
|
import { SiteEntry } from 'alfresco-js-api';
|
||||||
import { ContentManagementService } from '../../common/services/content-management.service';
|
import { ContentManagementService } from '../../common/services/content-management.service';
|
||||||
import { ContentApiService } from '../../services/content-api.service';
|
import { ContentApiService } from '../../services/content-api.service';
|
||||||
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './libraries.component.html'
|
templateUrl: './libraries.component.html'
|
||||||
@ -44,8 +45,9 @@ export class LibrariesComponent extends PageComponent implements OnInit {
|
|||||||
private content: ContentManagementService,
|
private content: ContentManagementService,
|
||||||
private contentApi: ContentApiService,
|
private contentApi: ContentApiService,
|
||||||
store: Store<AppStore>,
|
store: Store<AppStore>,
|
||||||
|
extensions: ExtensionService,
|
||||||
private router: Router) {
|
private router: Router) {
|
||||||
super(store);
|
super(store, extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -29,7 +29,7 @@ class TestClass extends PageComponent {
|
|||||||
node: any;
|
node: any;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(null);
|
super(null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ import { appSelection, sharedUrl } from '../store/selectors/app.selectors';
|
|||||||
import { AppStore } from '../store/states/app.state';
|
import { AppStore } from '../store/states/app.state';
|
||||||
import { SelectionState } from '../store/states/selection.state';
|
import { SelectionState } from '../store/states/selection.state';
|
||||||
import { Observable } from 'rxjs/Rx';
|
import { Observable } from 'rxjs/Rx';
|
||||||
|
import { ExtensionService } from '../extensions/extension.service';
|
||||||
|
import { ContentActionExtension } from '../extensions/content-action.extension';
|
||||||
|
|
||||||
export abstract class PageComponent implements OnInit, OnDestroy {
|
export abstract class PageComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@ -49,6 +51,7 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
selection: SelectionState;
|
selection: SelectionState;
|
||||||
displayMode = DisplayMode.List;
|
displayMode = DisplayMode.List;
|
||||||
sharedPreviewUrl$: Observable<string>;
|
sharedPreviewUrl$: Observable<string>;
|
||||||
|
actions: Array<ContentActionExtension> = [];
|
||||||
|
|
||||||
protected subscriptions: Subscription[] = [];
|
protected subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
@ -56,7 +59,9 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
return node.isLocked || (node.properties && node.properties['cm:lockType'] === 'READ_ONLY_LOCK');
|
return node.isLocked || (node.properties && node.properties['cm:lockType'] === 'READ_ONLY_LOCK');
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(protected store: Store<AppStore>) {}
|
constructor(
|
||||||
|
protected store: Store<AppStore>,
|
||||||
|
protected extensions: ExtensionService) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.sharedPreviewUrl$ = this.store.select(sharedUrl);
|
this.sharedPreviewUrl$ = this.store.select(sharedUrl);
|
||||||
@ -68,6 +73,21 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
this.selection = selection;
|
this.selection = selection;
|
||||||
if (selection.isEmpty) {
|
if (selection.isEmpty) {
|
||||||
this.infoDrawerOpened = false;
|
this.infoDrawerOpened = false;
|
||||||
|
this.actions = [];
|
||||||
|
} else {
|
||||||
|
this.actions = this.extensions.contentActions.filter(action => {
|
||||||
|
if (action.target && action.target.type) {
|
||||||
|
switch (action.target.type.toLowerCase()) {
|
||||||
|
case 'folder':
|
||||||
|
return selection.folder ? true : false;
|
||||||
|
case 'file':
|
||||||
|
return selection.file ? true : false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -144,4 +164,14 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
|||||||
this.displayMode = this.displayMode === DisplayMode.List ? DisplayMode.Gallery : DisplayMode.List;
|
this.displayMode = this.displayMode === DisplayMode.List ? DisplayMode.Gallery : DisplayMode.List;
|
||||||
this.documentList.display = this.displayMode;
|
this.documentList.display = this.displayMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is where each application decides how to treat an action and what to do
|
||||||
|
// the ACA maps actions to the NgRx actions as an example
|
||||||
|
runAction(actionId: string) {
|
||||||
|
const context = {
|
||||||
|
selection: this.selection
|
||||||
|
};
|
||||||
|
|
||||||
|
this.extensions.runActionById(actionId, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,15 @@
|
|||||||
(navigateBefore)="onNavigateBefore()"
|
(navigateBefore)="onNavigateBefore()"
|
||||||
(navigateNext)="onNavigateNext()">
|
(navigateNext)="onNavigateNext()">
|
||||||
|
|
||||||
|
<adf-viewer-open-with *ifExperimental="'extensions'">
|
||||||
|
<button *ngFor="let entry of openWith"
|
||||||
|
mat-menu-item
|
||||||
|
(click)="runAction(entry.action)">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
<span>{{ entry.title }}</span>
|
||||||
|
</button>
|
||||||
|
</adf-viewer-open-with>
|
||||||
|
|
||||||
<adf-viewer-more-actions>
|
<adf-viewer-more-actions>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -33,6 +33,8 @@ import { AppStore } from '../../store/states/app.state';
|
|||||||
import { DeleteNodesAction } from '../../store/actions';
|
import { DeleteNodesAction } from '../../store/actions';
|
||||||
import { PageComponent } from '../page.component';
|
import { PageComponent } from '../page.component';
|
||||||
import { ContentApiService } from '../../services/content-api.service';
|
import { ContentApiService } from '../../services/content-api.service';
|
||||||
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
|
import { OpenWithExtension } from '../../extensions/open-with.extension';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-preview',
|
selector: 'app-preview',
|
||||||
templateUrl: 'preview.component.html',
|
templateUrl: 'preview.component.html',
|
||||||
@ -55,6 +57,7 @@ export class PreviewComponent extends PageComponent implements OnInit {
|
|||||||
navigateMultiple = false;
|
navigateMultiple = false;
|
||||||
|
|
||||||
selectedEntities: MinimalNodeEntity[] = [];
|
selectedEntities: MinimalNodeEntity[] = [];
|
||||||
|
openWith: Array<OpenWithExtension> = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private contentApi: ContentApiService,
|
private contentApi: ContentApiService,
|
||||||
@ -63,9 +66,9 @@ export class PreviewComponent extends PageComponent implements OnInit {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
store: Store<AppStore>,
|
store: Store<AppStore>,
|
||||||
public permission: NodePermissionService) {
|
public permission: NodePermissionService,
|
||||||
|
extensions: ExtensionService) {
|
||||||
super(store);
|
super(store, extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -97,6 +100,8 @@ export class PreviewComponent extends PageComponent implements OnInit {
|
|||||||
this.subscriptions = this.subscriptions.concat([
|
this.subscriptions = this.subscriptions.concat([
|
||||||
this.uploadService.fileUploadError.subscribe((error) => this.onFileUploadedError(error))
|
this.uploadService.fileUploadError.subscribe((error) => this.onFileUploadedError(error))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
this.openWith = this.extensions.openWithActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -359,4 +364,10 @@ export class PreviewComponent extends PageComponent implements OnInit {
|
|||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is where each application decides how to treat an action and what to do
|
||||||
|
// the ACA maps actions to the NgRx actions as an example
|
||||||
|
runAction(actionId: string) {
|
||||||
|
this.extensions.runActionById(actionId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,15 @@
|
|||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||||
|
<ng-container *ifExperimental="'extensions'">
|
||||||
|
<button *ngFor="let entry of actions"
|
||||||
|
mat-icon-button
|
||||||
|
color="primary"
|
||||||
|
title="{{ entry.title | translate }}"
|
||||||
|
(click)="runAction(entry.target.action)">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
@ -32,6 +32,7 @@ import { PageComponent } from '../page.component';
|
|||||||
import { NodePermissionService } from '../../common/services/node-permission.service';
|
import { NodePermissionService } from '../../common/services/node-permission.service';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppStore } from '../../store/states/app.state';
|
import { AppStore } from '../../store/states/app.state';
|
||||||
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './recent-files.component.html'
|
templateUrl: './recent-files.component.html'
|
||||||
@ -40,10 +41,11 @@ export class RecentFilesComponent extends PageComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
store: Store<AppStore>,
|
store: Store<AppStore>,
|
||||||
|
extensions: ExtensionService,
|
||||||
private uploadService: UploadService,
|
private uploadService: UploadService,
|
||||||
private content: ContentManagementService,
|
private content: ContentManagementService,
|
||||||
public permission: NodePermissionService) {
|
public permission: NodePermissionService) {
|
||||||
super(store);
|
super(store, extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@ -3,6 +3,15 @@
|
|||||||
<adf-breadcrumb root="APP.BROWSE.SEARCH.TITLE">
|
<adf-breadcrumb root="APP.BROWSE.SEARCH.TITLE">
|
||||||
</adf-breadcrumb>
|
</adf-breadcrumb>
|
||||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||||
|
<ng-container *ifExperimental="'extensions'">
|
||||||
|
<button *ngFor="let entry of actions"
|
||||||
|
mat-icon-button
|
||||||
|
color="primary"
|
||||||
|
title="{{ entry.title | translate }}"
|
||||||
|
(click)="runAction(entry.target.action)">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
<button
|
<button
|
||||||
color="primary"
|
color="primary"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
|
@ -31,6 +31,7 @@ import { PageComponent } from '../page.component';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppStore } from '../../store/states/app.state';
|
import { AppStore } from '../../store/states/app.state';
|
||||||
import { NavigateToFolder } from '../../store/actions';
|
import { NavigateToFolder } from '../../store/actions';
|
||||||
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-search',
|
selector: 'app-search',
|
||||||
@ -53,9 +54,10 @@ export class SearchComponent extends PageComponent implements OnInit {
|
|||||||
public permission: NodePermissionService,
|
public permission: NodePermissionService,
|
||||||
private queryBuilder: SearchQueryBuilderService,
|
private queryBuilder: SearchQueryBuilderService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
store: Store<AppStore>
|
store: Store<AppStore>,
|
||||||
|
extensions: ExtensionService
|
||||||
) {
|
) {
|
||||||
super(store);
|
super(store, extensions);
|
||||||
|
|
||||||
queryBuilder.paging = {
|
queryBuilder.paging = {
|
||||||
skipCount: 0,
|
skipCount: 0,
|
||||||
|
@ -80,7 +80,6 @@
|
|||||||
Cardview
|
Cardview
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<mat-checkbox
|
<mat-checkbox
|
||||||
[(ngModel)]="share"
|
[(ngModel)]="share"
|
||||||
@ -88,5 +87,12 @@
|
|||||||
Share
|
Share
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-checkbox
|
||||||
|
[(ngModel)]="extensions"
|
||||||
|
(change)="onChangeExtensionsFeature($event)">
|
||||||
|
Extensions
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
|
@ -52,6 +52,7 @@ export class SettingsComponent implements OnInit {
|
|||||||
comments: boolean;
|
comments: boolean;
|
||||||
cardview: boolean;
|
cardview: boolean;
|
||||||
share: boolean;
|
share: boolean;
|
||||||
|
extensions: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<AppStore>,
|
private store: Store<AppStore>,
|
||||||
@ -86,6 +87,9 @@ export class SettingsComponent implements OnInit {
|
|||||||
|
|
||||||
const share = this.appConfig.get('experimental.share');
|
const share = this.appConfig.get('experimental.share');
|
||||||
this.share = (share === true || share === 'true');
|
this.share = (share === true || share === 'true');
|
||||||
|
|
||||||
|
const extensions = this.appConfig.get('experimental.extensions');
|
||||||
|
this.extensions = (extensions === true || extensions === 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(model: any, isValid: boolean) {
|
apply(model: any, isValid: boolean) {
|
||||||
@ -121,4 +125,8 @@ export class SettingsComponent implements OnInit {
|
|||||||
onChangeShareFeature(event: MatCheckboxChange) {
|
onChangeShareFeature(event: MatCheckboxChange) {
|
||||||
this.storage.setItem('experimental.share', event.checked.toString());
|
this.storage.setItem('experimental.share', event.checked.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onChangeExtensionsFeature(event: MatCheckboxChange) {
|
||||||
|
this.storage.setItem('experimental.extensions', event.checked.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,15 @@
|
|||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||||
|
<ng-container *ifExperimental="'extensions'">
|
||||||
|
<button *ngFor="let entry of actions"
|
||||||
|
mat-icon-button
|
||||||
|
color="primary"
|
||||||
|
title="{{ entry.title | translate }}"
|
||||||
|
(click)="runAction(entry.target.action)">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
<button
|
<button
|
||||||
*ngIf="selection.count === 1"
|
*ngIf="selection.count === 1"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
@ -31,17 +31,20 @@ import { NodePermissionService } from '../../common/services/node-permission.ser
|
|||||||
import { PageComponent } from '../page.component';
|
import { PageComponent } from '../page.component';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppStore } from '../../store/states/app.state';
|
import { AppStore } from '../../store/states/app.state';
|
||||||
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './shared-files.component.html'
|
templateUrl: './shared-files.component.html'
|
||||||
})
|
})
|
||||||
export class SharedFilesComponent extends PageComponent implements OnInit {
|
export class SharedFilesComponent extends PageComponent implements OnInit {
|
||||||
|
constructor(
|
||||||
constructor(store: Store<AppStore>,
|
store: Store<AppStore>,
|
||||||
private uploadService: UploadService,
|
extensions: ExtensionService,
|
||||||
private content: ContentManagementService,
|
private uploadService: UploadService,
|
||||||
public permission: NodePermissionService) {
|
private content: ContentManagementService,
|
||||||
super(store);
|
public permission: NodePermissionService
|
||||||
|
) {
|
||||||
|
super(store, extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -52,7 +55,9 @@ export class SharedFilesComponent extends PageComponent implements OnInit {
|
|||||||
this.content.nodesMoved.subscribe(() => this.reload()),
|
this.content.nodesMoved.subscribe(() => this.reload()),
|
||||||
this.content.nodesRestored.subscribe(() => this.reload()),
|
this.content.nodesRestored.subscribe(() => this.reload()),
|
||||||
this.content.linksUnshared.subscribe(() => this.reload()),
|
this.content.linksUnshared.subscribe(() => this.reload()),
|
||||||
this.uploadService.fileUploadError.subscribe((error) => this.onFileUploadedError(error))
|
this.uploadService.fileUploadError.subscribe(error =>
|
||||||
|
this.onFileUploadedError(error)
|
||||||
|
)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,14 @@
|
|||||||
<mat-icon [title]="'APP.NEW_MENU.TOOLTIP' | translate">queue</mat-icon>
|
<mat-icon [title]="'APP.NEW_MENU.TOOLTIP' | translate">queue</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
<div sidebar-menu-options>
|
<div sidebar-menu-options>
|
||||||
|
<ng-container *ifExperimental="'extensions'">
|
||||||
|
<button *ngFor="let entry of createActions"
|
||||||
|
mat-menu-item
|
||||||
|
(click)="runAction(entry.action)">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
<span>{{ entry.title | translate }}</span>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
<button
|
<button
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[disabled]="!permission.check(node, ['create'])"
|
[disabled]="!permission.check(node, ['create'])"
|
||||||
@ -48,16 +56,17 @@
|
|||||||
</adf-sidebar-action-menu>
|
</adf-sidebar-action-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidenav__section sidenav__section--menu" *ngFor="let list of navigation">
|
<div class="sidenav__section sidenav__section--menu" *ngFor="let group of groups">
|
||||||
<ul class="sidenav-menu">
|
<ul class="sidenav-menu">
|
||||||
<li *ngFor="let item of list" class="sidenav-menu__item"
|
<li *ngFor="let item of group" class="sidenav-menu__item"
|
||||||
routerLinkActive
|
routerLinkActive
|
||||||
#rla="routerLinkActive"
|
#rla="routerLinkActive"
|
||||||
title="{{ item.title || '' | translate }}">
|
title="{{ item.description | translate }}">
|
||||||
|
|
||||||
<button [routerLink]="item.route.url"
|
<button
|
||||||
|
[routerLink]="item.route"
|
||||||
[color]="rla.isActive ? 'accent': 'primary'"
|
[color]="rla.isActive ? 'accent': 'primary'"
|
||||||
[attr.aria-label]="item.label | translate"
|
[attr.aria-label]="item.title | translate"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
mat-ripple
|
mat-ripple
|
||||||
matRippleColor="primary"
|
matRippleColor="primary"
|
||||||
@ -68,13 +77,13 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span #rippleTrigger
|
<span #rippleTrigger
|
||||||
|
[routerLink]="item.route"
|
||||||
class="menu__item--label"
|
class="menu__item--label"
|
||||||
[routerLink]="item.route.url"
|
|
||||||
[hidden]="!showLabel"
|
[hidden]="!showLabel"
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'menu__item--active': rla.isActive,
|
'menu__item--active': rla.isActive,
|
||||||
'menu__item--default': !rla.isActive
|
'menu__item--default': !rla.isActive
|
||||||
}">{{ item.label | translate }}</span>
|
}">{{ item.title | translate }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,26 +25,17 @@
|
|||||||
|
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
|
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
|
||||||
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
|
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
|
||||||
import { SidenavComponent } from './sidenav.component';
|
import { SidenavComponent } from './sidenav.component';
|
||||||
import { EffectsModule } from '@ngrx/effects';
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
import { NodeEffects } from '../../store/effects/node.effects';
|
import { NodeEffects } from '../../store/effects/node.effects';
|
||||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||||
|
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||||
|
|
||||||
describe('SidenavComponent', () => {
|
describe('SidenavComponent', () => {
|
||||||
let fixture: ComponentFixture<SidenavComponent>;
|
let fixture: ComponentFixture<SidenavComponent>;
|
||||||
let component: SidenavComponent;
|
let component: SidenavComponent;
|
||||||
let browsingService: BrowsingFilesService;
|
let browsingService: BrowsingFilesService;
|
||||||
let appConfig: AppConfigService;
|
|
||||||
let appConfigSpy;
|
|
||||||
|
|
||||||
const navItem = {
|
|
||||||
label: 'some-label',
|
|
||||||
route: {
|
|
||||||
url: '/some-url'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -53,19 +44,17 @@ describe('SidenavComponent', () => {
|
|||||||
EffectsModule.forRoot([NodeEffects])
|
EffectsModule.forRoot([NodeEffects])
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
SidenavComponent
|
SidenavComponent,
|
||||||
|
ExperimentalDirective
|
||||||
],
|
],
|
||||||
schemas: [ NO_ERRORS_SCHEMA ]
|
schemas: [ NO_ERRORS_SCHEMA ]
|
||||||
})
|
})
|
||||||
.compileComponents()
|
.compileComponents()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
browsingService = TestBed.get(BrowsingFilesService);
|
browsingService = TestBed.get(BrowsingFilesService);
|
||||||
appConfig = TestBed.get(AppConfigService);
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(SidenavComponent);
|
fixture = TestBed.createComponent(SidenavComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|
||||||
appConfigSpy = spyOn(appConfig, 'get').and.returnValue([navItem]);
|
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -77,20 +66,4 @@ describe('SidenavComponent', () => {
|
|||||||
|
|
||||||
expect(component.node).toBe(node);
|
expect(component.node).toBe(node);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('menu', () => {
|
|
||||||
it('should build menu from array', () => {
|
|
||||||
appConfigSpy.and.returnValue([navItem, navItem]);
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.navigation).toEqual([[navItem, navItem]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should build menu from object', () => {
|
|
||||||
appConfigSpy.and.returnValue({ a: [navItem, navItem], b: [navItem, navItem] });
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.navigation).toEqual([[navItem, navItem], [navItem, navItem]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -26,11 +26,11 @@
|
|||||||
import { Subscription } from 'rxjs/Rx';
|
import { Subscription } from 'rxjs/Rx';
|
||||||
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
|
||||||
|
|
||||||
|
|
||||||
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
|
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
|
||||||
import { NodePermissionService } from '../../common/services/node-permission.service';
|
import { NodePermissionService } from '../../common/services/node-permission.service';
|
||||||
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
|
import { NavigationExtension } from '../../extensions/navigation.extension';
|
||||||
|
import { CreateExtension } from '../../extensions/create.extension';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sidenav',
|
selector: 'app-sidenav',
|
||||||
@ -41,22 +41,25 @@ export class SidenavComponent implements OnInit, OnDestroy {
|
|||||||
@Input() showLabel: boolean;
|
@Input() showLabel: boolean;
|
||||||
|
|
||||||
node: MinimalNodeEntryEntity = null;
|
node: MinimalNodeEntryEntity = null;
|
||||||
navigation = [];
|
groups: Array<NavigationExtension[]> = [];
|
||||||
|
createActions: Array<CreateExtension> = [];
|
||||||
|
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private browsingFilesService: BrowsingFilesService,
|
private browsingFilesService: BrowsingFilesService,
|
||||||
private appConfig: AppConfigService,
|
public permission: NodePermissionService,
|
||||||
public permission: NodePermissionService
|
private extensions: ExtensionService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.navigation = this.buildMenu();
|
this.groups = this.extensions.getNavigationGroups();
|
||||||
|
this.createActions = this.extensions.createActions;
|
||||||
|
|
||||||
this.subscriptions.concat([
|
this.subscriptions.concat([
|
||||||
this.browsingFilesService.onChangeParent
|
this.browsingFilesService.onChangeParent.subscribe(
|
||||||
.subscribe((node: MinimalNodeEntryEntity) => this.node = node)
|
(node: MinimalNodeEntryEntity) => (this.node = node)
|
||||||
|
)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +67,9 @@ export class SidenavComponent implements OnInit, OnDestroy {
|
|||||||
this.subscriptions.forEach(s => s.unsubscribe());
|
this.subscriptions.forEach(s => s.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildMenu() {
|
// this is where each application decides how to treat an action and what to do
|
||||||
const schema = this.appConfig.get('navigation');
|
// the ACA maps actions to the NgRx actions as an example
|
||||||
const data = Array.isArray(schema) ? { main: schema } : schema;
|
runAction(actionId: string) {
|
||||||
|
this.extensions.runActionById(actionId);
|
||||||
return Object.keys(data).map((key) => data[key]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,15 @@
|
|||||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
<adf-toolbar class="inline" *ngIf="!selection.isEmpty">
|
||||||
|
<ng-container *ifExperimental="'extensions'">
|
||||||
|
<button *ngFor="let entry of actions"
|
||||||
|
mat-icon-button
|
||||||
|
color="primary"
|
||||||
|
title="{{ entry.title | translate }}"
|
||||||
|
(click)="runAction(entry.target.action)">
|
||||||
|
<mat-icon>{{ entry.icon }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
<button
|
<button
|
||||||
color="primary"
|
color="primary"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
|
@ -30,6 +30,7 @@ import { Store } from '@ngrx/store';
|
|||||||
import { selectUser } from '../../store/selectors/app.selectors';
|
import { selectUser } from '../../store/selectors/app.selectors';
|
||||||
import { AppStore } from '../../store/states/app.state';
|
import { AppStore } from '../../store/states/app.state';
|
||||||
import { ProfileState } from '../../store/states/profile.state';
|
import { ProfileState } from '../../store/states/profile.state';
|
||||||
|
import { ExtensionService } from '../../extensions/extension.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './trashcan.component.html'
|
templateUrl: './trashcan.component.html'
|
||||||
@ -38,8 +39,9 @@ export class TrashcanComponent extends PageComponent implements OnInit {
|
|||||||
user: ProfileState;
|
user: ProfileState;
|
||||||
|
|
||||||
constructor(private contentManagementService: ContentManagementService,
|
constructor(private contentManagementService: ContentManagementService,
|
||||||
|
extensions: ExtensionService,
|
||||||
store: Store<AppStore>) {
|
store: Store<AppStore>) {
|
||||||
super(store);
|
super(store, extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
8
src/app/extensions.module.ts
Normal file
8
src/app/extensions.module.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
// Main entry point for external extensions only.
|
||||||
|
// For any application-specific code use CoreExtensionsModule instead.
|
||||||
|
@NgModule({
|
||||||
|
imports: []
|
||||||
|
})
|
||||||
|
export class ExtensionsModule {}
|
30
src/app/extensions/action.extension.ts
Normal file
30
src/app/extensions/action.extension.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ActionExtension {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
payload?: string;
|
||||||
|
}
|
37
src/app/extensions/content-action.extension.ts
Normal file
37
src/app/extensions/content-action.extension.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ContentActionExtension {
|
||||||
|
id: string;
|
||||||
|
order?: number;
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
target: {
|
||||||
|
type: string;
|
||||||
|
permissions: Array<string>,
|
||||||
|
action: string;
|
||||||
|
};
|
||||||
|
}
|
43
src/app/extensions/core.extensions.ts
Normal file
43
src/app/extensions/core.extensions.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { AuthGuardEcm } from '@alfresco/adf-core';
|
||||||
|
import { ExtensionService } from './extension.service';
|
||||||
|
import { AboutComponent } from '../components/about/about.component';
|
||||||
|
import { LayoutComponent } from '../components/layout/layout.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [],
|
||||||
|
entryComponents: [AboutComponent]
|
||||||
|
})
|
||||||
|
export class CoreExtensionsModule {
|
||||||
|
constructor(extensions: ExtensionService) {
|
||||||
|
extensions.components['aca:layouts/main'] = LayoutComponent;
|
||||||
|
extensions.components['aca:components/about'] = AboutComponent;
|
||||||
|
extensions.authGuards['aca:auth'] = AuthGuardEcm;
|
||||||
|
}
|
||||||
|
}
|
33
src/app/extensions/create.extension.ts
Normal file
33
src/app/extensions/create.extension.ts
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface CreateExtension {
|
||||||
|
id: string;
|
||||||
|
order?: number;
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
action: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
186
src/app/extensions/extension.service.ts
Normal file
186
src/app/extensions/extension.service.ts
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable, Type } from '@angular/core';
|
||||||
|
import { RouteExtension } from './route.extension';
|
||||||
|
import { ActionExtension } from './action.extension';
|
||||||
|
import { AppConfigService } from '@alfresco/adf-core';
|
||||||
|
import { ContentActionExtension } from './content-action.extension';
|
||||||
|
import { OpenWithExtension } from './open-with.extension';
|
||||||
|
import { AppStore } from '../store/states';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { NavigationExtension } from './navigation.extension';
|
||||||
|
import { Route } from '@angular/router';
|
||||||
|
import { CreateExtension } from './create.extension';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ExtensionService {
|
||||||
|
routes: Array<RouteExtension> = [];
|
||||||
|
actions: Array<ActionExtension> = [];
|
||||||
|
|
||||||
|
contentActions: Array<ContentActionExtension> = [];
|
||||||
|
openWithActions: Array<OpenWithExtension> = [];
|
||||||
|
createActions: Array<CreateExtension> = [];
|
||||||
|
|
||||||
|
authGuards: { [key: string]: Type<{}> } = {};
|
||||||
|
components: { [key: string]: Type<{}> } = {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private config: AppConfigService,
|
||||||
|
private store: Store<AppStore>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// initialise extension service
|
||||||
|
// in future will also load and merge data from the external plugins
|
||||||
|
init() {
|
||||||
|
this.routes = this.config.get<Array<RouteExtension>>(
|
||||||
|
'extensions.core.routes',
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
this.actions = this.config.get<Array<ActionExtension>>(
|
||||||
|
'extensions.core.actions',
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
this.contentActions = this.config
|
||||||
|
.get<Array<ContentActionExtension>>(
|
||||||
|
'extensions.core.features.content.actions',
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
.filter(entry => !entry.disabled)
|
||||||
|
.sort(this.sortByOrder);
|
||||||
|
|
||||||
|
this.openWithActions = this.config
|
||||||
|
.get<Array<OpenWithExtension>>(
|
||||||
|
'extensions.core.features.viewer.open-with',
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
.filter(entry => !entry.disabled)
|
||||||
|
.sort(this.sortByOrder);
|
||||||
|
|
||||||
|
this.createActions = this.config
|
||||||
|
.get<Array<CreateExtension>>('extensions.core.features.create', [])
|
||||||
|
.filter(entry => !entry.disabled)
|
||||||
|
.sort(this.sortByOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRouteById(id: string): RouteExtension {
|
||||||
|
return this.routes.find(route => route.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActionById(id: string): ActionExtension {
|
||||||
|
return this.actions.find(action => action.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
runActionById(id: string, context?: any) {
|
||||||
|
const action = this.getActionById(id);
|
||||||
|
if (action) {
|
||||||
|
const { type, payload } = action;
|
||||||
|
const expression = this.runExpression(payload, context);
|
||||||
|
|
||||||
|
this.store.dispatch({ type, payload: expression });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getNavigationGroups(): Array<NavigationExtension[]> {
|
||||||
|
const settings = this.config.get<any>(
|
||||||
|
'extensions.core.features.navigation'
|
||||||
|
);
|
||||||
|
if (settings) {
|
||||||
|
const groups = Object.keys(settings).map(key => {
|
||||||
|
return settings[key]
|
||||||
|
.map(group => {
|
||||||
|
const customRoute = this.getRouteById(group.route);
|
||||||
|
const route = `/${
|
||||||
|
customRoute ? customRoute.path : group.route
|
||||||
|
}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
route
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(entry => !entry.disabled);
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getAuthGuards(ids: string[] = []): Array<Type<{}>> {
|
||||||
|
return ids.map(id => this.authGuards[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getComponentById(id: string): Type<{}> {
|
||||||
|
return this.components[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
runExpression(value: string, context?: any) {
|
||||||
|
const pattern = new RegExp(/\$\((.*\)?)\)/g);
|
||||||
|
const matches = pattern.exec(value);
|
||||||
|
|
||||||
|
if (matches && matches.length > 1) {
|
||||||
|
const expression = matches[1];
|
||||||
|
const fn = new Function('context', `return ${expression}`);
|
||||||
|
const result = fn(context);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getApplicationRoutes(): Array<Route> {
|
||||||
|
return this.routes.map(route => {
|
||||||
|
const guards = this.getAuthGuards(route.auth);
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: route.path,
|
||||||
|
component: this.getComponentById(route.layout),
|
||||||
|
canActivateChild: guards,
|
||||||
|
canActivate: guards,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: this.getComponentById(route.component),
|
||||||
|
data: route.data
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private sortByOrder(
|
||||||
|
a: { order?: number | undefined },
|
||||||
|
b: { order?: number | undefined }
|
||||||
|
) {
|
||||||
|
const left = a.order === undefined ? Number.MAX_SAFE_INTEGER : a.order;
|
||||||
|
const right = b.order === undefined ? Number.MAX_SAFE_INTEGER : b.order;
|
||||||
|
return left - right;
|
||||||
|
}
|
||||||
|
}
|
34
src/app/extensions/navigation.extension.ts
Normal file
34
src/app/extensions/navigation.extension.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*!
|
||||||
|
* @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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface NavigationExtension {
|
||||||
|
id: string;
|
||||||
|
order: number;
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
route: string;
|
||||||
|
description?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
33
src/app/extensions/open-with.extension.ts
Normal file
33
src/app/extensions/open-with.extension.ts
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface OpenWithExtension {
|
||||||
|
id: string;
|
||||||
|
order?: number;
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
action: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
33
src/app/extensions/route.extension.ts
Normal file
33
src/app/extensions/route.extension.ts
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface RouteExtension {
|
||||||
|
id: string;
|
||||||
|
path: string;
|
||||||
|
component: string;
|
||||||
|
layout: string;
|
||||||
|
auth: string[];
|
||||||
|
data?: { [key: string]: string };
|
||||||
|
}
|
@ -26,10 +26,16 @@
|
|||||||
import { Action } from '@ngrx/store';
|
import { Action } from '@ngrx/store';
|
||||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||||
|
|
||||||
|
export const NAVIGATE_URL = 'NAVIGATE_URL';
|
||||||
export const NAVIGATE_ROUTE = 'NAVIGATE_ROUTE';
|
export const NAVIGATE_ROUTE = 'NAVIGATE_ROUTE';
|
||||||
export const NAVIGATE_FOLDER = 'NAVIGATE_FOLDER';
|
export const NAVIGATE_FOLDER = 'NAVIGATE_FOLDER';
|
||||||
export const NAVIGATE_PARENT_FOLDER = 'NAVIGATE_PARENT_FOLDER';
|
export const NAVIGATE_PARENT_FOLDER = 'NAVIGATE_PARENT_FOLDER';
|
||||||
|
|
||||||
|
export class NavigateUrlAction implements Action {
|
||||||
|
readonly type = NAVIGATE_URL;
|
||||||
|
constructor(public payload: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
export class NavigateRouteAction implements Action {
|
export class NavigateRouteAction implements Action {
|
||||||
readonly type = NAVIGATE_ROUTE;
|
readonly type = NAVIGATE_ROUTE;
|
||||||
constructor(public payload: any[]) {}
|
constructor(public payload: any[]) {}
|
||||||
@ -40,7 +46,6 @@ export class NavigateToFolder implements Action {
|
|||||||
constructor(public payload: MinimalNodeEntity) {}
|
constructor(public payload: MinimalNodeEntity) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class NavigateToParentFolder implements Action {
|
export class NavigateToParentFolder implements Action {
|
||||||
readonly type = NAVIGATE_PARENT_FOLDER;
|
readonly type = NAVIGATE_PARENT_FOLDER;
|
||||||
constructor(public payload: MinimalNodeEntity) {}
|
constructor(public payload: MinimalNodeEntity) {}
|
||||||
|
@ -34,12 +34,22 @@ import {
|
|||||||
NAVIGATE_PARENT_FOLDER,
|
NAVIGATE_PARENT_FOLDER,
|
||||||
NAVIGATE_ROUTE
|
NAVIGATE_ROUTE
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
import { NavigateToFolder, NAVIGATE_FOLDER } from '../actions/router.actions';
|
import { NavigateToFolder, NAVIGATE_FOLDER, NavigateUrlAction, NAVIGATE_URL } from '../actions/router.actions';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RouterEffects {
|
export class RouterEffects {
|
||||||
constructor(private actions$: Actions, private router: Router) {}
|
constructor(private actions$: Actions, private router: Router) {}
|
||||||
|
|
||||||
|
@Effect({ dispatch: false })
|
||||||
|
navigateUrl$ = this.actions$.pipe(
|
||||||
|
ofType<NavigateUrlAction>(NAVIGATE_URL),
|
||||||
|
map(action => {
|
||||||
|
if (action.payload) {
|
||||||
|
this.router.navigateByUrl(action.payload);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
@Effect({ dispatch: false })
|
@Effect({ dispatch: false })
|
||||||
navigateRoute$ = this.actions$.pipe(
|
navigateRoute$ = this.actions$.pipe(
|
||||||
ofType<NavigateRouteAction>(NAVIGATE_ROUTE),
|
ofType<NavigateRouteAction>(NAVIGATE_ROUTE),
|
||||||
|
@ -82,7 +82,7 @@ export class SnackbarEffects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const snackBarRef = this.snackBar.open(message, actionName, {
|
const snackBarRef = this.snackBar.open(message, actionName, {
|
||||||
duration: action.duration,
|
duration: action.duration || 4000,
|
||||||
panelClass: panelClass
|
panelClass: panelClass
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ import { NodeActionsService } from '../common/services/node-actions.service';
|
|||||||
import { NodePermissionService } from '../common/services/node-permission.service';
|
import { NodePermissionService } from '../common/services/node-permission.service';
|
||||||
import { BrowsingFilesService } from '../common/services/browsing-files.service';
|
import { BrowsingFilesService } from '../common/services/browsing-files.service';
|
||||||
import { ContentApiService } from '../services/content-api.service';
|
import { ContentApiService } from '../services/content-api.service';
|
||||||
|
import { ExtensionService } from '../extensions/extension.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -76,7 +77,11 @@ import { ContentApiService } from '../services/content-api.service';
|
|||||||
EffectsModule.forRoot([])
|
EffectsModule.forRoot([])
|
||||||
],
|
],
|
||||||
declarations: [TranslatePipeMock],
|
declarations: [TranslatePipeMock],
|
||||||
exports: [TranslatePipeMock, RouterTestingModule, MaterialModule],
|
exports: [
|
||||||
|
TranslatePipeMock,
|
||||||
|
RouterTestingModule,
|
||||||
|
MaterialModule,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: AlfrescoApiService, useClass: AlfrescoApiMock },
|
{ provide: AlfrescoApiService, useClass: AlfrescoApiMock },
|
||||||
{ provide: TranslationService, useClass: TranslationMock },
|
{ provide: TranslationService, useClass: TranslationMock },
|
||||||
@ -112,7 +117,8 @@ import { ContentApiService } from '../services/content-api.service';
|
|||||||
NodeActionsService,
|
NodeActionsService,
|
||||||
NodePermissionService,
|
NodePermissionService,
|
||||||
BrowsingFilesService,
|
BrowsingFilesService,
|
||||||
ContentApiService
|
ContentApiService,
|
||||||
|
ExtensionService
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppTestingModule {}
|
export class AppTestingModule {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user