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"
|
||||
],
|
||||
"dictionaries": [
|
||||
"html"
|
||||
"html",
|
||||
"en-gb",
|
||||
"en_US"
|
||||
]
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
import { browser, protractor } from 'protractor';
|
||||
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 { Utils } from '../../utilities/utils';
|
||||
|
||||
@ -384,7 +384,8 @@ describe('Toolbar actions - multiple selection : ', () => {
|
||||
});
|
||||
|
||||
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(done);
|
||||
});
|
||||
|
@ -12,7 +12,8 @@
|
||||
"libraries": false,
|
||||
"comments": false,
|
||||
"cardview": false,
|
||||
"share": false
|
||||
"share": false,
|
||||
"extensions": false
|
||||
},
|
||||
"headerColor": "#2196F3",
|
||||
"languagePicker": false,
|
||||
@ -31,65 +32,196 @@
|
||||
"preserveState": true,
|
||||
"expandedSidenav": true
|
||||
},
|
||||
"navigation": {
|
||||
"main": [
|
||||
{
|
||||
"icon": "folder",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"extensions": {
|
||||
"external": [
|
||||
"plugin1.json",
|
||||
"plugin2.json"
|
||||
],
|
||||
"secondary": [
|
||||
{
|
||||
"icon": "people",
|
||||
"label": "APP.BROWSE.SHARED.SIDENAV_LINK.LABEL",
|
||||
"title": "APP.BROWSE.SHARED.SIDENAV_LINK.TOOLTIP",
|
||||
"disabled": false,
|
||||
"route": {
|
||||
"url": "/shared"
|
||||
"core": {
|
||||
"routes": [
|
||||
{
|
||||
"id": "aca:routes/about",
|
||||
"path": "ext/about",
|
||||
"component": "aca:components/about",
|
||||
"layout": "aca:layouts/main",
|
||||
"auth":[ "aca:auth" ],
|
||||
"data": {
|
||||
"title": "Custom About"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"icon": "schedule",
|
||||
"label": "APP.BROWSE.RECENT.SIDENAV_LINK.LABEL",
|
||||
"title": "APP.BROWSE.RECENT.SIDENAV_LINK.TOOLTIP",
|
||||
"disabled": false,
|
||||
"route": {
|
||||
"url": "/recent-files"
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "aca:actions/info",
|
||||
"type": "SNACKBAR_INFO",
|
||||
"payload": "I'm a nice little popup raised by extension."
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
"icon": "star",
|
||||
"label": "APP.BROWSE.FAVORITES.SIDENAV_LINK.LABEL",
|
||||
"title": "APP.BROWSE.FAVORITES.SIDENAV_LINK.TOOLTIP",
|
||||
"disabled": false,
|
||||
"route": {
|
||||
"url": "/favorites"
|
||||
}
|
||||
},
|
||||
{
|
||||
"icon": "delete",
|
||||
"label": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.LABEL",
|
||||
"title": "APP.BROWSE.TRASHCAN.SIDENAV_LINK.TOOLTIP",
|
||||
"disabled": false,
|
||||
"route": {
|
||||
"url": "/trashcan"
|
||||
],
|
||||
"features": {
|
||||
"create": [
|
||||
{
|
||||
"id": "aca:create/action1",
|
||||
"order": 100,
|
||||
"icon": "build",
|
||||
"title": "Error",
|
||||
"action": "aca:actions/error"
|
||||
}
|
||||
],
|
||||
"navigation": {
|
||||
"aca:main": [
|
||||
{
|
||||
"id": "aca/personal-files",
|
||||
"order": 100,
|
||||
"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": [
|
||||
{
|
||||
|
@ -37,6 +37,7 @@ import {
|
||||
SetLanguagePickerAction,
|
||||
SetSharedUrlAction
|
||||
} from './store/actions';
|
||||
import { ExtensionService } from './extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -51,7 +52,8 @@ export class AppComponent implements OnInit {
|
||||
private store: Store<AppStore>,
|
||||
private config: AppConfigService,
|
||||
private alfrescoApiService: AlfrescoApiService,
|
||||
private authenticationService: AuthenticationService) {
|
||||
private authenticationService: AuthenticationService,
|
||||
private extensions: ExtensionService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@ -83,6 +85,12 @@ export class AppComponent implements OnInit {
|
||||
|
||||
pageTitle.setTitle(data.title || '');
|
||||
});
|
||||
|
||||
this.extensions.init();
|
||||
|
||||
this.router.config.unshift(
|
||||
...this.extensions.getApplicationRoutes()
|
||||
);
|
||||
}
|
||||
|
||||
private loadAppSettings() {
|
||||
|
@ -82,6 +82,9 @@ import { DocumentListDirective } from './directives/document-list.directive';
|
||||
import { MaterialModule } from './material.module';
|
||||
import { ExperimentalDirective } from './directives/experimental.directive';
|
||||
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({
|
||||
imports: [
|
||||
@ -96,7 +99,9 @@ import { ContentApiService } from './services/content-api.service';
|
||||
MaterialModule,
|
||||
CoreModule,
|
||||
ContentModule,
|
||||
AppStoreModule
|
||||
AppStoreModule,
|
||||
CoreExtensionsModule,
|
||||
ExtensionsModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
@ -155,7 +160,8 @@ import { ContentApiService } from './services/content-api.service';
|
||||
NodePermissionService,
|
||||
ProfileResolver,
|
||||
ExperimentalGuard,
|
||||
ContentApiService
|
||||
ContentApiService,
|
||||
ExtensionService
|
||||
],
|
||||
entryComponents: [
|
||||
NodeVersionsDialogComponent
|
||||
|
@ -10,7 +10,15 @@
|
||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||
</button>
|
||||
<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
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
|
@ -37,6 +37,7 @@ import { NodePermissionService } from '../../common/services/node-permission.ser
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
import { PageComponent } from '../page.component';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { ExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './favorites.component.html'
|
||||
@ -45,11 +46,12 @@ export class FavoritesComponent extends PageComponent implements OnInit {
|
||||
constructor(
|
||||
private router: Router,
|
||||
store: Store<AppStore>,
|
||||
extensions: ExtensionService,
|
||||
private contentApi: ContentApiService,
|
||||
private content: ContentManagementService,
|
||||
public permission: NodePermissionService
|
||||
) {
|
||||
super(store);
|
||||
super(store, extensions);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -13,6 +13,15 @@
|
||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||
</button>
|
||||
<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
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
|
@ -36,6 +36,7 @@ import { NodePermissionService } from '../../common/services/node-permission.ser
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
import { PageComponent } from '../page.component';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { ExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './files.component.html'
|
||||
@ -54,8 +55,9 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
private uploadService: UploadService,
|
||||
private contentManagementService: ContentManagementService,
|
||||
private browsingFilesService: BrowsingFilesService,
|
||||
public permission: NodePermissionService) {
|
||||
super(store);
|
||||
public permission: NodePermissionService,
|
||||
extensions: ExtensionService) {
|
||||
super(store, extensions);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -34,9 +34,9 @@ import {
|
||||
import { DocumentListComponent } from '@alfresco/adf-content-services';
|
||||
import { ShareDataTableAdapter } from '@alfresco/adf-content-services';
|
||||
import { LibrariesComponent } from './libraries.component';
|
||||
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
|
||||
describe('LibrariesComponent', () => {
|
||||
let fixture: ComponentFixture<LibrariesComponent>;
|
||||
|
@ -34,6 +34,7 @@ import { DeleteLibraryAction } from '../../store/actions';
|
||||
import { SiteEntry } from 'alfresco-js-api';
|
||||
import { ContentManagementService } from '../../common/services/content-management.service';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { ExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './libraries.component.html'
|
||||
@ -44,8 +45,9 @@ export class LibrariesComponent extends PageComponent implements OnInit {
|
||||
private content: ContentManagementService,
|
||||
private contentApi: ContentApiService,
|
||||
store: Store<AppStore>,
|
||||
extensions: ExtensionService,
|
||||
private router: Router) {
|
||||
super(store);
|
||||
super(store, extensions);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -29,7 +29,7 @@ class TestClass extends PageComponent {
|
||||
node: any;
|
||||
|
||||
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 { SelectionState } from '../store/states/selection.state';
|
||||
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 {
|
||||
|
||||
@ -49,6 +51,7 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
||||
selection: SelectionState;
|
||||
displayMode = DisplayMode.List;
|
||||
sharedPreviewUrl$: Observable<string>;
|
||||
actions: Array<ContentActionExtension> = [];
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
constructor(protected store: Store<AppStore>) {}
|
||||
constructor(
|
||||
protected store: Store<AppStore>,
|
||||
protected extensions: ExtensionService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.sharedPreviewUrl$ = this.store.select(sharedUrl);
|
||||
@ -68,6 +73,21 @@ export abstract class PageComponent implements OnInit, OnDestroy {
|
||||
this.selection = selection;
|
||||
if (selection.isEmpty) {
|
||||
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.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()"
|
||||
(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>
|
||||
|
||||
<button
|
||||
|
@ -33,6 +33,8 @@ import { AppStore } from '../../store/states/app.state';
|
||||
import { DeleteNodesAction } from '../../store/actions';
|
||||
import { PageComponent } from '../page.component';
|
||||
import { ContentApiService } from '../../services/content-api.service';
|
||||
import { ExtensionService } from '../../extensions/extension.service';
|
||||
import { OpenWithExtension } from '../../extensions/open-with.extension';
|
||||
@Component({
|
||||
selector: 'app-preview',
|
||||
templateUrl: 'preview.component.html',
|
||||
@ -55,6 +57,7 @@ export class PreviewComponent extends PageComponent implements OnInit {
|
||||
navigateMultiple = false;
|
||||
|
||||
selectedEntities: MinimalNodeEntity[] = [];
|
||||
openWith: Array<OpenWithExtension> = [];
|
||||
|
||||
constructor(
|
||||
private contentApi: ContentApiService,
|
||||
@ -63,9 +66,9 @@ export class PreviewComponent extends PageComponent implements OnInit {
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
store: Store<AppStore>,
|
||||
public permission: NodePermissionService) {
|
||||
|
||||
super(store);
|
||||
public permission: NodePermissionService,
|
||||
extensions: ExtensionService) {
|
||||
super(store, extensions);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@ -97,6 +100,8 @@ export class PreviewComponent extends PageComponent implements OnInit {
|
||||
this.subscriptions = this.subscriptions.concat([
|
||||
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;
|
||||
}, []);
|
||||
}
|
||||
|
||||
// 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>
|
||||
</button>
|
||||
<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
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
|
@ -32,6 +32,7 @@ import { PageComponent } from '../page.component';
|
||||
import { NodePermissionService } from '../../common/services/node-permission.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
import { ExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './recent-files.component.html'
|
||||
@ -40,10 +41,11 @@ export class RecentFilesComponent extends PageComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
store: Store<AppStore>,
|
||||
extensions: ExtensionService,
|
||||
private uploadService: UploadService,
|
||||
private content: ContentManagementService,
|
||||
public permission: NodePermissionService) {
|
||||
super(store);
|
||||
super(store, extensions);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -3,6 +3,15 @@
|
||||
<adf-breadcrumb root="APP.BROWSE.SEARCH.TITLE">
|
||||
</adf-breadcrumb>
|
||||
<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
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
|
@ -31,6 +31,7 @@ import { PageComponent } from '../page.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
import { NavigateToFolder } from '../../store/actions';
|
||||
import { ExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search',
|
||||
@ -53,9 +54,10 @@ export class SearchComponent extends PageComponent implements OnInit {
|
||||
public permission: NodePermissionService,
|
||||
private queryBuilder: SearchQueryBuilderService,
|
||||
private route: ActivatedRoute,
|
||||
store: Store<AppStore>
|
||||
store: Store<AppStore>,
|
||||
extensions: ExtensionService
|
||||
) {
|
||||
super(store);
|
||||
super(store, extensions);
|
||||
|
||||
queryBuilder.paging = {
|
||||
skipCount: 0,
|
||||
|
@ -80,7 +80,6 @@
|
||||
Cardview
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-checkbox
|
||||
[(ngModel)]="share"
|
||||
@ -88,5 +87,12 @@
|
||||
Share
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<mat-checkbox
|
||||
[(ngModel)]="extensions"
|
||||
(change)="onChangeExtensionsFeature($event)">
|
||||
Extensions
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
|
@ -52,6 +52,7 @@ export class SettingsComponent implements OnInit {
|
||||
comments: boolean;
|
||||
cardview: boolean;
|
||||
share: boolean;
|
||||
extensions: boolean;
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
@ -86,6 +87,9 @@ export class SettingsComponent implements OnInit {
|
||||
|
||||
const share = this.appConfig.get('experimental.share');
|
||||
this.share = (share === true || share === 'true');
|
||||
|
||||
const extensions = this.appConfig.get('experimental.extensions');
|
||||
this.extensions = (extensions === true || extensions === 'true');
|
||||
}
|
||||
|
||||
apply(model: any, isValid: boolean) {
|
||||
@ -121,4 +125,8 @@ export class SettingsComponent implements OnInit {
|
||||
onChangeShareFeature(event: MatCheckboxChange) {
|
||||
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>
|
||||
</button>
|
||||
<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
|
||||
*ngIf="selection.count === 1"
|
||||
color="primary"
|
||||
|
@ -31,17 +31,20 @@ import { NodePermissionService } from '../../common/services/node-permission.ser
|
||||
import { PageComponent } from '../page.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
import { ExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './shared-files.component.html'
|
||||
})
|
||||
export class SharedFilesComponent extends PageComponent implements OnInit {
|
||||
|
||||
constructor(store: Store<AppStore>,
|
||||
private uploadService: UploadService,
|
||||
private content: ContentManagementService,
|
||||
public permission: NodePermissionService) {
|
||||
super(store);
|
||||
constructor(
|
||||
store: Store<AppStore>,
|
||||
extensions: ExtensionService,
|
||||
private uploadService: UploadService,
|
||||
private content: ContentManagementService,
|
||||
public permission: NodePermissionService
|
||||
) {
|
||||
super(store, extensions);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@ -52,7 +55,9 @@ export class SharedFilesComponent extends PageComponent implements OnInit {
|
||||
this.content.nodesMoved.subscribe(() => this.reload()),
|
||||
this.content.nodesRestored.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>
|
||||
</div>
|
||||
<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
|
||||
mat-menu-item
|
||||
[disabled]="!permission.check(node, ['create'])"
|
||||
@ -48,16 +56,17 @@
|
||||
</adf-sidebar-action-menu>
|
||||
</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">
|
||||
<li *ngFor="let item of list" class="sidenav-menu__item"
|
||||
<li *ngFor="let item of group" class="sidenav-menu__item"
|
||||
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'"
|
||||
[attr.aria-label]="item.label | translate"
|
||||
[attr.aria-label]="item.title | translate"
|
||||
mat-icon-button
|
||||
mat-ripple
|
||||
matRippleColor="primary"
|
||||
@ -68,13 +77,13 @@
|
||||
</button>
|
||||
|
||||
<span #rippleTrigger
|
||||
[routerLink]="item.route"
|
||||
class="menu__item--label"
|
||||
[routerLink]="item.route.url"
|
||||
[hidden]="!showLabel"
|
||||
[ngClass]="{
|
||||
'menu__item--active': rla.isActive,
|
||||
'menu__item--default': !rla.isActive
|
||||
}">{{ item.label | translate }}</span>
|
||||
}">{{ item.title | translate }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -25,26 +25,17 @@
|
||||
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
|
||||
import { AppConfigService } from '@alfresco/adf-core';
|
||||
import { BrowsingFilesService } from '../../common/services/browsing-files.service';
|
||||
import { SidenavComponent } from './sidenav.component';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { NodeEffects } from '../../store/effects/node.effects';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { ExperimentalDirective } from '../../directives/experimental.directive';
|
||||
|
||||
describe('SidenavComponent', () => {
|
||||
let fixture: ComponentFixture<SidenavComponent>;
|
||||
let component: SidenavComponent;
|
||||
let browsingService: BrowsingFilesService;
|
||||
let appConfig: AppConfigService;
|
||||
let appConfigSpy;
|
||||
|
||||
const navItem = {
|
||||
label: 'some-label',
|
||||
route: {
|
||||
url: '/some-url'
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@ -53,19 +44,17 @@ describe('SidenavComponent', () => {
|
||||
EffectsModule.forRoot([NodeEffects])
|
||||
],
|
||||
declarations: [
|
||||
SidenavComponent
|
||||
SidenavComponent,
|
||||
ExperimentalDirective
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
browsingService = TestBed.get(BrowsingFilesService);
|
||||
appConfig = TestBed.get(AppConfigService);
|
||||
|
||||
fixture = TestBed.createComponent(SidenavComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
appConfigSpy = spyOn(appConfig, 'get').and.returnValue([navItem]);
|
||||
});
|
||||
}));
|
||||
|
||||
@ -77,20 +66,4 @@ describe('SidenavComponent', () => {
|
||||
|
||||
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 { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { AppConfigService } from '@alfresco/adf-core';
|
||||
|
||||
|
||||
import { BrowsingFilesService } from '../../common/services/browsing-files.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({
|
||||
selector: 'app-sidenav',
|
||||
@ -41,22 +41,25 @@ export class SidenavComponent implements OnInit, OnDestroy {
|
||||
@Input() showLabel: boolean;
|
||||
|
||||
node: MinimalNodeEntryEntity = null;
|
||||
navigation = [];
|
||||
groups: Array<NavigationExtension[]> = [];
|
||||
createActions: Array<CreateExtension> = [];
|
||||
|
||||
private subscriptions: Subscription[] = [];
|
||||
|
||||
constructor(
|
||||
private browsingFilesService: BrowsingFilesService,
|
||||
private appConfig: AppConfigService,
|
||||
public permission: NodePermissionService
|
||||
public permission: NodePermissionService,
|
||||
private extensions: ExtensionService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.navigation = this.buildMenu();
|
||||
this.groups = this.extensions.getNavigationGroups();
|
||||
this.createActions = this.extensions.createActions;
|
||||
|
||||
this.subscriptions.concat([
|
||||
this.browsingFilesService.onChangeParent
|
||||
.subscribe((node: MinimalNodeEntryEntity) => this.node = node)
|
||||
this.browsingFilesService.onChangeParent.subscribe(
|
||||
(node: MinimalNodeEntryEntity) => (this.node = node)
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
@ -64,10 +67,9 @@ export class SidenavComponent implements OnInit, OnDestroy {
|
||||
this.subscriptions.forEach(s => s.unsubscribe());
|
||||
}
|
||||
|
||||
private buildMenu() {
|
||||
const schema = this.appConfig.get('navigation');
|
||||
const data = Array.isArray(schema) ? { main: schema } : schema;
|
||||
|
||||
return Object.keys(data).map((key) => data[key]);
|
||||
// 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,6 +10,15 @@
|
||||
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'APP.DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
|
||||
</button>
|
||||
<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
|
||||
color="primary"
|
||||
mat-icon-button
|
||||
|
@ -30,6 +30,7 @@ import { Store } from '@ngrx/store';
|
||||
import { selectUser } from '../../store/selectors/app.selectors';
|
||||
import { AppStore } from '../../store/states/app.state';
|
||||
import { ProfileState } from '../../store/states/profile.state';
|
||||
import { ExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './trashcan.component.html'
|
||||
@ -38,8 +39,9 @@ export class TrashcanComponent extends PageComponent implements OnInit {
|
||||
user: ProfileState;
|
||||
|
||||
constructor(private contentManagementService: ContentManagementService,
|
||||
extensions: ExtensionService,
|
||||
store: Store<AppStore>) {
|
||||
super(store);
|
||||
super(store, extensions);
|
||||
}
|
||||
|
||||
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 { MinimalNodeEntity } from 'alfresco-js-api';
|
||||
|
||||
export const NAVIGATE_URL = 'NAVIGATE_URL';
|
||||
export const NAVIGATE_ROUTE = 'NAVIGATE_ROUTE';
|
||||
export const NAVIGATE_FOLDER = 'NAVIGATE_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 {
|
||||
readonly type = NAVIGATE_ROUTE;
|
||||
constructor(public payload: any[]) {}
|
||||
@ -40,7 +46,6 @@ export class NavigateToFolder implements Action {
|
||||
constructor(public payload: MinimalNodeEntity) {}
|
||||
}
|
||||
|
||||
|
||||
export class NavigateToParentFolder implements Action {
|
||||
readonly type = NAVIGATE_PARENT_FOLDER;
|
||||
constructor(public payload: MinimalNodeEntity) {}
|
||||
|
@ -34,12 +34,22 @@ import {
|
||||
NAVIGATE_PARENT_FOLDER,
|
||||
NAVIGATE_ROUTE
|
||||
} from '../actions';
|
||||
import { NavigateToFolder, NAVIGATE_FOLDER } from '../actions/router.actions';
|
||||
import { NavigateToFolder, NAVIGATE_FOLDER, NavigateUrlAction, NAVIGATE_URL } from '../actions/router.actions';
|
||||
|
||||
@Injectable()
|
||||
export class RouterEffects {
|
||||
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 })
|
||||
navigateRoute$ = this.actions$.pipe(
|
||||
ofType<NavigateRouteAction>(NAVIGATE_ROUTE),
|
||||
|
@ -82,7 +82,7 @@ export class SnackbarEffects {
|
||||
}
|
||||
|
||||
const snackBarRef = this.snackBar.open(message, actionName, {
|
||||
duration: action.duration,
|
||||
duration: action.duration || 4000,
|
||||
panelClass: panelClass
|
||||
});
|
||||
|
||||
|
@ -62,6 +62,7 @@ import { NodeActionsService } from '../common/services/node-actions.service';
|
||||
import { NodePermissionService } from '../common/services/node-permission.service';
|
||||
import { BrowsingFilesService } from '../common/services/browsing-files.service';
|
||||
import { ContentApiService } from '../services/content-api.service';
|
||||
import { ExtensionService } from '../extensions/extension.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -76,7 +77,11 @@ import { ContentApiService } from '../services/content-api.service';
|
||||
EffectsModule.forRoot([])
|
||||
],
|
||||
declarations: [TranslatePipeMock],
|
||||
exports: [TranslatePipeMock, RouterTestingModule, MaterialModule],
|
||||
exports: [
|
||||
TranslatePipeMock,
|
||||
RouterTestingModule,
|
||||
MaterialModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: AlfrescoApiService, useClass: AlfrescoApiMock },
|
||||
{ provide: TranslationService, useClass: TranslationMock },
|
||||
@ -112,7 +117,8 @@ import { ContentApiService } from '../services/content-api.service';
|
||||
NodeActionsService,
|
||||
NodePermissionService,
|
||||
BrowsingFilesService,
|
||||
ContentApiService
|
||||
ContentApiService,
|
||||
ExtensionService
|
||||
]
|
||||
})
|
||||
export class AppTestingModule {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user