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 ![](../images/navigation-03.png) +### 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,