Skip to content

Commit

Permalink
feat(abc:cell): add cell component (#1530)
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk authored Aug 28, 2023
1 parent 5d9bdef commit 26023ca
Show file tree
Hide file tree
Showing 37 changed files with 2,031 additions and 31 deletions.
4 changes: 4 additions & 0 deletions _mock/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const USERS = {
for (let i = 0; i < +req.queryString.ps; i++) {
res.list.push({
id: i + 1,
type: r(1, 3),
picture: {
thumbnail: `https://randomuser.me/api/portraits/thumb/${r(0, 1) === 0 ? 'men' : 'women'}/${r(1, 50)}.jpg`
},
Expand All @@ -27,6 +28,9 @@ export const USERS = {
email: `aaa${r(1, 10)}@qq.com`,
phone: `phone-${r(1000, 100000)}`,
price: r(10, 10000000),
total: r(10, 10000000),
website: `https://${r(10, 10000000)}.com/`,
disabled: r(1, 100) > 50,
registered: new Date()
});
}
Expand Down
33 changes: 33 additions & 0 deletions packages/abc/cell/cell-host.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Directive, Input, OnInit, Type, ViewContainerRef } from '@angular/core';

import { warn } from '@delon/util/other';

import { CellService } from './cell.service';
import { CellWidgetData } from './cell.types';

@Directive({
selector: '[cell-widget-host]'
})
export class CellHostDirective implements OnInit {
@Input() data!: CellWidgetData;

constructor(
private srv: CellService,
private viewContainerRef: ViewContainerRef
) {}

ngOnInit(): void {
const widget = this.data.options!.widget!;
const componentType = this.srv.getWidget(widget.key!)?.ref as Type<unknown>;
if (componentType == null) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
warn(`cell: No widget for type "${widget.key}"`);
}
return;
}

this.viewContainerRef.clear();
const componentRef = this.viewContainerRef.createComponent(componentType);
(componentRef.instance as { data: CellWidgetData }).data = this.data;
}
}
217 changes: 217 additions & 0 deletions packages/abc/cell/cell.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Inject,
Input,
OnChanges,
OnDestroy,
Output,
Renderer2,
SimpleChange,
ViewEncapsulation
} from '@angular/core';
import type { SafeValue } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';

import { updateHostClass } from '@delon/util/browser';
import { BooleanInput, InputBoolean } from '@delon/util/decorator';
import { WINDOW } from '@delon/util/token';
import type { NzSafeAny } from 'ng-zorro-antd/core/types';
import { NzImage, NzImageService } from 'ng-zorro-antd/image';

import { CellService } from './cell.service';
import type { CellDefaultText, CellOptions, CellTextResult, CellValue, CellWidgetData } from './cell.types';

@Component({
selector: 'cell, [cell]',
template: `
<ng-template #text>
<ng-container [ngSwitch]="safeOpt.type">
<label
*ngSwitchCase="'checkbox'"
nz-checkbox
[nzDisabled]="disabled"
[ngModel]="value"
(ngModelChange)="change($event)"
>
{{ safeOpt.checkbox?.label }}
</label>
<label
*ngSwitchCase="'radio'"
nz-radio
[nzDisabled]="disabled"
[ngModel]="value"
(ngModelChange)="change($event)"
>
{{ safeOpt.radio?.label }}
</label>
<a
*ngSwitchCase="'link'"
(click)="_link($event)"
[attr.target]="safeOpt.link?.target"
[attr.title]="value"
[innerHTML]="_text"
></a>
<nz-tag *ngSwitchCase="'tag'" [nzColor]="res?.result?.color">
<span [innerHTML]="_text"></span>
</nz-tag>
<nz-badge *ngSwitchCase="'badge'" [nzStatus]="res?.result?.color" nzText="{{ _text }}" />
<ng-template *ngSwitchCase="'widget'" cell-widget-host [data]="hostData" />
<ng-container *ngSwitchCase="'img'">
<img
*ngFor="let i of $any(_text)"
[attr.src]="i"
[attr.height]="safeOpt.img?.size"
[attr.width]="safeOpt.img?.size"
(click)="_showImg(i)"
class="img"
[class.point]="safeOpt.img?.big"
/>
</ng-container>
<ng-container *ngSwitchDefault>
<span *ngIf="!isText" [innerHTML]="_text" [attr.title]="value"></span>
<span *ngIf="isText" [innerText]="_text" [attr.title]="value"></span>
<span *ngIf="_unit" class="unit">{{ _unit }}</span>
</ng-container>
</ng-container>
</ng-template>
<ng-template #textWrap>
<ng-container *ngIf="showDefault">{{ safeOpt.default?.text }}</ng-container>
<ng-container *ngIf="!showDefault">
<span *ngIf="safeOpt.tooltip; else text" [nz-tooltip]="safeOpt.tooltip">
<ng-template [ngTemplateOutlet]="text" />
</span>
</ng-container>
</ng-template>
<span *ngIf="loading; else textWrap" nz-icon nzType="loading"></span>
`,
exportAs: 'cell',
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
export class CellComponent implements OnChanges, OnDestroy {
static ngAcceptInputType_loading: BooleanInput;
static ngAcceptInputType_disabled: BooleanInput;

private destroy$?: Subscription;

_text!: string | SafeValue | string[] | number;
_unit?: string;
res?: CellTextResult;
showDefault = false;

@Input() value?: CellValue;
@Output() readonly valueChange = new EventEmitter<NzSafeAny>();
@Input() options?: CellOptions;
@Input() @InputBoolean() loading = false;
@Input() @InputBoolean() disabled = false;

get safeOpt(): CellOptions {
return this.res?.options ?? {};
}

get isText(): boolean {
return this.res?.safeHtml === 'text';
}

get hostData(): CellWidgetData {
return {
value: this.value,
options: this.srv.fixOptions(this.options)
};
}

constructor(
private srv: CellService,
private router: Router,
private cdr: ChangeDetectorRef,
private el: ElementRef<HTMLElement>,
private renderer: Renderer2,
private imgSrv: NzImageService,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@Inject(WINDOW) private win: any
) {}

private updateValue(): void {
this.destroy$?.unsubscribe();
this.destroy$ = this.srv.get(this.value, this.options).subscribe(res => {
this.res = res;
this.showDefault = this.value == (this.safeOpt.default as CellDefaultText).condition;
this._text = res.result?.text ?? '';
this._unit = res.result?.unit ?? this.safeOpt?.unit;
this.cdr.detectChanges();
this.setClass();
});
}

private setClass(): void {
const { el, renderer } = this;
const { renderType, size } = this.safeOpt;
updateHostClass(el.nativeElement, renderer, {
[`cell`]: true,
[`cell__${renderType}`]: renderType != null,
[`cell__${size}`]: size != null,
[`cell__has-unit`]: this._unit,
[`cell__has-default`]: this.showDefault,
[`cell__disabled`]: this.disabled
});
el.nativeElement.dataset.type = this.safeOpt.type;
}

ngOnChanges(changes: { [p in keyof CellComponent]?: SimpleChange }): void {
// Do not call updateValue when only updating loading, disabled
if (Object.keys(changes).every(k => ['loading', 'disabled'].includes(k))) {
this.setClass();
} else {
this.updateValue();
}
}

change(value: NzSafeAny): void {
this.value = value;
this.valueChange.emit(value);
}

_link(e: Event): void {
e.preventDefault();
e.stopPropagation();

if (this.disabled) return;

const link = this.safeOpt.link;
const url = link?.url;
if (url == null) return;

if (/https?:\/\//g.test(url)) {
(this.win as Window).open(url, link?.target);
} else {
this.router.navigateByUrl(url);
}
}

_showImg(img: string): void {
const config = this.safeOpt.img;
if (config == null || config.big == null) return;

let idx = -1;
const list = (this._text as string[]).map((p, index) => {
if (idx === -1 && p === img) idx = index;
return typeof config.big === 'function' ? config.big(p) : p;
});
this.imgSrv
.preview(
list.map(p => ({ src: p }) as NzImage),
config.previewOptions
)
.switchTo(idx);
}

ngOnDestroy(): void {
this.destroy$?.unsubscribe();
}
}
33 changes: 33 additions & 0 deletions packages/abc/cell/cell.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { NzBadgeModule } from 'ng-zorro-antd/badge';
import { NzCheckboxModule } from 'ng-zorro-antd/checkbox';
import { NzImageModule } from 'ng-zorro-antd/experimental/image';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { NzRadioModule } from 'ng-zorro-antd/radio';
import { NzTagModule } from 'ng-zorro-antd/tag';
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';

import { CellHostDirective } from './cell-host.directive';
import { CellComponent } from './cell.component';

const COMPS = [CellComponent];

@NgModule({
imports: [
CommonModule,
FormsModule,
NzCheckboxModule,
NzRadioModule,
NzBadgeModule,
NzTagModule,
NzToolTipModule,
NzIconModule,
NzImageModule
],
declarations: [...COMPS, CellHostDirective],
exports: COMPS
})
export class CellModule {}
Loading

0 comments on commit 26023ca

Please sign in to comment.