UNPKG

@yelon/abc

Version:

Common business components of ng-yunzai.

375 lines (368 loc) 18.8 kB
import { Directionality } from '@angular/cdk/bidi'; import { DOCUMENT, CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { inject, ElementRef, ChangeDetectorRef, EventEmitter, ViewChild, ViewEncapsulation, ChangeDetectionStrategy, Component, InjectionToken, ApplicationRef, createComponent, Injectable, NgModule } from '@angular/core'; import { Router } from '@angular/router'; import { of, switchMap, delay, pipe } from 'rxjs'; import { YelonLocaleService, YelonLocaleModule } from '@yelon/theme'; import { YunzaiConfigService } from '@yelon/util/config'; import { Platform } from '@angular/cdk/platform'; import { NzButtonComponent, NzButtonModule } from 'ng-zorro-antd/button'; import { NzNoAnimationDirective, NzNoAnimationModule } from 'ng-zorro-antd/core/no-animation'; import { NzStringTemplateOutletDirective, NzOutletModule } from 'ng-zorro-antd/core/outlet'; import { NzPopoverDirective, NzPopoverModule } from 'ng-zorro-antd/popover'; class OnboardingComponent { el = inject(ElementRef).nativeElement; platform = inject(Platform); cdr = inject(ChangeDetectorRef); doc = inject(DOCUMENT); time; prevSelectorEl; config; item; active = 0; max = 0; op = new EventEmitter(); running = false; dir = 'ltr'; popover; get first() { return this.active === 0; } get last() { return this.active === this.max - 1; } _getDoc() { return this.doc; } _getWin() { return this._getDoc().defaultView || window; } getLightData() { const doc = this._getDoc(); const win = this._getWin(); const el = doc.querySelector(this.item.selectors); if (!el) { return null; } const scrollTop = win.scrollY || doc.documentElement.scrollTop || doc.body.scrollTop; const scrollLeft = win.scrollX || doc.documentElement.scrollLeft || doc.body.scrollLeft; const rect = el.getBoundingClientRect(); const top = rect.top + scrollTop; const left = rect.left + scrollLeft; const padding = 8; const needPadding = top > padding && left > padding; const offsetPos = needPadding ? padding : 0; const offsetWH = needPadding ? padding * 2 : 0; return { top: top - offsetPos, left: left - offsetPos, width: rect.width + offsetWH, height: rect.height + offsetWH, el, clientWidth: doc.body.clientWidth, clientHeight: doc.body.clientHeight }; } ngAfterViewInit() { // Waiting https://github.com/NG-ZORRO/ng-zorro-antd/issues/6491 this.popover.component.onClickOutside = () => { }; } scroll(pos) { this.prevSelectorEl = pos.el; const scrollY = pos.top - (pos.clientHeight - pos.height) / 2; this._getWin().scrollTo({ top: scrollY }); this.updatePrevElStatus(true); } updateRunning(status) { this.running = status; this.cdr.detectChanges(); if (!status) { this.updatePosition(); } } updatePosition() { if (!this.platform.isBrowser) { return; } const pos = this.getLightData(); if (pos == null) { if (typeof ngDevMode === 'undefined' || ngDevMode) { console.warn(`Did not matches selectors [${this.item.selectors}]`); } return; } const lightStyle = this.el.querySelector('.onboarding__light').style; lightStyle.top = `${pos.top}px`; lightStyle.left = `${pos.left}px`; lightStyle.width = `${pos.width}px`; lightStyle.height = `${pos.height}px`; this.updatePrevElStatus(false); this.scroll(pos); } updatePrevElStatus(status) { if (this.prevSelectorEl) { this.prevSelectorEl.classList[status ? 'add' : 'remove']('onboarding__light-el'); } } to(type) { this.op.emit(type); } handleMask() { if (this.config.maskClosable === true) { this.popover.component.hide(); this.to('done'); } } ngOnDestroy() { clearTimeout(this.time); this.updatePrevElStatus(false); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: OnboardingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: OnboardingComponent, isStandalone: true, selector: "onboarding", host: { properties: { "class.onboarding": "true", "class.onboarding-rtl": "dir === 'rtl'", "attr.data-onboarding-active": "active" } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true }], ngImport: i0, template: "@if (!running && config.mask) {\n <div class=\"onboarding__mask\" (click)=\"handleMask()\"></div>\n}\n@if (item) {\n <div\n class=\"onboarding__light\"\n [class.onboarding__light-hide]=\"running\"\n [attr.style]=\"item.lightStyle\"\n nz-popover\n #popover=\"nzPopover\"\n [nzPopoverTitle]=\"item.title\"\n [nzPopoverContent]=\"content\"\n [nzPopoverVisible]=\"!running\"\n [nzPopoverTrigger]=\"null\"\n [nzPopoverPlacement]=\"item.position\"\n [nzPopoverOverlayClassName]=\"item.className\"\n [nzPopoverOverlayStyle]=\"{ 'max-width.px': item.width, direction: dir }\"\n [nzNoAnimation]=\"true\"\n ></div>\n <ng-template #content>\n <ng-container *nzStringTemplateOutlet=\"item.content\">\n <div [innerHTML]=\"item.content\"></div>\n </ng-container>\n <div class=\"flex-center-between onboarding__footer\">\n <span class=\"onboarding__total\">\n @if (config.showTotal) {\n {{ active + 1 }}/{{ max }}\n }\n </span>\n <div class=\"onboarding__btns\">\n @if (!last && item.skip !== null && item.skip !== undefined) {\n <a nz-button nzType=\"link\" (click)=\"to('skip')\" nzSize=\"small\" data-btnType=\"skip\">\n <ng-container *nzStringTemplateOutlet=\"item.skip\">{{ item.skip }}</ng-container>\n </a>\n }\n @if (!first && item.prev !== null) {\n <a nz-button (click)=\"to('prev')\" nzSize=\"small\" data-btnType=\"prev\">\n <ng-container *nzStringTemplateOutlet=\"item.prev\">{{ item.prev }}</ng-container>\n </a>\n }\n @if (!last && item.next !== null && item.next !== undefined) {\n <a nz-button (click)=\"to('next')\" nzType=\"primary\" nzSize=\"small\" data-btnType=\"next\">\n <ng-container *nzStringTemplateOutlet=\"item.next\">{{ item.next }}</ng-container>\n </a>\n }\n @if (last && item.done !== null && item.done !== undefined) {\n <a nz-button (click)=\"to('done')\" nzType=\"primary\" nzSize=\"small\" data-btnType=\"done\">\n <ng-container *nzStringTemplateOutlet=\"item.done\">{{ item.done }}</ng-container>\n </a>\n }\n </div>\n </div>\n </ng-template>\n}\n", dependencies: [{ kind: "directive", type: NzPopoverDirective, selector: "[nz-popover]", inputs: ["nzPopoverArrowPointAtCenter", "nzPopoverTitle", "nzPopoverTitleContext", "nzPopoverContent", "nzPopoverContentContext", "nz-popover", "nzPopoverTrigger", "nzPopoverPlacement", "nzPopoverOrigin", "nzPopoverVisible", "nzPopoverMouseEnterDelay", "nzPopoverMouseLeaveDelay", "nzPopoverOverlayClassName", "nzPopoverOverlayStyle", "nzPopoverOverlayClickable", "nzPopoverBackdrop"], outputs: ["nzPopoverVisibleChange"], exportAs: ["nzPopover"] }, { kind: "directive", type: NzStringTemplateOutletDirective, selector: "[nzStringTemplateOutlet]", inputs: ["nzStringTemplateOutletContext", "nzStringTemplateOutlet"], exportAs: ["nzStringTemplateOutlet"] }, { kind: "component", type: NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: NzNoAnimationDirective, selector: "[nzNoAnimation]", inputs: ["nzNoAnimation"], exportAs: ["nzNoAnimation"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: OnboardingComponent, decorators: [{ type: Component, args: [{ selector: 'onboarding', host: { '[class.onboarding]': `true`, '[class.onboarding-rtl]': `dir === 'rtl'`, '[attr.data-onboarding-active]': `active` }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [NzPopoverDirective, NzStringTemplateOutletDirective, NzButtonComponent, NzNoAnimationDirective], template: "@if (!running && config.mask) {\n <div class=\"onboarding__mask\" (click)=\"handleMask()\"></div>\n}\n@if (item) {\n <div\n class=\"onboarding__light\"\n [class.onboarding__light-hide]=\"running\"\n [attr.style]=\"item.lightStyle\"\n nz-popover\n #popover=\"nzPopover\"\n [nzPopoverTitle]=\"item.title\"\n [nzPopoverContent]=\"content\"\n [nzPopoverVisible]=\"!running\"\n [nzPopoverTrigger]=\"null\"\n [nzPopoverPlacement]=\"item.position\"\n [nzPopoverOverlayClassName]=\"item.className\"\n [nzPopoverOverlayStyle]=\"{ 'max-width.px': item.width, direction: dir }\"\n [nzNoAnimation]=\"true\"\n ></div>\n <ng-template #content>\n <ng-container *nzStringTemplateOutlet=\"item.content\">\n <div [innerHTML]=\"item.content\"></div>\n </ng-container>\n <div class=\"flex-center-between onboarding__footer\">\n <span class=\"onboarding__total\">\n @if (config.showTotal) {\n {{ active + 1 }}/{{ max }}\n }\n </span>\n <div class=\"onboarding__btns\">\n @if (!last && item.skip !== null && item.skip !== undefined) {\n <a nz-button nzType=\"link\" (click)=\"to('skip')\" nzSize=\"small\" data-btnType=\"skip\">\n <ng-container *nzStringTemplateOutlet=\"item.skip\">{{ item.skip }}</ng-container>\n </a>\n }\n @if (!first && item.prev !== null) {\n <a nz-button (click)=\"to('prev')\" nzSize=\"small\" data-btnType=\"prev\">\n <ng-container *nzStringTemplateOutlet=\"item.prev\">{{ item.prev }}</ng-container>\n </a>\n }\n @if (!last && item.next !== null && item.next !== undefined) {\n <a nz-button (click)=\"to('next')\" nzType=\"primary\" nzSize=\"small\" data-btnType=\"next\">\n <ng-container *nzStringTemplateOutlet=\"item.next\">{{ item.next }}</ng-container>\n </a>\n }\n @if (last && item.done !== null && item.done !== undefined) {\n <a nz-button (click)=\"to('done')\" nzType=\"primary\" nzSize=\"small\" data-btnType=\"done\">\n <ng-container *nzStringTemplateOutlet=\"item.done\">{{ item.done }}</ng-container>\n </a>\n }\n </div>\n </div>\n </ng-template>\n}\n" }] }], propDecorators: { popover: [{ type: ViewChild, args: ['popover', { static: false }] }] } }); const ONBOARDING_STORE_TOKEN = new InjectionToken('ONBOARDING_STORE_TOKEN', { providedIn: 'root', factory: ONBOARDING_STORE_TOKEN_FACTORY }); function ONBOARDING_STORE_TOKEN_FACTORY() { return new LocalStorageStore(); } class LocalStorageStore { get(key) { return localStorage.getItem(key); } set(key, version) { localStorage.setItem(key, `${version}`); } } class OnboardingService { appRef = inject(ApplicationRef); router = inject(Router); doc = inject(DOCUMENT); configSrv = inject(YunzaiConfigService); keyStoreSrv = inject(ONBOARDING_STORE_TOKEN); directionality = inject(Directionality); compRef; op$; config; active = 0; running$ = null; _running = false; type = null; locale = inject(YelonLocaleService).valueSignal('onboarding'); _getDoc() { return this.doc; } /** * Get whether it is booting * * 获取是否正在引导中 */ get running() { return this._running; } attach() { const compRef = createComponent(OnboardingComponent, { environmentInjector: this.appRef.injector }); this.compRef = compRef; this.appRef.attachView(compRef.hostView); const compNode = compRef.hostView.rootNodes[0]; const doc = this._getDoc(); const cdk = doc.querySelector('.cdk-overlay-container'); if (cdk) { doc.body.insertBefore(compNode, cdk); } else { doc.body.appendChild(compNode); } this.op$ = this.compRef.instance.op.subscribe((type) => { switch (type) { case 'next': this.next(); break; case 'prev': this.prev(); break; default: this.done(); break; } }); } cancelRunning() { if (this.running$) { this.running$.unsubscribe(); this.running$ = null; } return this; } updateRunning(status) { this._running = status; this.compRef.instance.updateRunning(status); return this; } destroy() { const storeKey = this.config?.key; if (storeKey != null) { this.keyStoreSrv.set(storeKey, this.config?.keyVersion); } this.cancelRunning(); if (this.compRef) { this.appRef.detachView(this.compRef.hostView); this.compRef.destroy(); this.op$.unsubscribe(); } } showItem(isStart = false) { const items = this.config.items; const item = { position: 'bottomLeft', before: of(true), after: of(true), ...this.locale(), ...items[this.active] }; const dir = this.configSrv.get('onboarding').direction || this.directionality.value; Object.assign(this.compRef.instance, { item, config: this.config, active: this.active, max: items.length, dir }); const pipes = [ switchMap(() => (item.url ? this.router.navigateByUrl(item.url) : of(true))), switchMap(() => { const obs = this.type === 'prev' ? item.after : item.before; return typeof obs === 'number' ? of(true).pipe(delay(obs)) : obs; }) ]; if (!isStart) { pipes.push(delay(1)); } this.updateRunning(true); this.running$ = of(true) .pipe(pipe.apply(this, pipes)) .subscribe({ next: () => this.cancelRunning().updateRunning(false), error: () => this.done() }); } /** * Start a new user guidance * * 开启新的用户引导流程 */ start(config) { const cog = { keyVersion: '', items: [], mask: true, maskClosable: true, showTotal: false, ...config }; const storeKey = cog?.key; if (storeKey != null && this.keyStoreSrv.get(storeKey) === cog.keyVersion) { return; } if (this.running) { return; } this.destroy(); this.config = cog; this.active = 0; this.type = null; this.attach(); this.showItem(true); } /** * Next * * 下一步 */ next() { if (this._running || this.active + 1 >= this.config.items.length) { this.done(); return; } this.type = 'next'; ++this.active; this.showItem(); } /** * Prev * * 上一步 */ prev() { if (this._running || this.active - 1 < 0) { return; } this.type = 'prev'; --this.active; this.showItem(); } /** * Done * * 完成 */ done() { this.type = 'done'; this.destroy(); } ngOnDestroy() { this.destroy(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: OnboardingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: OnboardingService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: OnboardingService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); const COMPONENTS = [OnboardingComponent]; class OnboardingModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: OnboardingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.1.3", ngImport: i0, type: OnboardingModule, imports: [CommonModule, YelonLocaleModule, NzPopoverModule, NzOutletModule, NzButtonModule, NzNoAnimationModule, OnboardingComponent], exports: [OnboardingComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: OnboardingModule, imports: [CommonModule, YelonLocaleModule, NzPopoverModule, NzOutletModule, NzButtonModule, NzNoAnimationModule, COMPONENTS] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: OnboardingModule, decorators: [{ type: NgModule, args: [{ imports: [ CommonModule, YelonLocaleModule, NzPopoverModule, NzOutletModule, NzButtonModule, NzNoAnimationModule, COMPONENTS ], exports: COMPONENTS }] }] }); /** * Generated bundle index. Do not edit. */ export { LocalStorageStore, ONBOARDING_STORE_TOKEN, ONBOARDING_STORE_TOKEN_FACTORY, OnboardingComponent, OnboardingModule, OnboardingService }; //# sourceMappingURL=onboarding.mjs.map