diff --git a/docs/getting-started/navigation.md b/docs/getting-started/navigation.md
index 23aa98e62..07ec281b0 100644
--- a/docs/getting-started/navigation.md
+++ b/docs/getting-started/navigation.md
@@ -186,4 +186,28 @@ Map the `/custom-route` in `app.routes.ts` as a child of `LayoutComponent` defin

+### Rendering custom components
+
+Navigation definition also supports custom components to be dynamically render. The schema for this is as follows:
+
+```json
+"navbar": [
+ {
+ "id": "app.navbar.primary",
+ "items": [
+ ...
+
+ {
+ "id": "custom-component",
+ "component": "custom-menu-item"
+ }
+
+ ...
+ ]
+ }
+]
+```
+
+Note that components must be declared as entryComponents under the app module.
+
For more information about the content of a custom page see [Document List Layout](/features/document-list-layout) section.
diff --git a/e2e/pages/page.ts b/e2e/pages/page.ts
index 6663b7e66..64a616f22 100755
--- a/e2e/pages/page.ts
+++ b/e2e/pages/page.ts
@@ -56,22 +56,22 @@ export abstract class Page {
constructor(public url: string = '') {}
- getTitle() {
- return browser.getTitle();
+ async getTitle() {
+ return await browser.getTitle();
}
- load(relativeUrl: string = '') {
+ async load(relativeUrl: string = '') {
const hash = USE_HASH_STRATEGY ? '/#' : '';
const path = `${browser.baseUrl}${hash}${this.url}${relativeUrl}`;
- return browser.get(path);
+ return await browser.get(path);
}
- waitForApp() {
- return browser.wait(EC.presenceOf(this.layout), BROWSER_WAIT_TIMEOUT);
+ async waitForApp() {
+ return await browser.wait(EC.presenceOf(this.layout), BROWSER_WAIT_TIMEOUT);
}
- waitForSnackBarToAppear() {
- return browser.wait(until.elementLocated(by.css('.mat-snack-bar-container')), BROWSER_WAIT_TIMEOUT, '------- timeout waiting for snackbar to appear');
+ async waitForSnackBarToAppear() {
+ return await browser.wait(until.elementLocated(by.css('.mat-snack-bar-container')), BROWSER_WAIT_TIMEOUT, '------- timeout waiting for snackbar to appear');
}
async waitForSnackBarToClose() {
diff --git a/e2e/suites/actions/upload-new-version.test.ts b/e2e/suites/actions/upload-new-version.test.ts
index 4391f8370..8ebcf8f8d 100755
--- a/e2e/suites/actions/upload-new-version.test.ts
+++ b/e2e/suites/actions/upload-new-version.test.ts
@@ -730,7 +730,7 @@ describe('Upload new version', () => {
it('file is updated after uploading a new version - major - [C307004]', async () => {
await searchInput.clickSearchButton();
await searchInput.checkFilesAndFolders();
- await searchInput.searchFor('search-f');
+ await searchInput.searchFor(fileSearch1);
await dataTable.waitForBody();
await dataTable.selectItem(fileSearch1, parentSearch);
await toolbar.clickMoreActionsUploadNewVersion();
@@ -751,7 +751,7 @@ describe('Upload new version', () => {
it('file is updated after uploading a new version - minor - [C307005]', async () => {
await searchInput.clickSearchButton();
await searchInput.checkFilesAndFolders();
- await searchInput.searchFor('search-f');
+ await searchInput.searchFor(fileSearch2);
await dataTable.waitForBody();
await dataTable.selectItem(fileSearch2, parentSearch);
await toolbar.clickMoreActionsUploadNewVersion();
@@ -772,7 +772,7 @@ describe('Upload new version', () => {
it('file is not updated when clicking Cancel - [C307006]', async () => {
await searchInput.clickSearchButton();
await searchInput.checkFilesAndFolders();
- await searchInput.searchFor('search-f');
+ await searchInput.searchFor(fileSearch3);
await dataTable.waitForBody();
await dataTable.selectItem(fileSearch3, parentSearch);
await toolbar.clickMoreActionsUploadNewVersion();
@@ -792,7 +792,7 @@ describe('Upload new version', () => {
it('upload new version fails when new file name already exists - [C307007]', async () => {
await searchInput.clickSearchButton();
await searchInput.checkFilesAndFolders();
- await searchInput.searchFor('search-f');
+ await searchInput.searchFor(fileSearch4);
await dataTable.waitForBody();
await dataTable.selectItem(fileSearch4, parentSearch);
await toolbar.clickMoreActionsUploadNewVersion();
@@ -814,7 +814,7 @@ describe('Upload new version', () => {
it('file is unlocked after uploading a new version - [C307008]', async () => {
await searchInput.clickSearchButton();
await searchInput.checkFilesAndFolders();
- await searchInput.searchFor('search-f');
+ await searchInput.searchFor(fileLockedSearch1);
await dataTable.waitForBody();
await dataTable.selectItem(fileLockedSearch1, parentSearch);
await toolbar.clickMoreActionsUploadNewVersion();
@@ -836,7 +836,7 @@ describe('Upload new version', () => {
it('file remains locked after canceling of uploading a new version - [C307009]', async () => {
await searchInput.clickSearchButton();
await searchInput.checkFilesAndFolders();
- await searchInput.searchFor('search-f');
+ await searchInput.searchFor(fileLockedSearch2);
await dataTable.waitForBody();
await dataTable.selectItem(fileLockedSearch2, parentSearch);
await toolbar.clickMoreActionsUploadNewVersion();
diff --git a/e2e/utilities/reporters/console/console-logger.ts b/e2e/utilities/reporters/console/console-logger.ts
deleted file mode 100755
index 9287a5ac7..000000000
--- a/e2e/utilities/reporters/console/console-logger.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-/*!
- * @license
- * Alfresco Example Content Application
- *
- * Copyright (C) 2005 - 2019 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 .
- */
-
-/* tslint:disable */
-const chalk = require('chalk');
-/* tslint:enable */
-
-export const log = {
- i: 0,
-
- get indentation(): string {
- return new Array(this.i).fill(' ').join('');
- },
-
- indent() {
- this.i++;
- return this;
- },
-
- unindent() {
- this.i--;
- return this;
- },
-
- log(message: string = '', options: any = { ignoreIndentation: false }) {
- const indentation = (!options.ignoreIndentation)
- ? this.indentation
- : '';
-
- console.log(`${indentation}${message}`);
-
- return this;
- },
-
- blank() {
- return this.log();
- },
-
- info(message: string = '', options: any = { bold: false, title: false }) {
- const { bold } = options;
- const style = (bold ? chalk.bold : chalk).gray;
-
- return this.log(style(message), options);
- },
-
- success(message: string = '', options: any = { bold: false }) {
- const style = options.bold ? chalk.bold.green : chalk.green;
-
- return this.log(style(message), options);
- },
-
- error(message: string = '', options: any = { bold: false }) {
- const style = options.bold ? chalk.bold.red : chalk.red;
-
- return this.log(style(message), options);
- }
-};
diff --git a/e2e/utilities/reporters/console/console.ts b/e2e/utilities/reporters/console/console.ts
deleted file mode 100755
index fd7269769..000000000
--- a/e2e/utilities/reporters/console/console.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/*!
- * @license
- * Alfresco Example Content Application
- *
- * Copyright (C) 2005 - 2019 Alfresco Software Limited
- *
- * This file is part of the Alfresco Example Content Application.
- * If the software was purchased under a paid Alfresco license, the terms of
- * the paid license agreement will prevail. Otherwise, the software is
- * provided under the following open source license terms:
- *
- * The Alfresco Example Content Application is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * The Alfresco Example Content Application is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Alfresco. If not, see .
- */
-
-import { log } from './console-logger';
-
-const errors = [];
-
-export const consoleReporter = {
- jasmineStarted(suiteInfo) {
- log.blank().info(
- `Running ${suiteInfo.totalSpecsDefined} tests`,
- { bold: true, title: true }
- ).blank();
- },
-
- suiteStarted(suite) {
- log.info(suite.description).indent();
- },
-
- specDone: (spec) => {
- const {
- status,
- description,
- failedExpectations
- } = spec;
-
- if (status === 'passed') {
- log.success(`∙ ${description}`);
- }
-
- if (status === 'failed') {
- log.error(`✕ ${description}`, { bold: true });
-
- errors.push(spec);
-
- failedExpectations.forEach((failed) => {
- log.error(` ${failed.message}`);
- });
- }
- },
-
- suiteDone: (result) => {
- log.unindent();
- },
-
- jasmineDone: (result) => {
- if (!!errors.length) {
- log .blank()
- .blank()
- .info(`${errors.length} failing tests`, { bold: true, title: true });
-
- errors.forEach(error => {
- log .blank()
- .error(`✕ ${error.fullName}`, { bold: true });
-
- error.failedExpectations.forEach(failed => {
- log .info(`${failed.message}`)
- .blank()
- .error(`${failed.stack}`);
- });
- });
- } else {
- log.success(`All tests passed!`, { bold: true });
- }
-
- log.blank().blank();
- }
-};
diff --git a/src/app/components/sidenav/sidenav.component.html b/src/app/components/sidenav/sidenav.component.html
index f8762c692..5b52d5616 100644
--- a/src/app/components/sidenav/sidenav.component.html
+++ b/src/app/components/sidenav/sidenav.component.html
@@ -14,14 +14,24 @@
-
+
+
+
+
+
@@ -33,14 +43,25 @@
-
+
+
+
+
+
+
diff --git a/src/app/components/sidenav/sidenav.component.ts b/src/app/components/sidenav/sidenav.component.ts
index 3de324148..9c2a8c3a1 100755
--- a/src/app/components/sidenav/sidenav.component.ts
+++ b/src/app/components/sidenav/sidenav.component.ts
@@ -38,9 +38,9 @@ import { AppExtensionService } from '../../extensions/extension.service';
import { NavBarGroupRef } from '@alfresco/adf-extensions';
import { Store } from '@ngrx/store';
import { AppStore } from '../../store/states';
-import { ruleContext } from '../../store/selectors/app.selectors';
+import { sidenavState } from '../../store/selectors/app.selectors';
import { Subject } from 'rxjs';
-import { takeUntil, distinctUntilChanged, map } from 'rxjs/operators';
+import { takeUntil, distinctUntilChanged, debounceTime } from 'rxjs/operators';
@Component({
selector: 'app-sidenav',
@@ -68,9 +68,9 @@ export class SidenavComponent implements OnInit, OnDestroy {
ngOnInit() {
this.store
- .select(ruleContext)
+ .select(sidenavState)
.pipe(
- map(rules => rules.repository),
+ debounceTime(300),
distinctUntilChanged(),
takeUntil(this.onDestroy$)
)
diff --git a/src/app/components/sidenav/sidenav.module.ts b/src/app/components/sidenav/sidenav.module.ts
index 427f0b02c..79f0d9f42 100644
--- a/src/app/components/sidenav/sidenav.module.ts
+++ b/src/app/components/sidenav/sidenav.module.ts
@@ -28,6 +28,8 @@ import { AppCreateMenuModule } from '../create-menu/create-menu.module';
import { CommonModule } from '@angular/common';
import { CoreModule } from '@alfresco/adf-core';
import { RouterModule } from '@angular/router';
+import { ExtensionsModule } from '@alfresco/adf-extensions';
+import { CoreExtensionsModule } from '../../extensions/core.extensions.module';
import { ExpansionPanelDirective } from './directives/expansion-panel.directive';
import { MenuPanelDirective } from './directives/menu-panel.directive';
import { CollapsedTemplateDirective } from './directives/collapsed-template.directive';
@@ -41,6 +43,8 @@ import { ActionDirective } from './directives/action.directive';
imports: [
CommonModule,
CoreModule.forChild(),
+ CoreExtensionsModule.forChild(),
+ ExtensionsModule.forChild(),
RouterModule,
AppCreateMenuModule
],
diff --git a/src/app/extensions/extension.service.ts b/src/app/extensions/extension.service.ts
index 5a058aa12..380f9eff9 100644
--- a/src/app/extensions/extension.service.ts
+++ b/src/app/extensions/extension.service.ts
@@ -244,6 +244,12 @@ export class AppExtensionService implements RuleContext {
.filter(child => this.filterVisible(child))
.sort(sortByOrder)
.map(child => {
+ if (child.component) {
+ return {
+ ...child
+ };
+ }
+
if (!child.click) {
const childRouteRef = this.extensions.getRouteById(
child.route
@@ -268,6 +274,10 @@ export class AppExtensionService implements RuleContext {
};
}
+ if (item.component) {
+ return { ...item };
+ }
+
if (!item.click) {
const routeRef = this.extensions.getRouteById(item.route);
const url = `/${routeRef ? routeRef.path : item.route}`;
diff --git a/src/app/store/selectors/app.selectors.ts b/src/app/store/selectors/app.selectors.ts
index d7d0c241a..baaa7aad4 100644
--- a/src/app/store/selectors/app.selectors.ts
+++ b/src/app/store/selectors/app.selectors.ts
@@ -103,6 +103,17 @@ export const isAdmin = createSelector(
state => state.user.isAdmin
);
+export const sidenavState = createSelector(
+ appSelection,
+ appNavigation,
+ (selection, navigation) => {
+ return {
+ selection,
+ navigation
+ };
+ }
+);
+
export const ruleContext = createSelector(
appSelection,
appNavigation,