mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-24 17:31:52 +00:00
[ACA-2320] Navigation - support store actions (#1052)
* move component into folder * update module * add children template references * clean up styling * clean up theme * use content projection * remove old tests * button menu component * expand menu component * link item component * resolve action directive * custom active link directive * collapse template reference * expanded template reference * expansion panel directive * item template directive * menu panel directive * support for ngrx actions * update side navigation inplementation * remove unused component * remove unused styling * update module * clean up * unit tests * unit tests * remove unused component * lint * remove unused import * fix test * add tooltip * fix text * fix e2e * use action route commands * remove fdescribe * styles fix * e2e fix tooltip test * fix active route when drill down * update docs
This commit is contained in:
committed by
Denys Vuika
parent
9f127c0530
commit
839c9d0dbb
@@ -138,6 +138,30 @@ In the `app.config.json` define a link entry which will point to the custom page
|
||||
|
||||
```
|
||||
|
||||
This can also be declared using ngrx store action:
|
||||
|
||||
```json
|
||||
{
|
||||
...,
|
||||
"navigation": [
|
||||
"main": [ ... ],
|
||||
"secondary": [ ... ],
|
||||
"custom": [
|
||||
{
|
||||
"icon": "work",
|
||||
"label": "Link",
|
||||
"title": "My custom link",
|
||||
"click": {
|
||||
"action": "NAVIGATE_ROUTE",
|
||||
"payload": "custom-route"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Map the `/custom-route` in `app.routes.ts` as a child of `LayoutComponent` definition.
|
||||
|
||||
```js
|
||||
|
@@ -32,13 +32,14 @@ import { Utils } from '../../utilities/utils';
|
||||
export class Sidenav extends Component {
|
||||
private static selectors = {
|
||||
root: 'app-sidenav',
|
||||
link: '.menu__item',
|
||||
label: '.item--label',
|
||||
link: '.item',
|
||||
label: '.action-button__label',
|
||||
expansion_panel: ".mat-expansion-panel-header",
|
||||
expansion_panel_content: ".mat-expansion-panel-body",
|
||||
active: 'mat-accent',
|
||||
activeClass: '.item--active',
|
||||
activeChild: 'item--active',
|
||||
activeClass: '.action-button--active',
|
||||
activeClassName: 'action-button--active',
|
||||
activeChild: 'action-button--active',
|
||||
|
||||
newButton: '[data-automation-id="create-button"]',
|
||||
|
||||
@@ -106,7 +107,7 @@ export class Sidenav extends Component {
|
||||
}
|
||||
|
||||
async isActive(name: string) {
|
||||
return await this.getLinkLabel(name).isElementPresent(by.css(Sidenav.selectors.activeClass));
|
||||
return (await this.getLinkLabel(name).getAttribute('class')).includes(Sidenav.selectors.activeClassName);
|
||||
}
|
||||
|
||||
async childIsActive(name: string) {
|
||||
|
@@ -61,22 +61,22 @@ describe('Sidebar', () => {
|
||||
it('My Libraries is automatically selected on expanding File Libraries - [C289900]', async () => {
|
||||
await sidenav.expandFileLibraries();
|
||||
expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.MY_LIBRARIES);
|
||||
expect(await sidenav.isActive(SIDEBAR_LABELS.FILE_LIBRARIES)).toBe(false, 'File Libraries link is active');
|
||||
expect(await sidenav.childIsActive(SIDEBAR_LABELS.MY_LIBRARIES)).toBe(true, 'My Libraries link not active');
|
||||
expect(await sidenav.isActive(SIDEBAR_LABELS.FILE_LIBRARIES)).toBe(true, 'File Libraries is not active');
|
||||
expect(await sidenav.isActive(SIDEBAR_LABELS.MY_LIBRARIES)).toBe(true, 'My Libraries link not active');
|
||||
});
|
||||
|
||||
it('navigate to Favorite Libraries - [C289902]', async () => {
|
||||
await page.goToFavoriteLibraries();
|
||||
expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.FAVORITE_LIBRARIES);
|
||||
expect(await sidenav.isActive(SIDEBAR_LABELS.FILE_LIBRARIES)).toBe(false, 'File Libraries link is active');
|
||||
expect(await sidenav.childIsActive(SIDEBAR_LABELS.FAVORITE_LIBRARIES)).toBe(true, 'Favorite Libraries link not active');
|
||||
expect(await sidenav.isActive(SIDEBAR_LABELS.FILE_LIBRARIES)).toBe(true, 'File Libraries link is not active');
|
||||
expect(await sidenav.isActive(SIDEBAR_LABELS.FAVORITE_LIBRARIES)).toBe(true, 'Favorite Libraries link not active');
|
||||
});
|
||||
|
||||
it('navigate to My Libraries - [C289901]', async () => {
|
||||
await page.goToMyLibraries();
|
||||
expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.MY_LIBRARIES);
|
||||
expect(await sidenav.isActive(SIDEBAR_LABELS.FILE_LIBRARIES)).toBe(false, 'File Libraries link is active');
|
||||
expect(await sidenav.childIsActive(SIDEBAR_LABELS.MY_LIBRARIES)).toBe(true, 'My Libraries link not active');
|
||||
expect(await sidenav.isActive(SIDEBAR_LABELS.FILE_LIBRARIES)).toBe(true, 'File Libraries link is not active');
|
||||
expect(await sidenav.isActive(SIDEBAR_LABELS.MY_LIBRARIES)).toBe(true, 'My Libraries link not active');
|
||||
});
|
||||
|
||||
it('navigates to "Shared Files" - [C213110]', async () => {
|
||||
|
@@ -16,7 +16,6 @@
|
||||
|
||||
<ng-container *ngIf="!expanded">
|
||||
<button
|
||||
color="accent"
|
||||
mat-icon-button
|
||||
class="app-create-menu--collapsed"
|
||||
data-automation-id="create-button"
|
||||
@@ -25,7 +24,7 @@
|
||||
title="{{ 'APP.NEW_MENU.TOOLTIP' | translate }}"
|
||||
>
|
||||
<mat-icon
|
||||
[color]="createMenu.menuOpen ? 'accent' : 'primary'"
|
||||
class="app-create-menu--icon"
|
||||
title="{{ 'APP.NEW_MENU.TOOLTIP' | translate }}"
|
||||
>queue</mat-icon
|
||||
>
|
||||
|
@@ -52,13 +52,17 @@
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: mat-color($primary);
|
||||
color: mat-color($accent);
|
||||
}
|
||||
margin: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.app-create-menu--icon {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
|
||||
&__sub-menu {
|
||||
.mat-menu-item {
|
||||
display: flex;
|
||||
|
@@ -21,12 +21,13 @@
|
||||
<adf-sidenav-layout-navigation>
|
||||
<ng-template let-isMenuMinimized="isMenuMinimized">
|
||||
<app-sidenav
|
||||
[showLabel]="!isMenuMinimized()"
|
||||
[mode]="isMenuMinimized() ? 'collapsed' : 'expanded'"
|
||||
[attr.data-automation-id]="
|
||||
isMenuMinimized() ? 'collapsed' : 'expanded'
|
||||
"
|
||||
(swipeleft)="hideMenu($event)"
|
||||
></app-sidenav>
|
||||
>
|
||||
</app-sidenav>
|
||||
</ng-template>
|
||||
</adf-sidenav-layout-navigation>
|
||||
|
||||
|
@@ -0,0 +1,50 @@
|
||||
<ng-container *ngIf="!item.children">
|
||||
<button
|
||||
class="action-button"
|
||||
mat-icon-button
|
||||
acaActiveLink="action-button--active"
|
||||
[action]="item"
|
||||
[id]="item.id"
|
||||
[attr.aria-label]="item.title | translate"
|
||||
[attr.title]="item.description | translate"
|
||||
[attr.data-automation-id]="item.id"
|
||||
>
|
||||
<adf-icon [value]="item.icon"></adf-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="item.children && item.children.length">
|
||||
<button
|
||||
[matMenuTriggerFor]="menu"
|
||||
[acaMenuPanel]="item"
|
||||
#acaMenuPanel="acaMenuPanel"
|
||||
mat-icon-button
|
||||
[id]="item.id"
|
||||
[attr.data-automation-id]="item.id"
|
||||
[attr.title]="item.description | translate"
|
||||
[attr.aria-label]="item.title | translate"
|
||||
class="action-button"
|
||||
[ngClass]="{
|
||||
'action-button--active': acaMenuPanel.hasActiveLinks()
|
||||
}"
|
||||
>
|
||||
<adf-icon [value]="item.icon"></adf-icon>
|
||||
</button>
|
||||
|
||||
<mat-menu #menu="matMenu" [overlapTrigger]="false">
|
||||
<button
|
||||
*ngFor="let child of item.children; trackBy: trackById"
|
||||
acaActiveLink="action-button--active"
|
||||
[action]="child"
|
||||
[attr.aria-label]="child.title | translate"
|
||||
[id]="child.id"
|
||||
[attr.title]="child.description | translate"
|
||||
[attr.data-automation-id]="child.id"
|
||||
mat-menu-item
|
||||
class="action-button"
|
||||
>
|
||||
<adf-icon *ngIf="child.icon" [value]="child.icon"></adf-icon>
|
||||
<span class="action-button__label">{{ child.title | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
@@ -0,0 +1,99 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ButtonMenuComponent } from './button-menu.component';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
TranslateModule,
|
||||
TranslateLoader,
|
||||
TranslateFakeLoader
|
||||
} from '@ngx-translate/core';
|
||||
import { AppSidenavModule } from '../sidenav.module';
|
||||
|
||||
describe('ButtonMenuComponent', () => {
|
||||
let component: ButtonMenuComponent;
|
||||
let fixture: ComponentFixture<ButtonMenuComponent>;
|
||||
let router: Router;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AppTestingModule,
|
||||
AppSidenavModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(ButtonMenuComponent);
|
||||
component = fixture.componentInstance;
|
||||
router = TestBed.get(Router);
|
||||
|
||||
spyOn(router, 'navigate');
|
||||
});
|
||||
|
||||
it('should render action item', () => {
|
||||
component.item = {
|
||||
id: 'test-action-button',
|
||||
url: 'dummy'
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const actionButton = document.body.querySelector('#test-action-button');
|
||||
expect(actionButton).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should render action item with children', () => {
|
||||
component.item = {
|
||||
id: 'test-action-button',
|
||||
children: [
|
||||
{
|
||||
id: 'child-1',
|
||||
title: 'child-1',
|
||||
url: 'dummy'
|
||||
},
|
||||
{
|
||||
id: 'child-2',
|
||||
title: 'child-2',
|
||||
url: 'dummy'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const actionButton = document.body.querySelector(
|
||||
'[id="test-action-button"]'
|
||||
);
|
||||
actionButton.dispatchEvent(new Event('click'));
|
||||
|
||||
expect(document.querySelector('[id="child-1"]')).not.toBeNull();
|
||||
expect(document.querySelector('[id="child-2"]')).not.toBeNull();
|
||||
});
|
||||
});
|
@@ -0,0 +1,58 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
ViewEncapsulation,
|
||||
OnInit,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||
|
||||
@Component({
|
||||
selector: 'app-button-menu',
|
||||
templateUrl: './button-menu.component.html',
|
||||
host: { class: 'app-button-menu' },
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ButtonMenuComponent implements OnInit {
|
||||
@Input() item;
|
||||
|
||||
constructor(
|
||||
private cd: ChangeDetectorRef,
|
||||
private overlayContainer: OverlayContainer
|
||||
) {
|
||||
this.overlayContainer.getContainerElement().classList.add('aca-menu-panel');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
|
||||
trackById(index: number, obj: { id: string }) {
|
||||
return obj.id;
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
<ng-container *ngIf="!item.children">
|
||||
<div class="item">
|
||||
<button
|
||||
acaActiveLink="action-button--active"
|
||||
[action]="item"
|
||||
[attr.aria-label]="item.title | translate"
|
||||
[id]="item.id"
|
||||
[attr.data-automation-id]="item.id"
|
||||
[attr.title]="item.description | translate"
|
||||
mat-button
|
||||
class="action-button full-width"
|
||||
>
|
||||
<adf-icon *ngIf="item.icon" [value]="item.icon"></adf-icon>
|
||||
<span class="action-button__label">{{ item.title | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="item.children && item.children.length">
|
||||
<mat-expansion-panel
|
||||
[expanded]="acaExpansionPanel.hasActiveLinks()"
|
||||
[acaExpansionPanel]="item"
|
||||
#acaExpansionPanel="acaExpansionPanel"
|
||||
[@.disabled]="true"
|
||||
>
|
||||
<mat-expansion-panel-header expandedHeight="48px" collapsedHeight="48px">
|
||||
<mat-panel-title>
|
||||
<div class="item">
|
||||
<button
|
||||
[ngClass]="{
|
||||
'action-button--active': acaExpansionPanel.hasActiveLinks()
|
||||
}"
|
||||
[attr.aria-label]="item.title | translate"
|
||||
[id]="item.id"
|
||||
[attr.title]="item.description | translate"
|
||||
[attr.data-automation-id]="item.id"
|
||||
mat-button
|
||||
class="action-button full-width"
|
||||
>
|
||||
<adf-icon *ngIf="item.icon" [value]="item.icon"></adf-icon>
|
||||
<span class="action-button__label">{{
|
||||
item.title | translate
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div *ngFor="let child of item.children; trackBy: trackById" class="item">
|
||||
<button
|
||||
acaActiveLink="action-button--active"
|
||||
[action]="child"
|
||||
[attr.aria-label]="child.title | translate"
|
||||
[id]="child.id"
|
||||
[attr.data-automation-id]="child.id"
|
||||
[attr.title]="child.description | translate"
|
||||
mat-button
|
||||
class="action-button full-width"
|
||||
>
|
||||
<adf-icon *ngIf="child.icon" [value]="child.icon"></adf-icon>
|
||||
<span class="action-button__label">{{ child.title | translate }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</ng-container>
|
@@ -0,0 +1,99 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ExpandMenuComponent } from './expand-menu.component';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
TranslateModule,
|
||||
TranslateLoader,
|
||||
TranslateFakeLoader
|
||||
} from '@ngx-translate/core';
|
||||
import { AppSidenavModule } from '../sidenav.module';
|
||||
|
||||
describe('ExpandMenuComponent', () => {
|
||||
let component: ExpandMenuComponent;
|
||||
let fixture: ComponentFixture<ExpandMenuComponent>;
|
||||
let router: Router;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AppTestingModule,
|
||||
AppSidenavModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(ExpandMenuComponent);
|
||||
component = fixture.componentInstance;
|
||||
router = TestBed.get(Router);
|
||||
|
||||
spyOn(router, 'navigate');
|
||||
});
|
||||
|
||||
it('should render action item', () => {
|
||||
component.item = {
|
||||
id: 'test-action-button',
|
||||
url: 'dummy'
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const actionButton = document.body.querySelector('#test-action-button');
|
||||
expect(actionButton).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should render action item with children', () => {
|
||||
component.item = {
|
||||
id: 'test-action-button',
|
||||
children: [
|
||||
{
|
||||
id: 'child-1',
|
||||
title: 'child-1',
|
||||
url: 'dummy'
|
||||
},
|
||||
{
|
||||
id: 'child-2',
|
||||
title: 'child-2',
|
||||
url: 'dummy'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const actionButton = document.body.querySelector(
|
||||
'[id="test-action-button"]'
|
||||
);
|
||||
actionButton.dispatchEvent(new Event('click'));
|
||||
|
||||
expect(document.querySelector('[id="child-1"]')).not.toBeNull();
|
||||
expect(document.querySelector('[id="child-2"]')).not.toBeNull();
|
||||
});
|
||||
});
|
@@ -0,0 +1,52 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
Input,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-expand-menu',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
templateUrl: './expand-menu.component.html',
|
||||
host: { class: 'app-expand-menu' }
|
||||
})
|
||||
export class ExpandMenuComponent implements OnInit {
|
||||
@Input() item;
|
||||
|
||||
constructor(private cd: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
|
||||
trackById(index: number, obj: { id: string }) {
|
||||
return obj.id;
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ActionDirective } from './action.directive';
|
||||
|
||||
describe('ActionDirective', () => {
|
||||
let directive: ActionDirective;
|
||||
const routeMock = <any>{
|
||||
navigate: jasmine.createSpy('navigate'),
|
||||
parseUrl: () => ({
|
||||
root: {
|
||||
children: []
|
||||
}
|
||||
})
|
||||
};
|
||||
const storeMock = <any>{
|
||||
dispatch: jasmine.createSpy('dispatch')
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
directive = new ActionDirective(routeMock, storeMock);
|
||||
});
|
||||
|
||||
it('should navigate if action is route', () => {
|
||||
directive.action = { route: 'dummy' };
|
||||
directive.onClick();
|
||||
expect(routeMock.navigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should dispatch store action', () => {
|
||||
directive.action = { click: {} };
|
||||
directive.onClick();
|
||||
expect(storeMock.dispatch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
72
src/app/components/sidenav/directives/action.directive.ts
Normal file
72
src/app/components/sidenav/directives/action.directive.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Directive, Input, HostListener } from '@angular/core';
|
||||
import { PRIMARY_OUTLET, Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../../store/states/app.state';
|
||||
|
||||
@Directive({
|
||||
/* tslint:disable-next-line */
|
||||
selector: '[action]',
|
||||
exportAs: 'action'
|
||||
})
|
||||
export class ActionDirective {
|
||||
@Input() action;
|
||||
|
||||
@HostListener('click')
|
||||
onClick() {
|
||||
if (this.action.route) {
|
||||
this.router.navigate(this.getNavigationCommands(this.action.route));
|
||||
} else if (this.action.click) {
|
||||
this.dispatchAction(this.action.click);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private router: Router, private store: Store<AppStore>) {}
|
||||
|
||||
private dispatchAction(action) {
|
||||
this.store.dispatch({
|
||||
type: action.action,
|
||||
payload: this.getNavigationCommands(action.payload)
|
||||
});
|
||||
}
|
||||
|
||||
private getNavigationCommands(url: string): any[] {
|
||||
const urlTree = this.router.parseUrl(url);
|
||||
const urlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
|
||||
|
||||
if (!urlSegmentGroup) {
|
||||
return [url];
|
||||
}
|
||||
|
||||
const urlSegments = urlSegmentGroup.segments;
|
||||
|
||||
return urlSegments.reduce(function(acc, item) {
|
||||
acc.push(item.path, item.parameters);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
}
|
@@ -0,0 +1,109 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { AppSidenavModule } from '../sidenav.module';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||
import { Router, NavigationEnd } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-test-component',
|
||||
template: `
|
||||
<span
|
||||
id="test-element"
|
||||
acaActiveLink="active-link-class"
|
||||
[action]="item"
|
||||
></span>
|
||||
`
|
||||
})
|
||||
class TestComponent {
|
||||
item = {
|
||||
route: 'dummy'
|
||||
};
|
||||
}
|
||||
|
||||
class MockRouter {
|
||||
private subject = new Subject();
|
||||
events = this.subject.asObservable();
|
||||
url = '';
|
||||
|
||||
navigateByUrl(url: string) {
|
||||
const navigationEnd = new NavigationEnd(0, '', url);
|
||||
this.subject.next(navigationEnd);
|
||||
}
|
||||
}
|
||||
|
||||
describe('ActionDirective', () => {
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
let router: Router;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [AppTestingModule, AppSidenavModule],
|
||||
declarations: [TestComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: Router,
|
||||
useClass: MockRouter
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
router = TestBed.get(Router);
|
||||
});
|
||||
|
||||
it('should add active route class name', () => {
|
||||
fixture.detectChanges();
|
||||
router.navigateByUrl('/dummy');
|
||||
// fixture.detectChanges();
|
||||
expect(
|
||||
document.body
|
||||
.querySelector('#test-element')
|
||||
.className.includes('active-link-class')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should remove class name if route not active', () => {
|
||||
fixture.detectChanges();
|
||||
router.navigateByUrl('/dummy');
|
||||
|
||||
expect(
|
||||
document.body
|
||||
.querySelector('#test-element')
|
||||
.className.includes('active-link-class')
|
||||
).toBe(true);
|
||||
|
||||
router.navigateByUrl('/other');
|
||||
|
||||
expect(
|
||||
document.body
|
||||
.querySelector('#test-element')
|
||||
.className.includes('active-link-class')
|
||||
).not.toBe(true);
|
||||
});
|
||||
});
|
@@ -27,57 +27,71 @@ import {
|
||||
Directive,
|
||||
OnInit,
|
||||
Input,
|
||||
HostListener,
|
||||
OnDestroy
|
||||
ElementRef,
|
||||
Renderer2,
|
||||
ContentChildren,
|
||||
QueryList,
|
||||
AfterContentInit
|
||||
} from '@angular/core';
|
||||
import { Router, NavigationEnd } from '@angular/router';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
import { Subject } from 'rxjs';
|
||||
import { MatExpansionPanel } from '@angular/material/expansion';
|
||||
import { ActionDirective } from './action.directive';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaExpansionPanel]',
|
||||
exportAs: 'acaExpansionPanel'
|
||||
selector: '[acaActiveLink]',
|
||||
exportAs: 'acaActiveLink'
|
||||
})
|
||||
export class AcaExpansionPanelDirective implements OnInit, OnDestroy {
|
||||
@Input() acaExpansionPanel;
|
||||
selected = false;
|
||||
export class ActiveLinkDirective implements OnInit, AfterContentInit {
|
||||
@Input() acaActiveLink;
|
||||
@ContentChildren(ActionDirective, { descendants: true })
|
||||
links: QueryList<ActionDirective>;
|
||||
isLinkActive = false;
|
||||
|
||||
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
@HostListener('click')
|
||||
onClick() {
|
||||
if (this.expansionPanel.expanded && !this.selected) {
|
||||
this.router.navigate([this.acaExpansionPanel.children[0].url]);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private expansionPanel: MatExpansionPanel
|
||||
private element: ElementRef,
|
||||
private renderer: Renderer2
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.setSelected(this.router.url);
|
||||
|
||||
this.router.events
|
||||
.pipe(
|
||||
filter(event => event instanceof NavigationEnd),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe((event: NavigationEnd) => {
|
||||
this.setSelected(event.urlAfterRedirects);
|
||||
this.update(event.urlAfterRedirects);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
private update(url: string) {
|
||||
this.links.map(item => {
|
||||
const itemUrl = this.resolveUrl(item);
|
||||
if (url && url.substring(1).startsWith(itemUrl)) {
|
||||
this.isLinkActive = true;
|
||||
this.renderer.addClass(this.element.nativeElement, this.acaActiveLink);
|
||||
} else {
|
||||
this.isLinkActive = false;
|
||||
this.renderer.removeClass(
|
||||
this.element.nativeElement,
|
||||
this.acaActiveLink
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setSelected(url: string) {
|
||||
this.selected = this.acaExpansionPanel.children.some(child =>
|
||||
url.startsWith(child.url)
|
||||
ngAfterContentInit() {
|
||||
this.links.changes.subscribe(() => this.update(this.router.url));
|
||||
this.update(this.router.url);
|
||||
}
|
||||
|
||||
private resolveUrl(item): string {
|
||||
return (
|
||||
(item.action && (item.action.click && item.action.click.payload)) ||
|
||||
item.action.route
|
||||
);
|
||||
}
|
||||
}
|
31
src/app/components/sidenav/directives/collapsed-template.directive.ts
Executable file
31
src/app/components/sidenav/directives/collapsed-template.directive.ts
Executable file
@@ -0,0 +1,31 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Directive } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaCollapsedTemplate]'
|
||||
})
|
||||
export class CollapsedTemplateDirective {}
|
31
src/app/components/sidenav/directives/expanded-template.directive.ts
Executable file
31
src/app/components/sidenav/directives/expanded-template.directive.ts
Executable file
@@ -0,0 +1,31 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Directive } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaExpandedTemplate]'
|
||||
})
|
||||
export class ExpandedTemplateDirective {}
|
@@ -0,0 +1,139 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NavigationEnd } from '@angular/router';
|
||||
import { ExpansionPanelDirective } from './expansion-panel.directive';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
class RouterStub {
|
||||
url;
|
||||
private subject = new Subject();
|
||||
events = this.subject.asObservable();
|
||||
|
||||
constructor(url = 'some-url') {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
parseUrl() {
|
||||
return {
|
||||
root: {
|
||||
children: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
navigate(nextUrl: string) {
|
||||
const navigationEnd = new NavigationEnd(0, this.url, nextUrl);
|
||||
this.subject.next(navigationEnd);
|
||||
}
|
||||
}
|
||||
|
||||
describe('AcaExpansionPanel', () => {
|
||||
const mockStore = <any>{
|
||||
dispatch: jasmine.createSpy('dispatch')
|
||||
};
|
||||
const mockMatExpansionPanel = <any>{
|
||||
expanded: false,
|
||||
children: []
|
||||
};
|
||||
|
||||
describe('hasActiveLinks()', () => {
|
||||
it('should return true if child is active route', () => {
|
||||
const router: any = new RouterStub('dummy-route-2');
|
||||
const item = {
|
||||
children: [{ url: 'dummy-route-1' }, { url: 'dummy-route-2' }]
|
||||
};
|
||||
const directive = new ExpansionPanelDirective(
|
||||
mockStore,
|
||||
router,
|
||||
mockMatExpansionPanel
|
||||
);
|
||||
|
||||
directive.acaExpansionPanel = item;
|
||||
|
||||
expect(directive.hasActiveLinks()).toBe(true);
|
||||
});
|
||||
it('should return false if no child is active route', () => {
|
||||
const router: any = new RouterStub('other');
|
||||
const item = {
|
||||
children: [{ url: 'dummy-route-1' }, { url: 'dummy-route-2' }]
|
||||
};
|
||||
const directive = new ExpansionPanelDirective(
|
||||
mockStore,
|
||||
router,
|
||||
mockMatExpansionPanel
|
||||
);
|
||||
|
||||
directive.acaExpansionPanel = item;
|
||||
|
||||
expect(directive.hasActiveLinks()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('navigation', () => {
|
||||
it('should navigate to first child if none is active route', () => {
|
||||
const router: any = new RouterStub('other');
|
||||
spyOn(router, 'navigate').and.callThrough();
|
||||
const item = {
|
||||
children: [{ url: 'dummy-route-1' }, { url: 'dummy-route-2' }]
|
||||
};
|
||||
|
||||
mockMatExpansionPanel.expanded = true;
|
||||
|
||||
const directive = new ExpansionPanelDirective(
|
||||
mockStore,
|
||||
router,
|
||||
mockMatExpansionPanel
|
||||
);
|
||||
|
||||
directive.acaExpansionPanel = item;
|
||||
|
||||
directive.onClick();
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['dummy-route-1']);
|
||||
});
|
||||
|
||||
it('should not navigate to first child if one is active route', () => {
|
||||
const router: any = new RouterStub('dummy-route-2');
|
||||
spyOn(router, 'navigate').and.callThrough();
|
||||
const item = {
|
||||
children: [{ url: 'dummy-route-1' }, { url: 'dummy-route-2' }]
|
||||
};
|
||||
|
||||
const directive = new ExpansionPanelDirective(
|
||||
mockStore,
|
||||
router,
|
||||
mockMatExpansionPanel
|
||||
);
|
||||
|
||||
directive.acaExpansionPanel = item;
|
||||
mockMatExpansionPanel.expanded = true;
|
||||
|
||||
directive.onClick();
|
||||
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,113 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
Directive,
|
||||
Input,
|
||||
HostListener,
|
||||
OnInit,
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
import { Router, NavigationEnd, PRIMARY_OUTLET } from '@angular/router';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
import { Subject } from 'rxjs';
|
||||
import { MatExpansionPanel } from '@angular/material/expansion';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../../store/states/app.state';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaExpansionPanel]',
|
||||
exportAs: 'acaExpansionPanel'
|
||||
})
|
||||
export class ExpansionPanelDirective implements OnInit, OnDestroy {
|
||||
@Input() acaExpansionPanel;
|
||||
public hasActiveChildren = false;
|
||||
|
||||
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
@HostListener('click')
|
||||
onClick() {
|
||||
if (this.expansionPanel.expanded && !this.hasActiveLinks()) {
|
||||
const firstChild = this.acaExpansionPanel.children[0];
|
||||
if (firstChild.url) {
|
||||
this.router.navigate(this.getNavigationCommands(firstChild.url));
|
||||
} else {
|
||||
this.store.dispatch({
|
||||
type: firstChild.action.action,
|
||||
payload: this.getNavigationCommands(firstChild.action.payload)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private router: Router,
|
||||
private expansionPanel: MatExpansionPanel
|
||||
) {}
|
||||
|
||||
hasActiveLinks() {
|
||||
if (this.acaExpansionPanel && this.acaExpansionPanel.children) {
|
||||
return this.acaExpansionPanel.children.some(child => {
|
||||
return this.router.url.startsWith(child.url || child.action.payload);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.hasActiveChildren = this.hasActiveLinks();
|
||||
|
||||
this.router.events
|
||||
.pipe(
|
||||
filter(event => event instanceof NavigationEnd),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.hasActiveChildren = this.hasActiveLinks();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
private getNavigationCommands(url: string): any[] {
|
||||
const urlTree = this.router.parseUrl(url);
|
||||
const urlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
|
||||
|
||||
if (!urlSegmentGroup) {
|
||||
return [url];
|
||||
}
|
||||
|
||||
const urlSegments = urlSegmentGroup.segments;
|
||||
|
||||
return urlSegments.reduce(function(acc, item) {
|
||||
acc.push(item.path, item.parameters);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
}
|
@@ -0,0 +1,123 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NavigationEnd } from '@angular/router';
|
||||
import { MenuPanelDirective } from './menu-panel.directive';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
class RouterStub {
|
||||
url;
|
||||
private subject = new Subject();
|
||||
events = this.subject.asObservable();
|
||||
|
||||
constructor(url = 'some-url') {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
parseUrl() {
|
||||
return {
|
||||
root: {
|
||||
children: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
navigate(nextUrl: string) {
|
||||
const navigationEnd = new NavigationEnd(0, this.url, nextUrl);
|
||||
this.subject.next(navigationEnd);
|
||||
}
|
||||
}
|
||||
|
||||
describe('MenuPanelDirective', () => {
|
||||
const mockStore = <any>{
|
||||
dispatch: jasmine.createSpy('dispatch')
|
||||
};
|
||||
const mockMatExpansionPanel = <any>{
|
||||
expanded: false,
|
||||
children: []
|
||||
};
|
||||
|
||||
describe('hasActiveLinks()', () => {
|
||||
it('should return true if child is active route', () => {
|
||||
const router: any = new RouterStub('dummy-route-2');
|
||||
const item = {
|
||||
children: [{ url: 'dummy-route-1' }, { url: 'dummy-route-2' }]
|
||||
};
|
||||
const directive = new MenuPanelDirective(mockStore, router);
|
||||
|
||||
directive.acaMenuPanel = item;
|
||||
|
||||
expect(directive.hasActiveLinks()).toBe(true);
|
||||
});
|
||||
it('should return false if no child is active route', () => {
|
||||
const router: any = new RouterStub('other');
|
||||
const item = {
|
||||
children: [{ url: 'dummy-route-1' }, { url: 'dummy-route-2' }]
|
||||
};
|
||||
const directive = new MenuPanelDirective(mockStore, router);
|
||||
|
||||
directive.acaMenuPanel = item;
|
||||
|
||||
expect(directive.hasActiveLinks()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('navigation', () => {
|
||||
it('should navigate to first child if none is active route', () => {
|
||||
const router: any = new RouterStub('other');
|
||||
spyOn(router, 'navigate').and.callThrough();
|
||||
const item = {
|
||||
children: [{ url: 'dummy-route-1' }, { url: 'dummy-route-2' }]
|
||||
};
|
||||
|
||||
mockMatExpansionPanel.expanded = true;
|
||||
|
||||
const directive = new MenuPanelDirective(mockStore, router);
|
||||
|
||||
directive.acaMenuPanel = item;
|
||||
|
||||
directive.menuOpened();
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['dummy-route-1']);
|
||||
});
|
||||
|
||||
it('should not navigate to first child if one is active route', () => {
|
||||
const router: any = new RouterStub('dummy-route-2');
|
||||
spyOn(router, 'navigate').and.callThrough();
|
||||
const item = {
|
||||
children: [{ url: 'dummy-route-1' }, { url: 'dummy-route-2' }]
|
||||
};
|
||||
|
||||
const directive = new MenuPanelDirective(mockStore, router);
|
||||
|
||||
directive.acaMenuPanel = item;
|
||||
mockMatExpansionPanel.expanded = true;
|
||||
|
||||
directive.menuOpened();
|
||||
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
108
src/app/components/sidenav/directives/menu-panel.directive.ts
Normal file
108
src/app/components/sidenav/directives/menu-panel.directive.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/*!
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
Directive,
|
||||
Input,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
HostListener
|
||||
} from '@angular/core';
|
||||
import { Router, NavigationEnd, PRIMARY_OUTLET } from '@angular/router';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppStore } from '../../../store/states/app.state';
|
||||
|
||||
@Directive({
|
||||
selector: '[acaMenuPanel]',
|
||||
exportAs: 'acaMenuPanel'
|
||||
})
|
||||
export class MenuPanelDirective implements OnInit, OnDestroy {
|
||||
@Input() acaMenuPanel;
|
||||
hasActiveChildren = false;
|
||||
|
||||
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
@HostListener('menuOpened')
|
||||
menuOpened() {
|
||||
if (this.acaMenuPanel.children && !this.hasActiveLinks()) {
|
||||
const firstChild = this.acaMenuPanel.children[0];
|
||||
if (firstChild.url) {
|
||||
this.router.navigate(this.getNavigationCommands(firstChild.url));
|
||||
} else {
|
||||
this.store.dispatch({
|
||||
type: firstChild.action.action,
|
||||
payload: this.getNavigationCommands(firstChild.action.payload)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private store: Store<AppStore>, private router: Router) {}
|
||||
|
||||
hasActiveLinks() {
|
||||
if (this.acaMenuPanel && this.acaMenuPanel.children) {
|
||||
return this.acaMenuPanel.children.some(child => {
|
||||
return this.router.url.startsWith(child.url || child.action.payload);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.hasActiveChildren = this.hasActiveLinks();
|
||||
|
||||
this.router.events
|
||||
.pipe(
|
||||
filter(event => event instanceof NavigationEnd),
|
||||
takeUntil(this.onDestroy$)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.hasActiveChildren = this.hasActiveLinks();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
private getNavigationCommands(url: string): any[] {
|
||||
const urlTree = this.router.parseUrl(url);
|
||||
const urlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET];
|
||||
|
||||
if (!urlSegmentGroup) {
|
||||
return [url];
|
||||
}
|
||||
|
||||
const urlSegments = urlSegmentGroup.segments;
|
||||
|
||||
return urlSegments.reduce(function(acc, item) {
|
||||
acc.push(item.path, item.parameters);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
}
|
@@ -1,119 +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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { NavigationEnd } from '@angular/router';
|
||||
import { AcaExpansionPanelDirective } from './expansion-panel.directive';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
class RouterStub {
|
||||
url;
|
||||
private subject = new Subject();
|
||||
events = this.subject.asObservable();
|
||||
|
||||
constructor(url = 'some-url') {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
navigate(nextUrl: string) {
|
||||
const navigationEnd = new NavigationEnd(0, this.url, nextUrl);
|
||||
this.subject.next(navigationEnd);
|
||||
}
|
||||
}
|
||||
|
||||
describe('AcaExpansionPanel', () => {
|
||||
const item = {
|
||||
children: [{ url: 'dummy-route-1' }, { url: 'dummy-route-2' }]
|
||||
};
|
||||
|
||||
it('should set panel as selected on initialization if url contains child url', () => {
|
||||
const router: any = new RouterStub('dummy-route-2');
|
||||
const directive = new AcaExpansionPanelDirective(router, null);
|
||||
|
||||
directive.acaExpansionPanel = item;
|
||||
directive.ngOnInit();
|
||||
|
||||
expect(directive.selected).toBe(true);
|
||||
});
|
||||
|
||||
it('should not set panel as selected on initialization if url does not contain child url', () => {
|
||||
const router: any = new RouterStub('dummy-route-other');
|
||||
const directive = new AcaExpansionPanelDirective(router, null);
|
||||
|
||||
directive.acaExpansionPanel = item;
|
||||
directive.ngOnInit();
|
||||
|
||||
expect(directive.selected).toBe(false);
|
||||
});
|
||||
|
||||
it('should go on first child url when expended and url does not contain any child url', () => {
|
||||
const router: any = new RouterStub();
|
||||
spyOn(router, 'navigate');
|
||||
const expansionPanelInstance: any = { expanded: true };
|
||||
const directive = new AcaExpansionPanelDirective(
|
||||
router,
|
||||
expansionPanelInstance
|
||||
);
|
||||
directive.acaExpansionPanel = item;
|
||||
|
||||
directive.ngOnInit();
|
||||
directive.onClick();
|
||||
|
||||
expect(router.navigate).toHaveBeenCalledWith(['dummy-route-1']);
|
||||
});
|
||||
|
||||
it('should not go on first child url when expended and url contains any child url', () => {
|
||||
const router: any = new RouterStub('dummy-route-2');
|
||||
spyOn(router, 'navigate');
|
||||
const expansionPanelInstance: any = { expanded: true };
|
||||
const directive = new AcaExpansionPanelDirective(
|
||||
router,
|
||||
expansionPanelInstance
|
||||
);
|
||||
directive.acaExpansionPanel = item;
|
||||
|
||||
directive.ngOnInit();
|
||||
directive.onClick();
|
||||
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set panel selected on navigation change', done => {
|
||||
const router: any = new RouterStub();
|
||||
const directive = new AcaExpansionPanelDirective(router, null);
|
||||
directive.acaExpansionPanel = item;
|
||||
|
||||
directive.ngOnInit();
|
||||
|
||||
router.navigate('dummy-route-1');
|
||||
done();
|
||||
|
||||
expect(directive.selected).toBe(true);
|
||||
|
||||
router.navigate('some-url');
|
||||
done();
|
||||
|
||||
expect(directive.selected).toBe(false);
|
||||
});
|
||||
});
|
@@ -1,216 +1,48 @@
|
||||
<div class="sidenav">
|
||||
<div class="section action-menu">
|
||||
<app-create-menu [expanded]="showLabel"></app-create-menu>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let group of groups; trackBy: trackById" class="section">
|
||||
<div class="menu">
|
||||
<div
|
||||
*ngFor="let item of group.items; trackBy: trackById"
|
||||
routerLinkActive
|
||||
#routerLink="routerLinkActive"
|
||||
>
|
||||
<ng-container *ngIf="showLabel">
|
||||
<ng-container *ngIf="!item.children">
|
||||
<div
|
||||
class="menu__item"
|
||||
[attr.title]="item.description | translate"
|
||||
[attr.data-automation-id]="item.id"
|
||||
>
|
||||
<button
|
||||
[id]="item.id"
|
||||
mat-icon-button
|
||||
mat-ripple
|
||||
[routerLink]="item.url"
|
||||
[color]="routerLink.isActive ? 'accent' : 'primary'"
|
||||
[attr.aria-label]="item.title | translate"
|
||||
matRippleColor="primary"
|
||||
[matRippleTrigger]="rippleTrigger"
|
||||
[matRippleCentered]="true"
|
||||
[matRippleRadius]="20"
|
||||
>
|
||||
<adf-icon
|
||||
[color]="routerLink.isActive ? 'accent' : 'primary'"
|
||||
[value]="item.icon"
|
||||
></adf-icon>
|
||||
</button>
|
||||
|
||||
<span
|
||||
#rippleTrigger
|
||||
class="item--label item--parent"
|
||||
[routerLink]="item.url"
|
||||
[attr.aria-label]="item.title | translate"
|
||||
[ngClass]="{
|
||||
'item--active': routerLink.isActive,
|
||||
'item--default': !routerLink.isActive
|
||||
}"
|
||||
>
|
||||
{{ item.title | translate }}</span
|
||||
>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="item.children && item.children.length">
|
||||
<mat-expansion-panel
|
||||
#expansionPanel="matExpansionPanel"
|
||||
[acaExpansionPanel]="item"
|
||||
[expanded]="routerLink.isActive"
|
||||
[@.disabled]="true"
|
||||
>
|
||||
<mat-expansion-panel-header
|
||||
expandedHeight="48px"
|
||||
collapsedHeight="48px"
|
||||
>
|
||||
<mat-panel-title
|
||||
[attr.title]="item.description | translate"
|
||||
[attr.data-automation-id]="item.id"
|
||||
>
|
||||
<adf-icon
|
||||
[color]="
|
||||
routerLink.isActive && !expansionPanel.expanded
|
||||
? 'accent'
|
||||
: 'primary'
|
||||
"
|
||||
[value]="item.icon"
|
||||
></adf-icon>
|
||||
<span
|
||||
class="item--label item--parent"
|
||||
[id]="item.id"
|
||||
[ngClass]="{
|
||||
'item--default':
|
||||
!routerLink.isActive && expansionPanel.expanded,
|
||||
'item--active':
|
||||
routerLink.isActive && !expansionPanel.expanded
|
||||
}"
|
||||
>{{ item.title | translate }}</span
|
||||
>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div
|
||||
*ngFor="let child of item.children; trackBy: trackById"
|
||||
routerLinkActive
|
||||
#childRouteActive="routerLinkActive"
|
||||
[attr.title]="child.description | translate"
|
||||
[attr.data-automation-id]="child.id"
|
||||
>
|
||||
<ng-container *ngIf="child.icon">
|
||||
<button
|
||||
[id]="child.id"
|
||||
mat-icon-button
|
||||
mat-ripple
|
||||
[routerLink]="child.url"
|
||||
[color]="childRouteActive.isActive ? 'accent' : 'primary'"
|
||||
[attr.aria-label]="child.title | translate"
|
||||
matRippleColor="primary"
|
||||
[matRippleTrigger]="rippleTrigger"
|
||||
[matRippleCentered]="true"
|
||||
[matRippleRadius]="20"
|
||||
>
|
||||
<adf-icon [value]="item.icon"></adf-icon>
|
||||
</button>
|
||||
|
||||
<span
|
||||
#rippleTrigger
|
||||
[routerLink]="child.url"
|
||||
class="item--label item--label__trigger"
|
||||
[ngClass]="{
|
||||
'item--active': childRouteActive.isActive,
|
||||
'item--default': !childRouteActive.isActive
|
||||
}"
|
||||
>
|
||||
{{ child.title | translate }}
|
||||
</span>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!child.icon">
|
||||
<div
|
||||
[id]="child.id"
|
||||
class="menu__item item--label item--child"
|
||||
[routerLink]="child.url"
|
||||
[attr.aria-label]="child.title | translate"
|
||||
>
|
||||
<span
|
||||
[ngClass]="{
|
||||
'item--active': childRouteActive.isActive,
|
||||
'item--default': !childRouteActive.isActive
|
||||
}"
|
||||
>
|
||||
{{ child.title | translate }}
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!showLabel">
|
||||
<ng-container *ngIf="!item.children">
|
||||
<div class="menu__item">
|
||||
<button
|
||||
[id]="item.id"
|
||||
mat-icon-button
|
||||
[routerLink]="item.url"
|
||||
[color]="routerLink.isActive ? 'accent' : 'primary'"
|
||||
[attr.aria-label]="item.title | translate"
|
||||
[attr.title]="item.description | translate"
|
||||
[attr.data-automation-id]="item.id"
|
||||
>
|
||||
<adf-icon
|
||||
[color]="routerLink.isActive ? 'accent' : 'primary'"
|
||||
[value]="item.icon"
|
||||
></adf-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="item.children && item.children.length">
|
||||
<div
|
||||
class="menu__item"
|
||||
[attr.title]="item.description | translate"
|
||||
[attr.data-automation-id]="item.id"
|
||||
>
|
||||
<button
|
||||
[id]="item.id"
|
||||
color="accent"
|
||||
mat-icon-button
|
||||
#childMenu="matMenuTrigger"
|
||||
[matMenuTriggerFor]="menu"
|
||||
>
|
||||
<adf-icon
|
||||
[color]="
|
||||
routerLink.isActive || childMenu.menuOpen
|
||||
? 'accent'
|
||||
: 'primary'
|
||||
"
|
||||
[value]="item.icon"
|
||||
></adf-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<mat-menu #menu="matMenu" [overlapTrigger]="false">
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngFor="let child of item.children; trackBy: trackById"
|
||||
routerLinkActive
|
||||
#menuRouterLink="routerLinkActive"
|
||||
[routerLink]="child.url"
|
||||
[attr.title]="child.description | translate"
|
||||
[id]="child.id"
|
||||
[attr.data-automation-id]="child.id"
|
||||
>
|
||||
<span
|
||||
class="mat-button"
|
||||
[ngClass]="{ 'mat-primary': menuRouterLink.isActive }"
|
||||
>
|
||||
{{ child.title | translate }}
|
||||
</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-container [ngSwitch]="mode">
|
||||
<div class="section action-menu" [ngClass]="'section--' + mode">
|
||||
<app-create-menu [expanded]="mode === 'expanded'"></app-create-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngFor="let group of groups; trackBy: trackById"
|
||||
class="section"
|
||||
[ngClass]="'section--' + mode"
|
||||
>
|
||||
<ng-container *ngSwitchCase="'expanded'">
|
||||
<mat-list-item *ngFor="let item of group.items; trackBy: trackById">
|
||||
<ng-container *ngIf="expandedTemplate">
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="expandedTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: item }"
|
||||
>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!expandedTemplate">
|
||||
<app-expand-menu [item]="item"></app-expand-menu>
|
||||
</ng-container>
|
||||
</mat-list-item>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'collapsed'">
|
||||
<div
|
||||
class="list-item"
|
||||
*ngFor="let item of group.items; trackBy: trackById"
|
||||
>
|
||||
<ng-container *ngIf="collapsedTemplate">
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="collapsedTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: item }"
|
||||
>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!collapsedTemplate">
|
||||
<app-button-menu [item]="item"></app-button-menu>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
@@ -1,3 +1,10 @@
|
||||
.app-sidenav {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sidenav {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -8,10 +15,6 @@
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 8px 14px;
|
||||
}
|
||||
|
||||
.action-menu {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
@@ -19,14 +22,50 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
.section.action-menu {
|
||||
padding: 8px 14px;
|
||||
}
|
||||
|
||||
.menu__item {
|
||||
.section {
|
||||
padding: 8px 6px;
|
||||
}
|
||||
|
||||
.section--collapsed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
padding: 12px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-button .action-button__label {
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
|
||||
.app-item,
|
||||
.app-item .item {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 12px 0;
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
@@ -34,41 +73,27 @@
|
||||
text-decoration: none;
|
||||
text-decoration: none;
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.item--parent {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.item--label {
|
||||
cursor: pointer;
|
||||
width: 240px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.item--child {
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.item--label:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.item--label__trigger {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header {
|
||||
padding: 0 8px !important;
|
||||
padding: 0 8px 0 0 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-title span {
|
||||
margin-left: 8px;
|
||||
.mat-expansion-panel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mat-expansion-indicator {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-body {
|
||||
padding: 0 24px 0px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header-title {
|
||||
|
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* 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
|
||||
* 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
|
||||
@@ -16,7 +16,7 @@
|
||||
*
|
||||
* 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
|
||||
* 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
|
||||
@@ -27,7 +27,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
|
||||
import { SidenavComponent } from './sidenav.component';
|
||||
import { AppTestingModule } from '../../testing/app-testing.module';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
|
||||
describe('SidenavComponent', () => {
|
||||
@@ -46,7 +45,7 @@ describe('SidenavComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MatExpansionModule, AppTestingModule],
|
||||
imports: [AppTestingModule],
|
||||
providers: [AppExtensionService],
|
||||
declarations: [SidenavComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
|
@@ -6,11 +6,27 @@
|
||||
|
||||
$border: 1px solid mat-color($foreground, divider, 0.07);
|
||||
|
||||
.sidenav {
|
||||
@include angular-material-theme($theme);
|
||||
.aca-menu-panel {
|
||||
.action-button--active {
|
||||
color: mat-color($accent) !important;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
color: mat-color($primary);
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
}
|
||||
|
||||
.sidenav {
|
||||
background-color: mat-color($background, background);
|
||||
|
||||
.item:hover .action-button__label {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
|
||||
.mat-expansion-panel {
|
||||
background-color: unset;
|
||||
color: mat-color($primary, 0.87) !important;
|
||||
@@ -29,28 +45,20 @@
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.adf-sidebar-action-menu-button {
|
||||
background-color: mat-color($accent);
|
||||
.mat-expansion-panel-header-title > span {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.action-button--active {
|
||||
color: mat-color($accent) !important;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
color: mat-color($primary);
|
||||
}
|
||||
|
||||
.section {
|
||||
border-bottom: $border;
|
||||
}
|
||||
|
||||
.item--label:not(.item--active):hover {
|
||||
color: mat-color($foreground, text);
|
||||
}
|
||||
|
||||
.item--label {
|
||||
color: mat-color($primary, 0.87);
|
||||
}
|
||||
|
||||
.item--active {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
|
||||
.item--default {
|
||||
color: mat-color($primary, 0.87);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
14
src/app/components/sidenav/sidenav.component.ts
Normal file → Executable file
14
src/app/components/sidenav/sidenav.component.ts
Normal file → Executable file
@@ -25,11 +25,15 @@
|
||||
|
||||
import {
|
||||
Component,
|
||||
ContentChild,
|
||||
Input,
|
||||
TemplateRef,
|
||||
OnInit,
|
||||
ViewEncapsulation,
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
import { CollapsedTemplateDirective } from './directives/collapsed-template.directive';
|
||||
import { ExpandedTemplateDirective } from './directives/expanded-template.directive';
|
||||
import { AppExtensionService } from '../../extensions/extension.service';
|
||||
import { NavBarGroupRef } from '@alfresco/adf-extensions';
|
||||
import { Store } from '@ngrx/store';
|
||||
@@ -46,12 +50,16 @@ import { takeUntil, distinctUntilChanged, map } from 'rxjs/operators';
|
||||
host: { class: 'app-sidenav' }
|
||||
})
|
||||
export class SidenavComponent implements OnInit, OnDestroy {
|
||||
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
@Input() mode: 'collapsed' | 'expanded' = 'expanded';
|
||||
|
||||
@Input()
|
||||
showLabel: boolean;
|
||||
@ContentChild(ExpandedTemplateDirective, { read: TemplateRef })
|
||||
expandedTemplate;
|
||||
|
||||
@ContentChild(CollapsedTemplateDirective, { read: TemplateRef })
|
||||
collapsedTemplate;
|
||||
|
||||
groups: Array<NavBarGroupRef> = [];
|
||||
private onDestroy$: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
|
@@ -26,11 +26,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AppCreateMenuModule } from '../create-menu/create-menu.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SidenavComponent } from './sidenav.component';
|
||||
import { CoreModule } from '@alfresco/adf-core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AcaExpansionPanelDirective } from './expansion-panel.directive';
|
||||
|
||||
import { ExpansionPanelDirective } from './directives/expansion-panel.directive';
|
||||
import { MenuPanelDirective } from './directives/menu-panel.directive';
|
||||
import { CollapsedTemplateDirective } from './directives/collapsed-template.directive';
|
||||
import { ExpandedTemplateDirective } from './directives/expanded-template.directive';
|
||||
import { SidenavComponent } from './sidenav.component';
|
||||
import { ActiveLinkDirective } from './directives/active-link.directive';
|
||||
import { ExpandMenuComponent } from './components/expand-menu.component';
|
||||
import { ButtonMenuComponent } from './components/button-menu.component';
|
||||
import { ActionDirective } from './directives/action.directive';
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -38,7 +44,27 @@ import { AcaExpansionPanelDirective } from './expansion-panel.directive';
|
||||
RouterModule,
|
||||
AppCreateMenuModule
|
||||
],
|
||||
declarations: [SidenavComponent, AcaExpansionPanelDirective],
|
||||
exports: [SidenavComponent, AcaExpansionPanelDirective]
|
||||
declarations: [
|
||||
MenuPanelDirective,
|
||||
ExpansionPanelDirective,
|
||||
ExpandedTemplateDirective,
|
||||
CollapsedTemplateDirective,
|
||||
ActiveLinkDirective,
|
||||
ActionDirective,
|
||||
ExpandMenuComponent,
|
||||
ButtonMenuComponent,
|
||||
SidenavComponent
|
||||
],
|
||||
exports: [
|
||||
MenuPanelDirective,
|
||||
ExpansionPanelDirective,
|
||||
ExpandedTemplateDirective,
|
||||
CollapsedTemplateDirective,
|
||||
ActiveLinkDirective,
|
||||
ActionDirective,
|
||||
ExpandMenuComponent,
|
||||
ButtonMenuComponent,
|
||||
SidenavComponent
|
||||
]
|
||||
})
|
||||
export class AppSidenavModule {}
|
||||
|
@@ -244,15 +244,22 @@ export class AppExtensionService implements RuleContext {
|
||||
.filter(child => this.filterVisible(child))
|
||||
.sort(sortByOrder)
|
||||
.map(child => {
|
||||
const childRouteRef = this.extensions.getRouteById(
|
||||
child.route
|
||||
);
|
||||
const childUrl = `/${
|
||||
childRouteRef ? childRouteRef.path : child.route
|
||||
}`;
|
||||
if (!child.click) {
|
||||
const childRouteRef = this.extensions.getRouteById(
|
||||
child.route
|
||||
);
|
||||
const childUrl = `/${
|
||||
childRouteRef ? childRouteRef.path : child.route
|
||||
}`;
|
||||
return {
|
||||
...child,
|
||||
url: childUrl
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...child,
|
||||
url: childUrl
|
||||
action: child.click
|
||||
};
|
||||
});
|
||||
|
||||
@@ -261,11 +268,18 @@ export class AppExtensionService implements RuleContext {
|
||||
};
|
||||
}
|
||||
|
||||
const routeRef = this.extensions.getRouteById(item.route);
|
||||
const url = `/${routeRef ? routeRef.path : item.route}`;
|
||||
if (!item.click) {
|
||||
const routeRef = this.extensions.getRouteById(item.route);
|
||||
const url = `/${routeRef ? routeRef.path : item.route}`;
|
||||
return {
|
||||
...item,
|
||||
url
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
url
|
||||
action: item.click
|
||||
};
|
||||
})
|
||||
.reduce(reduceEmptyMenus, [])
|
||||
|
Reference in New Issue
Block a user