From abe244fed9da1ec38cf463356e5095ff3314732d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Popovics=20Andr=C3=A1s?= Date: Sat, 24 Mar 2018 07:07:34 +0000 Subject: [PATCH] [ADF-2559] Sidenav layout extraction (#253) * First step of extraction * Add content also and fix initial mobile screen issue * Move ADF sidenav-layout to different folder for easier future removal * Test the new behaviour * test fix * minor fixes --- .gitignore | 1 + .vscode/settings.json | 5 + src/app/app.module.ts | 11 +- .../{layout => adf-layout}/animations.ts | 0 .../app-layout-content.directive.ts | 31 ++ .../adf-layout/app-layout-header.directive.ts | 31 ++ .../app-layout-navigation.directive.ts | 31 ++ .../layout-container.component.html | 4 +- .../layout-container.component.scss | 0 .../adf-layout/layout-container.component.ts | 113 ++++++++ .../adf-layout/sidenav-layout.component.html | 23 ++ .../adf-layout/sidenav-layout.component.scss | 12 + .../sidenav-layout.component.spec.ts | 269 ++++++++++++++++++ .../adf-layout/sidenav-layout.component.ts | 107 +++++++ .../layout/layout-container.component.ts | 105 ------- .../components/layout/layout.component.html | 28 +- .../components/layout/layout.component.scss | 4 - src/app/components/layout/layout.component.ts | 2 +- 18 files changed, 651 insertions(+), 126 deletions(-) create mode 100644 .vscode/settings.json rename src/app/components/{layout => adf-layout}/animations.ts (100%) create mode 100644 src/app/components/adf-layout/app-layout-content.directive.ts create mode 100644 src/app/components/adf-layout/app-layout-header.directive.ts create mode 100644 src/app/components/adf-layout/app-layout-navigation.directive.ts rename src/app/components/{layout => adf-layout}/layout-container.component.html (81%) rename src/app/components/{layout => adf-layout}/layout-container.component.scss (100%) create mode 100644 src/app/components/adf-layout/layout-container.component.ts create mode 100644 src/app/components/adf-layout/sidenav-layout.component.html create mode 100644 src/app/components/adf-layout/sidenav-layout.component.scss create mode 100644 src/app/components/adf-layout/sidenav-layout.component.spec.ts create mode 100644 src/app/components/adf-layout/sidenav-layout.component.ts delete mode 100644 src/app/components/layout/layout-container.component.ts diff --git a/.gitignore b/.gitignore index 455f8050e..c5d885dad 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +.history # misc /.sass-cache diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..acadd47de --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "sidenav" + ] +} \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4e9e807b7..3f799a26d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -44,7 +44,6 @@ import { RecentFilesComponent } from './components/recent-files/recent-files.com import { SharedFilesComponent } from './components/shared-files/shared-files.component'; import { TrashcanComponent } from './components/trashcan/trashcan.component'; import { LayoutComponent } from './components/layout/layout.component'; -import { LayoutContainerComponent } from './components/layout/layout-container.component'; import { HeaderComponent } from './components/header/header.component'; import { CurrentUserComponent } from './components/current-user/current-user.component'; import { SearchComponent } from './components/search/search.component'; @@ -66,6 +65,12 @@ import { ContentManagementService } from './common/services/content-management.s import { NodeActionsService } from './common/services/node-actions.service'; import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInputModule } from '@angular/material'; +import { LayoutContainerComponent } from './components/adf-layout/layout-container.component'; +import { SidenavLayoutComponent } from './components/adf-layout/sidenav-layout.component'; +import { AppLayoutHeaderDirective } from './components/adf-layout/app-layout-header.directive'; +import { AppLayoutNavigationDirective } from './components/adf-layout/app-layout-navigation.directive'; +import { AppLayoutContentDirective } from './components/adf-layout/app-layout-content.directive'; + @NgModule({ imports: [ BrowserModule, @@ -90,6 +95,10 @@ import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInpu LoginComponent, LayoutComponent, LayoutContainerComponent, + SidenavLayoutComponent, + AppLayoutHeaderDirective, + AppLayoutNavigationDirective, + AppLayoutContentDirective, HeaderComponent, CurrentUserComponent, SearchComponent, diff --git a/src/app/components/layout/animations.ts b/src/app/components/adf-layout/animations.ts similarity index 100% rename from src/app/components/layout/animations.ts rename to src/app/components/adf-layout/animations.ts diff --git a/src/app/components/adf-layout/app-layout-content.directive.ts b/src/app/components/adf-layout/app-layout-content.directive.ts new file mode 100644 index 000000000..3a5f8b235 --- /dev/null +++ b/src/app/components/adf-layout/app-layout-content.directive.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Directive } from '@angular/core'; + +@Directive({ + selector: '[appLayoutContent]' +}) +export class AppLayoutContentDirective {} diff --git a/src/app/components/adf-layout/app-layout-header.directive.ts b/src/app/components/adf-layout/app-layout-header.directive.ts new file mode 100644 index 000000000..218a9d637 --- /dev/null +++ b/src/app/components/adf-layout/app-layout-header.directive.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Directive } from '@angular/core'; + +@Directive({ + selector: '[appLayoutHeader]' +}) +export class AppLayoutHeaderDirective {} diff --git a/src/app/components/adf-layout/app-layout-navigation.directive.ts b/src/app/components/adf-layout/app-layout-navigation.directive.ts new file mode 100644 index 000000000..dd2ee8b31 --- /dev/null +++ b/src/app/components/adf-layout/app-layout-navigation.directive.ts @@ -0,0 +1,31 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Directive } from '@angular/core'; + +@Directive({ + selector: '[appLayoutNavigation]' +}) +export class AppLayoutNavigationDirective {} diff --git a/src/app/components/layout/layout-container.component.html b/src/app/components/adf-layout/layout-container.component.html similarity index 81% rename from src/app/components/layout/layout-container.component.html rename to src/app/components/adf-layout/layout-container.component.html index 7eda7146f..83856bbd3 100644 --- a/src/app/components/layout/layout-container.component.html +++ b/src/app/components/adf-layout/layout-container.component.html @@ -2,8 +2,8 @@ + [opened]="!isMobileScreenSize" + [mode]="isMobileScreenSize ? 'over' : 'side'"> diff --git a/src/app/components/layout/layout-container.component.scss b/src/app/components/adf-layout/layout-container.component.scss similarity index 100% rename from src/app/components/layout/layout-container.component.scss rename to src/app/components/adf-layout/layout-container.component.scss diff --git a/src/app/components/adf-layout/layout-container.component.ts b/src/app/components/adf-layout/layout-container.component.ts new file mode 100644 index 000000000..e9dec5f7d --- /dev/null +++ b/src/app/components/adf-layout/layout-container.component.ts @@ -0,0 +1,113 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Component, Input, ViewChild, OnInit, OnDestroy } from '@angular/core'; +import { MatSidenav } from '@angular/material'; +import { sidenavAnimation, contentAnimation } from './animations'; + +@Component({ + selector: 'app-layout-container', + templateUrl: './layout-container.component.html', + styleUrls: ['./layout-container.component.scss'], + animations: [ sidenavAnimation, contentAnimation ] +}) +export class LayoutContainerComponent implements OnInit, OnDestroy { + @Input() + sidenavMin: number; + + @Input() + sidenavMax: number; + + @Input() + mediaQueryList: MediaQueryList; + + @Input() + hideSidenav = false; + + @ViewChild(MatSidenav) sidenav: MatSidenav; + + sidenavAnimationState: any; + contentAnimationState: any; + + SIDENAV_STATES = { EXPANDED: {}, COMPACT: {} }; + CONTENT_STATES = { MOBILE: {}, EXPANDED: {}, COMPACT: {} }; + + constructor() { + this.onMediaQueryChange = this.onMediaQueryChange.bind(this); + } + + ngOnInit() { + this.SIDENAV_STATES.EXPANDED = { value: 'expanded', params: { width: this.sidenavMax } }; + this.SIDENAV_STATES.COMPACT = { value: 'compact', params: {width: this.sidenavMin } }; + this.CONTENT_STATES.MOBILE = { value: 'expanded', params: { marginLeft: 0 } }; + this.CONTENT_STATES.EXPANDED = { value: 'expanded', params: { marginLeft: this.sidenavMin } }; + this.CONTENT_STATES.COMPACT = { value: 'compact', params: { marginLeft: this.sidenavMax } }; + + this.mediaQueryList.addListener(this.onMediaQueryChange); + + this.sidenavAnimationState = this.SIDENAV_STATES.EXPANDED; + this.contentAnimationState = this.isMobileScreenSize ? this.CONTENT_STATES.MOBILE : this.CONTENT_STATES.COMPACT; + } + + ngOnDestroy(): void { + this.mediaQueryList.removeListener(this.onMediaQueryChange); + } + + toggleMenu(): void { + if (this.isMobileScreenSize) { + this.sidenav.toggle(); + } else { + this.sidenavAnimationState = this.toggledSidenavAnimation; + this.contentAnimationState = this.toggledContentAnimation; + } + } + + private get toggledSidenavAnimation() { + return this.sidenavAnimationState === this.SIDENAV_STATES.EXPANDED + ? this.SIDENAV_STATES.COMPACT + : this.SIDENAV_STATES.EXPANDED; + } + + private get toggledContentAnimation() { + if (this.isMobileScreenSize) { + return this.CONTENT_STATES.MOBILE; + } + + if (this.sidenavAnimationState === this.SIDENAV_STATES.EXPANDED) { + return this.CONTENT_STATES.COMPACT; + } else { + return this.CONTENT_STATES.EXPANDED; + } + } + + get isMobileScreenSize(): boolean { + return this.mediaQueryList.matches; + } + + private onMediaQueryChange() { + this.sidenavAnimationState = this.SIDENAV_STATES.EXPANDED; + this.contentAnimationState = this.toggledContentAnimation; + } +} diff --git a/src/app/components/adf-layout/sidenav-layout.component.html b/src/app/components/adf-layout/sidenav-layout.component.html new file mode 100644 index 000000000..b807a65dd --- /dev/null +++ b/src/app/components/adf-layout/sidenav-layout.component.html @@ -0,0 +1,23 @@ +
+ + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/src/app/components/adf-layout/sidenav-layout.component.scss b/src/app/components/adf-layout/sidenav-layout.component.scss new file mode 100644 index 000000000..5247cb8e7 --- /dev/null +++ b/src/app/components/adf-layout/sidenav-layout.component.scss @@ -0,0 +1,12 @@ +:host { + display: flex; + flex: 1; + + .sidenav-layout { + width: 100%; + } + + router-outlet { + flex: 0 0; + } +} diff --git a/src/app/components/adf-layout/sidenav-layout.component.spec.ts b/src/app/components/adf-layout/sidenav-layout.component.spec.ts new file mode 100644 index 000000000..6932ccff8 --- /dev/null +++ b/src/app/components/adf-layout/sidenav-layout.component.spec.ts @@ -0,0 +1,269 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*tslint:disable: ban*/ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { SidenavLayoutComponent } from './sidenav-layout.component'; +import { Component, Input } from '@angular/core'; +import { LayoutModule, MediaMatcher } from '@angular/cdk/layout'; +import { PlatformModule } from '@angular/cdk/platform'; +import { AppLayoutNavigationDirective } from './app-layout-navigation.directive'; +import { AppLayoutHeaderDirective } from './app-layout-header.directive'; +import { AppLayoutContentDirective } from './app-layout-content.directive'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-layout-container', + template: ` + + ` +}) +export class DummyLayoutContainerComponent { + @Input() sidenavMin: number; + @Input() sidenavMax: number; + @Input() mediaQueryList: MediaQueryList; + @Input() hideSidenav: boolean; + toggleMenu () {} +} + +describe('SidenavLayoutComponent', () => { + + let fixture: ComponentFixture, + mediaMatcher: MediaMatcher, + mediaQueryList: any; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CommonModule, + PlatformModule, + LayoutModule + ], + declarations: [ + DummyLayoutContainerComponent, + SidenavLayoutComponent, + AppLayoutNavigationDirective, + AppLayoutHeaderDirective, + AppLayoutContentDirective + ], + providers: [ + MediaMatcher + ] + }); + })); + + beforeEach(() => { + mediaQueryList = { + matches: false, + addListener: () => {}, + removeListener: () => {} + }; + }); + + afterEach(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + }); + + describe('Template transclusion', () => { + + @Component({ + selector: 'app-test-component-for-sidenav', + template: ` + + + +
+
+ + + + + + +
+
+
` + }) + class SidenavLayoutTesterComponent {} + + beforeEach(async(() => { + TestBed.configureTestingModule({ declarations: [ SidenavLayoutTesterComponent ] }).compileComponents(); + })); + + beforeEach(() => { + mediaMatcher = TestBed.get(MediaMatcher); + spyOn(mediaMatcher, 'matchMedia').and.returnValue(mediaQueryList); + + fixture = TestBed.createComponent(SidenavLayoutTesterComponent); + fixture.detectChanges(); + }); + + describe('appLayoutNavigation', () => { + + const injectedElementSelector = By.css('[data-automation-id="adf-layout-container"] #nav-test'); + + it('should contain the transcluded side navigation template', () => { + const injectedElement = fixture.debugElement.query(injectedElementSelector); + + expect(injectedElement === null).toBe(false); + }); + + it('should let the isMenuMinimized property of component to be accessed by the transcluded template', () => { + const injectedElement = fixture.debugElement.query(injectedElementSelector); + + expect(injectedElement.nativeElement.innerText.trim()).toBe('variable-is-injected'); + }); + }); + + describe('appLayoutHeader', () => { + + const outerHeaderSelector = By.css('.sidenav-layout > #header-test'), + innerHeaderSelector = By.css('.sidenav-layout [data-automation-id="adf-layout-container"] #header-test'); + + it('should contain the transcluded header template outside of the layout-container', () => { + mediaQueryList.matches = false; + fixture.detectChanges(); + const outerHeaderElement = fixture.debugElement.query(outerHeaderSelector); + const innerHeaderElement = fixture.debugElement.query(innerHeaderSelector); + + expect(outerHeaderElement === null).toBe(false, 'Outer header should be shown'); + expect(innerHeaderElement === null).toBe(true, 'Inner header should not be shown'); + }); + + it('should contain the transcluded header template inside of the layout-container', () => { + mediaQueryList.matches = true; + fixture.detectChanges(); + const outerHeaderElement = fixture.debugElement.query(outerHeaderSelector); + const innerHeaderElement = fixture.debugElement.query(innerHeaderSelector); + + expect(outerHeaderElement === null).toBe(true, 'Outer header should not be shown'); + expect(innerHeaderElement === null).toBe(false, 'Inner header should be shown'); + }); + + it('should call through the layout container\'s toggleMenu method', () => { + mediaQueryList.matches = false; + fixture.detectChanges(); + const layoutContainerComponent = fixture.debugElement.query(By.directive(DummyLayoutContainerComponent)).componentInstance; + spyOn(layoutContainerComponent, 'toggleMenu'); + + const outerHeaderElement = fixture.debugElement.query(outerHeaderSelector); + outerHeaderElement.triggerEventHandler('click', {}); + + expect(layoutContainerComponent.toggleMenu).toHaveBeenCalled(); + }); + }); + + describe('appLayoutContent', () => { + + const injectedElementSelector = By.css('[data-automation-id="adf-layout-container"] #content-test'); + + it('should contain the transcluded content template', () => { + const injectedElement = fixture.debugElement.query(injectedElementSelector); + + expect(injectedElement === null).toBe(false); + }); + }); + }); + + describe('General behaviour', () => { + + let component: SidenavLayoutComponent; + + beforeEach(async(() => { + TestBed.compileComponents(); + })); + + beforeEach(() => { + mediaMatcher = TestBed.get(MediaMatcher); + spyOn(mediaMatcher, 'matchMedia').and.callFake((mediaQuery) => { + mediaQueryList.originalMediaQueryPassed = mediaQuery; + spyOn(mediaQueryList, 'addListener').and.stub(); + spyOn(mediaQueryList, 'removeListener').and.stub(); + return mediaQueryList; + }); + + fixture = TestBed.createComponent(SidenavLayoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should pass through input parameters', () => { + component.sidenavMin = 1; + component.sidenavMax = 2; + component.hideSidenav = true; + fixture.detectChanges(); + + const layoutContainerComponent = fixture.debugElement.query(By.directive(DummyLayoutContainerComponent)).componentInstance; + + expect(layoutContainerComponent.sidenavMin).toBe(component.sidenavMin); + expect(layoutContainerComponent.sidenavMax).toBe(component.sidenavMax); + expect(layoutContainerComponent.hideSidenav).toBe(component.hideSidenav); + expect(layoutContainerComponent.mediaQueryList.originalMediaQueryPassed).toBe(`(max-width: 600px)`); + }); + + it('addListener of mediaQueryList should have been called', () => { + expect(mediaQueryList.addListener).toHaveBeenCalledTimes(1); + expect(mediaQueryList.addListener).toHaveBeenCalledWith(component.onMediaQueryChange); + }); + + it('addListener of mediaQueryList should have been called', () => { + fixture.destroy(); + + expect(mediaQueryList.removeListener).toHaveBeenCalledTimes(1); + expect(mediaQueryList.removeListener).toHaveBeenCalledWith(component.onMediaQueryChange); + }); + }); + + describe('toggleMenu', () => { + + let component; + + beforeEach(async(() => { + TestBed.compileComponents(); + })); + + beforeEach(() => { + mediaMatcher = TestBed.get(MediaMatcher); + spyOn(mediaMatcher, 'matchMedia').and.returnValue(mediaQueryList); + + fixture = TestBed.createComponent(SidenavLayoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should toggle the isMenuMinimized if the mediaQueryList.matches is false (we are on desktop)', () => { + mediaQueryList.matches = false; + component.isMenuMinimized = false; + + component.toggleMenu(); + + expect(component.isMenuMinimized).toBe(true); + }); + + it('should set the isMenuMinimized to false if the mediaQueryList.matches is true (we are on mobile)', () => { + mediaQueryList.matches = true; + component.isMenuMinimized = true; + + component.toggleMenu(); + + expect(component.isMenuMinimized).toBe(false); + }); + }); +}); diff --git a/src/app/components/adf-layout/sidenav-layout.component.ts b/src/app/components/adf-layout/sidenav-layout.component.ts new file mode 100644 index 000000000..fba62e4f5 --- /dev/null +++ b/src/app/components/adf-layout/sidenav-layout.component.ts @@ -0,0 +1,107 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { Component, ContentChild, Input, OnInit, TemplateRef, AfterViewInit, ViewChild, OnDestroy } from '@angular/core'; +import { MediaMatcher } from '@angular/cdk/layout'; +import { AppLayoutHeaderDirective } from './app-layout-header.directive'; +import { AppLayoutNavigationDirective } from './app-layout-navigation.directive'; +import { AppLayoutContentDirective } from './app-layout-content.directive'; + +@Component({ + selector: 'app-sidenav-layout', + templateUrl: './sidenav-layout.component.html', + styleUrls: ['./sidenav-layout.component.scss'] +}) +export class SidenavLayoutComponent implements OnInit, AfterViewInit, OnDestroy { + + static STEP_OVER = 600; + + @Input() + sidenavMin: number; + + @Input() + sidenavMax: number; + + @Input() + stepOver: number; + + @Input() + hideSidenav = false; + + @ContentChild(AppLayoutHeaderDirective, {read: TemplateRef}) + headerTemplate: TemplateRef; + + @ContentChild(AppLayoutNavigationDirective, {read: TemplateRef}) + navigationTemplate: TemplateRef; + + @ContentChild(AppLayoutContentDirective, {read: TemplateRef}) + contentTemplate: TemplateRef; + + @ViewChild('container') + container: any; + + mediaQueryList: MediaQueryList; + isMenuMinimized = false; + templateContext = { + toggleMenu: () => {}, + isMenuMinimized: () => this.isMenuMinimized + }; + + constructor(private mediaMatcher: MediaMatcher) { + this.onMediaQueryChange = this.onMediaQueryChange.bind(this); + } + + ngOnInit() { + const stepOver = this.stepOver || SidenavLayoutComponent.STEP_OVER; + this.mediaQueryList = this.mediaMatcher.matchMedia(`(max-width: ${stepOver}px)`); + this.mediaQueryList.addListener(this.onMediaQueryChange); + } + + ngAfterViewInit() { + this.templateContext.toggleMenu = this.toggleMenu.bind(this); + } + + ngOnDestroy(): void { + this.mediaQueryList.removeListener(this.onMediaQueryChange); + } + + toggleMenu() { + if (!this.mediaQueryList.matches) { + this.isMenuMinimized = !this.isMenuMinimized; + } else { + this.isMenuMinimized = false; + } + + this.container.toggleMenu(); + } + + get isHeaderInside() { + return this.mediaQueryList.matches; + } + + onMediaQueryChange() { + this.isMenuMinimized = false; + } +} diff --git a/src/app/components/layout/layout-container.component.ts b/src/app/components/layout/layout-container.component.ts deleted file mode 100644 index 4a696e940..000000000 --- a/src/app/components/layout/layout-container.component.ts +++ /dev/null @@ -1,105 +0,0 @@ -/*! - * @license - * Alfresco Example Content Application - * - * Copyright (C) 2005 - 2018 Alfresco Software Limited - * - * This file is part of the Alfresco Example Content Application. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * The Alfresco Example Content Application is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Alfresco Example Content Application is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ - -import { Component, Input, ViewChild, OnInit, OnDestroy } from '@angular/core'; -import { MatSidenav } from '@angular/material'; -import {MediaMatcher} from '@angular/cdk/layout'; -import { sidenavAnimation, contentAnimation } from './animations'; - -@Component({ - selector: 'app-layout-container', - templateUrl: './layout-container.component.html', - styleUrls: ['./layout-container.component.scss'], - animations: [ sidenavAnimation, contentAnimation ] -}) -export class LayoutContainerComponent implements OnInit, OnDestroy { - static STEP_OVER = 600; - - @Input() sidenavMin: number; - @Input() sidenavMax: number; - @Input() stepOver: number; - @Input() hideSidenav: boolean = false; - - @ViewChild(MatSidenav) sidenav: MatSidenav; - - sidenavAnimationState: any; - contentAnimationState: any; - isMenuMinimized = false; - mobileQuery: MediaQueryList; - - constructor(private mediaMatcher: MediaMatcher) { - this.mobileQueryListener = this.mobileQueryListener.bind(this); - } - - ngOnInit() { - const stepOver = this.stepOver || LayoutContainerComponent.STEP_OVER; - - this.mobileQuery = this.mediaMatcher.matchMedia(`(max-width: ${stepOver}px)`); - this.mobileQuery.addListener(this.mobileQueryListener); - this.sidenavAnimationState = { value: 'expanded', params: { width: this.sidenavMax } }; - this.contentAnimationState = { value: 'compact', params: {marginLeft: this.sidenavMax } }; - } - - ngOnDestroy(): void { - this.mobileQuery.removeListener(this.mobileQueryListener); - } - - toggleMenu(): void { - if (!this.mobileQuery.matches) { - this.isMenuMinimized = !this.isMenuMinimized; - this.sidenavAnimationState = this.sidenavAnimation(); - this.contentAnimationState = this.contentAnimation(); - } else { - this.isMenuMinimized = false; - this.sidenav.toggle(); - } - } - - sidenavAnimation() { - return this.sidenavAnimationState.value === 'expanded' - ? { value: 'compact', params: {width: this.sidenavMin } } - : { value: 'expanded', params: { width: this.sidenavMax } }; - } - - contentAnimation() { - if (this.mobileQuery.matches) { - return { value: 'expanded', params: { marginLeft: 0 } }; - } - - if (this.sidenavAnimationState.value === 'expanded') { - return { value: 'compact', params: { marginLeft: this.sidenavMax } }; - } - - if (this.sidenavAnimationState.value === 'compact') { - return { value: 'expanded', params: { marginLeft: this.sidenavMin } }; - } - } - - private mobileQueryListener() { - this.isMenuMinimized = false; - this.sidenavAnimationState = { value: 'expanded', params: { width: this.sidenavMax } }; - this.contentAnimationState = this.contentAnimation(); - } -} diff --git a/src/app/components/layout/layout.component.html b/src/app/components/layout/layout.component.html index b62133652..b3d7d2399 100644 --- a/src/app/components/layout/layout.component.html +++ b/src/app/components/layout/layout.component.html @@ -3,22 +3,24 @@ [parentId]="node?.id" [disabled]="!canCreateContent(node)"> - - - + [hideSidenav]="isPreview"> - - + + + - - + + + + + + + + diff --git a/src/app/components/layout/layout.component.scss b/src/app/components/layout/layout.component.scss index c3aa6941d..bfe4ce972 100644 --- a/src/app/components/layout/layout.component.scss +++ b/src/app/components/layout/layout.component.scss @@ -1,8 +1,4 @@ :host { display: flex; flex: 1; - - router-outlet { - flex: 0 0; - } } diff --git a/src/app/components/layout/layout.component.ts b/src/app/components/layout/layout.component.ts index a098e2d0e..411ec04f4 100644 --- a/src/app/components/layout/layout.component.ts +++ b/src/app/components/layout/layout.component.ts @@ -37,7 +37,7 @@ import { BrowsingFilesService } from '../../common/services/browsing-files.servi }) export class LayoutComponent implements OnInit, OnDestroy { node: MinimalNodeEntryEntity; - isPreview: boolean = false; + isPreview = false; private subscriptions: Subscription[] = [];