Skip to content

Commit

Permalink
feat(theme:default): add LayoutDefaultService service (#1561)
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk authored Dec 4, 2022
1 parent 41fbae0 commit bf90034
Show file tree
Hide file tree
Showing 19 changed files with 279 additions and 73 deletions.
12 changes: 6 additions & 6 deletions packages/theme/layout-default/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class LayoutBasicComponent {
}
```

In addition, in layout operations, you can subscribe to layout changes through `SettingsService.notify` (for example: sidebar show and hide, etc.). Note that all layout-related changes will pass through this interface, so you need to do `filter` operation.
The layout can be dynamically managed at runtime through the `LayoutDefaultService` service. In addition, in layout operations, you can subscribe to layout changes through `SettingsService.notify` (for example: sidebar show and hide, etc.). Note that all layout-related changes will pass through this interface, so you need to do `filter` operation.

## API

Expand All @@ -98,6 +98,7 @@ In addition, in layout operations, you can subscribe to layout changes through `
|----------|-------------|------|---------|
| `[options]` | Options of the layout | `LayoutDefaultOptions` | `-` |
| `[asideUser]` | Side user of the layout | `TemplateRef<void>` | `-` |
| `[asideBottom]` | Bottom information of the layout | `TemplateRef<void>` | `-` |
| `[nav]` | Nav | `TemplateRef<void>` | `-` |
| `[content]` | Content | `TemplateRef<void>` | `-` |
| `[customError]` | Custom exception routing error message, can't show when is `null` | `string, null` | `Could not load ${evt.url} route` |
Expand All @@ -112,6 +113,9 @@ In addition, in layout operations, you can subscribe to layout changes through `
| `[logoFixWidth]` | Specify a fixed logo width | `number` | - |
| `[logoLink]` | Specify the logo routing address | `string` | `/` |
| `[hideAside]` | Hide the sidebar without showing the collapsed icon button | `boolean` | `false` |
| `[hideHeader]` | Hide top bar | `boolean` | `false` |
| `[showHeaderCollapse]` | Whether to display the menu collapse button on the top bar | `boolean` | `true` |
| `[showSiderCollapse]` | Whether to show the menu collapse button at the bottom of the sidebar | `boolean` | `false` |

### layout-default-nav

Expand Down Expand Up @@ -239,8 +243,4 @@ The menu will be re-rendered via calling `MenuService.setItem(key, newValue)`, p

**How to control menu expand**

Use `SettingsService.setLayout` to operate on `collapsed`, for example:

```ts
SettingsService.setLayout('collapsed', status);
````
Use `LayoutDefaultService.toggleCollapsed()` for manual control at runtime.
12 changes: 6 additions & 6 deletions packages/theme/layout-default/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class LayoutBasicComponent {
}
```

除此之外,在布局的操作都可以通过 `SettingsService.notify` 来订阅布局的变化(例如:侧边栏的展开与收缩等),注意所有布局相关的变化都会通过这个接口,所以需要做好 `filter` 操作。
通过 `LayoutDefaultService` 服务可以在运行时动态管理布局。除此之外,在布局的操作都可以通过 `SettingsService.notify` 来订阅布局的变化(例如:侧边栏的展开与收缩等),注意所有布局相关的变化都会通过这个接口,所以需要做好 `filter` 操作。

## API

Expand All @@ -98,6 +98,7 @@ export class LayoutBasicComponent {
|----|----|----|-----|
| `[options]` | 选项 | `LayoutDefaultOptions` | `-` |
| `[asideUser]` | 侧边用户信息 | `TemplateRef<void>` | `-` |
| `[asideBottom]` | 侧边底部信息 | `TemplateRef<void>` | `-` |
| `[nav]` | 导航信息 | `TemplateRef<void>` | `-` |
| `[content]` | 内容信息 | `TemplateRef<void>` | `-` |
| `[customError]` | 自定义异常路由错误消息,当 `null` 时表示不显示错误消息 | `string, null` | `Could not load ${evt.url} route` |
Expand All @@ -112,6 +113,9 @@ export class LayoutBasicComponent {
| `[logoFixWidth]` | 指定固定 Logo 宽度 | `number` | - |
| `[logoLink]` | 指定 Logo 路由地址 | `string` | `/` |
| `[hideAside]` | 隐藏侧边栏,同时不显收缩图标按钮 | `boolean` | `false` |
| `[hideHeader]` | 隐藏顶栏 | `boolean` | `false` |
| `[showHeaderCollapse]` | 是否在顶栏显示菜单折叠按钮 | `boolean` | `true` |
| `[showSiderCollapse]` | 是否在侧边栏底部显示菜单折叠按钮 | `boolean` | `false` |

### layout-default-nav

Expand Down Expand Up @@ -239,8 +243,4 @@ export class LayoutBasicComponent {

**如何控制菜单展开**

利用 `SettingsService.setLayout``collapsed` 进行操作,例如:

```ts
SettingsService.setLayout('collapsed', status);
```
利用 `LayoutDefaultService.toggleCollapsed()` 来运行时手动控制。
31 changes: 16 additions & 15 deletions packages/theme/layout-default/layout-header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { App, SettingsService } from '@delon/theme';
import type { NzSafeAny } from 'ng-zorro-antd/core/types';

import { LayoutDefaultHeaderItemComponent } from './layout-header-item.component';
import { LayoutDefaultService } from './layout.service';
import { LayoutDefaultHeaderItemDirection, LayoutDefaultHeaderItemHidden, LayoutDefaultOptions } from './types';

interface LayoutDefaultHeaderItem {
Expand All @@ -30,19 +31,19 @@ interface LayoutDefaultHeaderItem {
<ng-container *ngTemplateOutlet="i.host"></ng-container>
</li>
</ng-template>
<div class="alain-default__header-logo" [style.width.px]="options.logoFixWidth">
<ng-container *ngIf="!options.logo; else options.logo!">
<a [routerLink]="options.logoLink" class="alain-default__header-logo-link">
<img class="alain-default__header-logo-expanded" [attr.src]="options.logoExpanded" [attr.alt]="app.name" />
<img class="alain-default__header-logo-collapsed" [attr.src]="options.logoCollapsed" [attr.alt]="app.name" />
<div class="alain-default__header-logo" [style.width.px]="opt.logoFixWidth">
<ng-container *ngIf="!opt.logo; else opt.logo!">
<a [routerLink]="opt.logoLink" class="alain-default__header-logo-link">
<img class="alain-default__header-logo-expanded" [attr.src]="opt.logoExpanded" [attr.alt]="app.name" />
<img class="alain-default__header-logo-collapsed" [attr.src]="opt.logoCollapsed" [attr.alt]="app.name" />
</a>
</ng-container>
</div>
<div class="alain-default__nav-wrap">
<ul class="alain-default__nav">
<li *ngIf="!options.hideAside">
<li *ngIf="!opt.hideAside && opt.showHeaderCollapse">
<div class="alain-default__nav-item alain-default__nav-item--collapse" (click)="toggleCollapsed()">
<i nz-icon [nzType]="collapsedIcon"></i>
<span nz-icon [nzType]="collapsedIcon"></span>
</div>
</li>
<ng-template [ngTemplateOutlet]="render" [ngTemplateOutletContext]="{ $implicit: left }"></ng-template>
Expand All @@ -64,12 +65,15 @@ export class LayoutDefaultHeaderComponent implements AfterViewInit, OnDestroy {
private destroy$ = new Subject<void>();

@Input() items!: QueryList<LayoutDefaultHeaderItemComponent>;
@Input() options!: LayoutDefaultOptions;

left: LayoutDefaultHeaderItem[] = [];
middle: LayoutDefaultHeaderItem[] = [];
right: LayoutDefaultHeaderItem[] = [];

get opt(): LayoutDefaultOptions {
return this.srv.options;
}

get app(): App {
return this.settings.app;
}
Expand All @@ -79,14 +83,10 @@ export class LayoutDefaultHeaderComponent implements AfterViewInit, OnDestroy {
}

get collapsedIcon(): string {
let type = this.collapsed ? 'unfold' : 'fold';
if (this.settings.layout.direction === 'rtl') {
type = this.collapsed ? 'fold' : 'unfold';
}
return `menu-${type}`;
return this.srv.collapsedIcon;
}

constructor(private settings: SettingsService, private cdr: ChangeDetectorRef) {}
constructor(private srv: LayoutDefaultService, private settings: SettingsService, private cdr: ChangeDetectorRef) {}

private refresh(): void {
const arr = this.items.toArray();
Expand All @@ -98,11 +98,12 @@ export class LayoutDefaultHeaderComponent implements AfterViewInit, OnDestroy {

ngAfterViewInit(): void {
this.items.changes.pipe(takeUntil(this.destroy$)).subscribe(() => this.refresh());
this.srv.options$.pipe(takeUntil(this.destroy$)).subscribe(() => this.cdr.detectChanges());
this.refresh();
}

toggleCollapsed(): void {
this.settings.setLayout('collapsed', !this.settings.layout.collapsed);
this.srv.toggleCollapsed();
}

ngOnDestroy(): void {
Expand Down
3 changes: 2 additions & 1 deletion packages/theme/layout-default/layout-nav.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const FLOATINGCLS = 'sidebar-nav__floating';
templateUrl: './layout-nav.component.html',
host: {
'(click)': '_click()',
'(document:click)': 'closeSubMenu()'
'(document:click)': 'closeSubMenu()',
'[class.d-block]': `true`
},
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush,
Expand Down
13 changes: 6 additions & 7 deletions packages/theme/layout-default/layout.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { SettingsService } from '../src/services/settings/settings.service';
import { AlainThemeModule } from '../src/theme.module';
import { LayoutDefaultComponent } from './layout.component';
import { LayoutDefaultModule } from './layout.module';
import { LayoutDefaultService } from './layout.service';
import { LayoutDefaultOptions } from './types';

describe('theme: layout-default', () => {
Expand Down Expand Up @@ -50,16 +51,14 @@ describe('theme: layout-default', () => {
});

it('should be toggle collapsed', () => {
const srv = TestBed.inject(SettingsService);
let collapsed = false;
spyOnProperty(srv, 'layout').and.returnValue({ collapsed });
const srv = TestBed.inject(LayoutDefaultService);
srv.toggleCollapsed(true);
fixture.detectChanges();
const el = page.getEl('.alain-default__nav-item--collapse');
expect(el.querySelector('.anticon-menu-fold') != null).toBe(true);
collapsed = true;
el.click();
fixture.detectChanges();
expect(el.querySelector('.anticon-menu-unfold') != null).toBe(true);
srv.toggleCollapsed(false);
fixture.detectChanges();
expect(el.querySelector('.anticon-menu-fold') != null).toBe(true);
});

it('#colorWeak', () => {
Expand Down
69 changes: 45 additions & 24 deletions packages/theme/layout-default/layout.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
Inject,
Input,
OnDestroy,
OnInit,
QueryList,
Renderer2,
TemplateRef
Expand All @@ -28,19 +27,29 @@ import type { NzSafeAny } from 'ng-zorro-antd/core/types';
import { NzMessageService } from 'ng-zorro-antd/message';

import { LayoutDefaultHeaderItemComponent } from './layout-header-item.component';
import { LayoutDefaultService } from './layout.service';
import { LayoutDefaultOptions } from './types';

@Component({
selector: 'layout-default',
exportAs: 'layoutDefault',
template: `
<div class="alain-default__progress-bar" *ngIf="isFetching"></div>
<layout-default-header [options]="options" [items]="headerItems"></layout-default-header>
<div *ngIf="!options.hideAside" class="alain-default__aside">
<div class="alain-default__aside-inner">
<ng-container *ngTemplateOutlet="asideUser"></ng-container>
<ng-container *ngTemplateOutlet="nav"></ng-container>
<layout-default-nav *ngIf="!nav" class="d-block py-lg"></layout-default-nav>
<layout-default-header *ngIf="!opt.hideHeader" [items]="headerItems"></layout-default-header>
<div *ngIf="!opt.hideAside" class="alain-default__aside">
<div class="alain-default__aside-wrap">
<div class="alain-default__aside-inner">
<ng-container *ngTemplateOutlet="asideUser"></ng-container>
<ng-container *ngTemplateOutlet="nav"></ng-container>
<layout-default-nav *ngIf="!nav"></layout-default-nav>
</div>
<div *ngIf="opt.showSiderCollapse" class="alain-default__aside-link">
<ng-container *ngIf="asideBottom === null; else asideBottom">
<div class="alain-default__aside-link-collapsed" (click)="toggleCollapsed()">
<span nz-icon [nzType]="collapsedIcon"></span>
</div>
</ng-container>
</div>
</div>
</div>
<section class="alain-default__content">
Expand All @@ -49,28 +58,52 @@ import { LayoutDefaultOptions } from './types';
</section>
`
})
export class LayoutDefaultComponent implements OnInit, OnDestroy {
export class LayoutDefaultComponent implements OnDestroy {
@ContentChildren(LayoutDefaultHeaderItemComponent, { descendants: false })
headerItems!: QueryList<LayoutDefaultHeaderItemComponent>;

@Input() options!: LayoutDefaultOptions;
get opt(): LayoutDefaultOptions {
return this.srv.options;
}

@Input()
set options(value: LayoutDefaultOptions | null | undefined) {
this.srv.setOptions(value);
}
@Input() asideUser: TemplateRef<void> | null = null;
@Input() asideBottom: TemplateRef<NzSafeAny> | null = null;
@Input() nav: TemplateRef<void> | null = null;
@Input() content: TemplateRef<void> | null = null;
@Input() customError?: string | null;

private destroy$ = new Subject<void>();
isFetching = false;

get collapsed(): boolean {
return this.settings.layout.collapsed;
}

get collapsedIcon(): string {
return this.srv.collapsedIcon;
}

toggleCollapsed(): void {
this.srv.toggleCollapsed();
}

constructor(
router: Router,
private msgSrv: NzMessageService,
private settings: SettingsService,
private el: ElementRef,
private renderer: Renderer2,
@Inject(DOCUMENT) private doc: NzSafeAny
@Inject(DOCUMENT) private doc: NzSafeAny,
private srv: LayoutDefaultService
) {
router.events.pipe(takeUntil(this.destroy$)).subscribe(ev => this.processEv(ev));
const { destroy$ } = this;
this.srv.options$.pipe(takeUntil(destroy$)).subscribe(() => this.setClass());
this.settings.notify.pipe(takeUntil(destroy$)).subscribe(() => this.setClass());
}

processEv(ev: Event): void {
Expand Down Expand Up @@ -102,25 +135,13 @@ export class LayoutDefaultComponent implements OnInit, OnDestroy {
['alain-default']: true,
[`alain-default__fixed`]: layout.fixed,
[`alain-default__collapsed`]: layout.collapsed,
[`alain-default__hide-aside`]: this.options!.hideAside
[`alain-default__hide-aside`]: this.opt.hideAside,
[`alain-default__hide-header`]: this.opt.hideHeader
});

doc.body.classList[layout.colorWeak ? 'add' : 'remove']('color-weak');
}

ngOnInit(): void {
this.options = {
logoExpanded: `./assets/logo-full.svg`,
logoCollapsed: `./assets/logo.svg`,
logoLink: `/`,
hideAside: false,
...this.options
};
const { settings, destroy$ } = this;
settings.notify.pipe(takeUntil(destroy$)).subscribe(() => this.setClass());
this.setClass();
}

ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
Expand Down
40 changes: 40 additions & 0 deletions packages/theme/layout-default/layout.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { TestBed } from '@angular/core/testing';

import { AlainThemeModule, Layout, SettingsService } from '@delon/theme';

import { LayoutDefaultService } from './layout.service';

describe('theme: #LayoutDefaultService', () => {
let srv: LayoutDefaultService;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [AlainThemeModule]
});
srv = TestBed.inject(LayoutDefaultService);
});

it('should be working', () => {
srv.setOptions({ hideAside: true });
expect(srv.options.hideAside).toBe(true);
});

it('#toggleCollapsed', () => {
const settings = TestBed.inject(SettingsService);
(settings.layout as Layout).direction = 'ltr';
srv.toggleCollapsed(true);
expect(srv.collapsedIcon).toBe('menu-unfold');
srv.toggleCollapsed(false);
expect(srv.collapsedIcon).toBe('menu-fold');
srv.toggleCollapsed();
expect(srv.collapsedIcon).toBe('menu-unfold');
srv.toggleCollapsed();
expect(srv.collapsedIcon).toBe('menu-fold');
// when is direction
(settings.layout as Layout).direction = 'rtl';
srv.toggleCollapsed(true);
expect(srv.collapsedIcon).toBe('menu-fold');
srv.toggleCollapsed(false);
expect(srv.collapsedIcon).toBe('menu-unfold');
});
});
Loading

0 comments on commit bf90034

Please sign in to comment.