From 73fc419537be2ba5df1adb7f7903612f98f9d9cc Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 20 Mar 2025 19:16:37 +0200 Subject: [PATCH] UI: Fix markdown widgets blur (set use transform to false for gridster). Fix markdown blinking (cache angular/compiler module on dynamic component service). Fix echarts tooltip. --- ui-ngx/package.json | 2 +- .../dynamic-component-factory.service.ts | 14 +++-- .../dashboard/dashboard.component.ts | 1 + .../widget/lib/chart/latest-chart.ts | 3 +- .../widget/lib/chart/time-series-chart.ts | 12 ++++ .../widget/lib/markdown-widget.component.html | 2 +- .../widget/lib/markdown-widget.component.ts | 14 +++-- .../shared/components/markdown.component.ts | 59 +++++++++++-------- ui-ngx/yarn.lock | 15 +++-- 9 files changed, 73 insertions(+), 49 deletions(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 1a32c7d41c..6280fb94c2 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -46,7 +46,7 @@ "canvas-gauges": "^2.1.7", "core-js": "^3.39.0", "dayjs": "1.11.13", - "echarts": "https://github.com/thingsboard/echarts/archive/5.5.0-TB.tar.gz", + "echarts": "https://github.com/thingsboard/echarts/archive/5.5.1-TB.tar.gz", "flot": "https://github.com/thingsboard/flot.git#0.9-work", "flot.curvedlines": "https://github.com/MichaelZinsmaier/CurvedLines.git#master", "font-awesome": "^4.7.0", diff --git a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts index 75bfa6b1e1..c062ac2e82 100644 --- a/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts +++ b/ui-ngx/src/app/core/services/dynamic-component-factory.service.ts @@ -15,9 +15,9 @@ /// import { Component, Injectable, Type, ɵComponentDef, ɵNG_COMP_DEF } from '@angular/core'; -import { from, Observable, of } from 'rxjs'; +import { from, Observable, shareReplay } from 'rxjs'; import { CommonModule } from '@angular/common'; -import { mergeMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { guid } from '@core/utils'; @Injectable({ @@ -25,6 +25,10 @@ import { guid } from '@core/utils'; }) export class DynamicComponentFactoryService { + private compiler$: Observable = from(import('@angular/compiler')).pipe( + shareReplay({refCount: true, bufferSize: 1}) + ); + constructor() { } @@ -34,14 +38,14 @@ export class DynamicComponentFactoryService { imports?: Type[], preserveWhitespaces?: boolean, styles?: string[]): Observable> { - return from(import('@angular/compiler')).pipe( - mergeMap(() => { + return this.compiler$.pipe( + map(() => { let componentImports: Type[] = [CommonModule]; if (imports) { componentImports = [...componentImports, ...imports]; } const comp = this.createAndCompileDynamicComponent(componentType, template, componentImports, preserveWhitespaces, styles); - return of(comp.type); + return comp.type; }) ); } diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index cc013fe34d..95f2095bad 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -247,6 +247,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo defaultItemCols: 8, defaultItemRows: 6, displayGrid: this.displayGrid, + useTransformPositioning: false, resizable: { enabled: this.isEdit && !this.isEditingWidget, delayStart: 50, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts index 5f17abc37d..13e9dd7d5e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.ts @@ -270,8 +270,7 @@ export abstract class TbLatestChart { this.latestChartOption = { tooltip: { trigger: this.settings.showTooltip ? 'item' : 'none', - confine: false, - appendTo: 'body', + confine: true, formatter: (params: CallbackDataParams) => this.settings.showTooltip ? latestChartTooltipFormatter(this.renderer, this.settings, params, this.units, this.total, this.dataItems) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index 9086bb1896..ad121a1eb0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -161,6 +161,8 @@ export class TbTimeSeriesChart { private latestData: FormattedData[] = []; + private onParentScroll = this._onParentScroll.bind(this); + yMin$ = this.yMinSubject.asObservable(); yMax$ = this.yMaxSubject.asObservable(); @@ -358,6 +360,7 @@ export class TbTimeSeriesChart { this.yMinSubject.complete(); this.yMaxSubject.complete(); this.darkModeObserver?.disconnect(); + this.ctx.dashboard.gridster.el.removeEventListener('scroll', this.onParentScroll); } public resize(): void { @@ -611,6 +614,7 @@ export class TbTimeSeriesChart { this.timeSeriesChart = echarts.init(this.chartElement, null, { renderer: 'svg' }); + this.ctx.dashboard.gridster.el.addEventListener('scroll', this.onParentScroll); this.timeSeriesChartOptions = { darkMode: this.darkMode, backgroundColor: 'transparent', @@ -837,6 +841,14 @@ export class TbTimeSeriesChart { return this.settings.dataZoom ? 45 : 5; } + private _onParentScroll() { + if (this.timeSeriesChart) { + this.timeSeriesChart.dispatchAction({ + type: 'hideTip' + }); + } + } + private onResize() { const shapeWidth = this.chartElement.offsetWidth; const shapeHeight = this.chartElement.offsetHeight; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html index 3ef8d6c023..ce1a1438aa 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html @@ -19,4 +19,4 @@ [additionalStyles]="additionalStyles" [containerClass]="markdownClass" [applyDefaultMarkdownStyle]="applyDefaultMarkdownStyle" - [context]="{ ctx: ctx }" lineNumbers fallbackToPlainMarkdown (click)="markdownClick($event)"> + [context]="{ ctx: ctx, data: data }" lineNumbers fallbackToPlainMarkdown (click)="markdownClick($event)"> diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts index 1e80baddfa..61f5073953 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts @@ -63,6 +63,8 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit { @Input() ctx: WidgetContext; + data: FormattedData[]; + markdownText: string; additionalStyles: string[]; @@ -128,15 +130,15 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit { } else { initialData = []; } - const data = formattedDataFormDatasourceData(initialData); + this.data = formattedDataFormDatasourceData(initialData); - let markdownText = this.settings.useMarkdownTextFunction ? - this.markdownTextFunction.pipe(map(markdownTextFunction => safeExecuteTbFunction(markdownTextFunction, [data, this.ctx]))) : this.settings.markdownTextPattern; + const markdownText = this.settings.useMarkdownTextFunction ? + this.markdownTextFunction.pipe(map(markdownTextFunction => safeExecuteTbFunction(markdownTextFunction, [this.data, this.ctx]))) : this.settings.markdownTextPattern; if (typeof markdownText === 'string') { - this.updateMarkdownText(markdownText, data); + this.updateMarkdownText(markdownText, this.data); } else { markdownText.subscribe((text) => { - this.updateMarkdownText(text, data); + this.updateMarkdownText(text, this.data); }); } } @@ -146,8 +148,8 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit { markdownText = createLabelFromPattern(markdownText, allData); if (this.markdownText !== markdownText) { this.markdownText = this.utils.customTranslation(markdownText, markdownText); - this.cd.detectChanges(); } + this.cd.markForCheck(); } markdownClick($event: MouseEvent) { diff --git a/ui-ngx/src/app/shared/components/markdown.component.ts b/ui-ngx/src/app/shared/components/markdown.component.ts index bee7da0a51..0db86a9eef 100644 --- a/ui-ngx/src/app/shared/components/markdown.component.ts +++ b/ui-ngx/src/app/shared/components/markdown.component.ts @@ -32,16 +32,14 @@ import { ViewChild, ViewContainerRef } from '@angular/core'; -import { HelpService } from '@core/services/help.service'; import { MarkdownService, PrismPlugin } from 'ngx-markdown'; import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; -import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { SHARED_MODULE_TOKEN } from '@shared/components/tokens'; -import { deepClone, guid, isDefinedAndNotNull } from '@core/utils'; +import { guid, isDefinedAndNotNull } from '@core/utils'; import { Observable, of, ReplaySubject } from 'rxjs'; import { coerceBoolean } from '@shared/decorators/coercion'; -let defaultMarkdownStyle; +let defaultMarkdownStyle: string; @Component({ selector: 'tb-markdown', @@ -70,12 +68,12 @@ export class TbMarkdownComponent implements OnChanges { @Input() additionalStyles: string[]; @Input() - get lineNumbers(): boolean { return this.lineNumbersValue; } - set lineNumbers(value: boolean) { this.lineNumbersValue = coerceBooleanProperty(value); } + @coerceBoolean() + lineNumbers = false; @Input() - get fallbackToPlainMarkdown(): boolean { return this.fallbackToPlainMarkdownValue; } - set fallbackToPlainMarkdown(value: boolean) { this.fallbackToPlainMarkdownValue = coerceBooleanProperty(value); } + @coerceBoolean() + fallbackToPlainMarkdown = false; @Input() @coerceBoolean() @@ -83,9 +81,6 @@ export class TbMarkdownComponent implements OnChanges { @Output() ready = new EventEmitter(); - private lineNumbersValue = false; - private fallbackToPlainMarkdownValue = false; - isMarkdownReady = false; error = null; @@ -93,8 +88,7 @@ export class TbMarkdownComponent implements OnChanges { private tbMarkdownInstanceComponentRef: ComponentRef; private tbMarkdownInstanceComponentType: Type; - constructor(private help: HelpService, - private cd: ChangeDetectorRef, + constructor(private cd: ChangeDetectorRef, private zone: NgZone, public markdownService: MarkdownService, @Inject(SHARED_MODULE_TOKEN) private sharedModule: Type, @@ -102,8 +96,19 @@ export class TbMarkdownComponent implements OnChanges { private renderer: Renderer2) {} ngOnChanges(changes: SimpleChanges): void { - if (isDefinedAndNotNull(this.data)) { - this.zone.run(() => this.render(this.data)); + for (const propName of Object.keys(changes)) { + const change = changes[propName]; + if (propName === 'data' && change.currentValue !== change.previousValue) { + if (isDefinedAndNotNull(this.data)) { + this.zone.run(() => this.render(this.data)); + } + } else if (propName === 'context' && !change.firstChange) { + if (this.context && this.tbMarkdownInstanceComponentRef) { + for (const propName of Object.keys(this.context)) { + this.tbMarkdownInstanceComponentRef.instance[propName] = this.context[propName]; + } + } + } } } @@ -134,8 +139,8 @@ export class TbMarkdownComponent implements OnChanges { if (this.applyDefaultMarkdownStyle) { if (!defaultMarkdownStyle) { const compDef = this.dynamicComponentFactoryService.getComponentDef(TbMarkdownComponent); - defaultMarkdownStyle = deepClone(compDef.styles[0]).replace(/\[_nghost\-%COMP%\]/g, '') - .replace(/\[_ngcontent\-%COMP%\]/g, ''); + defaultMarkdownStyle = compDef.styles[0].replace(/\[_nghost-%COMP%]/g, '') + .replace(/\[_ngcontent-%COMP%]/g, ''); } styles.push(defaultMarkdownStyle); } @@ -149,7 +154,7 @@ export class TbMarkdownComponent implements OnChanges { this.ready.emit(); }); } else { - const parent = this; + const destroyMarkdownInstanceResources = this.destroyMarkdownInstanceResources.bind(this); let compileModules = [this.sharedModule]; if (this.additionalCompileModules) { compileModules = compileModules.concat(this.additionalCompileModules); @@ -157,13 +162,14 @@ export class TbMarkdownComponent implements OnChanges { this.dynamicComponentFactoryService.createDynamicComponent( class TbMarkdownInstance { ngOnDestroy(): void { - parent.destroyMarkdownInstanceResources(); + destroyMarkdownInstanceResources(); } }, template, compileModules, true, styles - ).subscribe((componentType) => { + ).subscribe({ + next: (componentType) => { this.tbMarkdownInstanceComponentType = componentType; const injector: Injector = Injector.create({providers: [], parent: this.markdownContainer.injector}); try { @@ -187,20 +193,21 @@ export class TbMarkdownComponent implements OnChanges { this.ready.emit(); }); }, - (error) => { + error: (error) => { readyObservable = this.handleError(template, error, styles); this.cd.detectChanges(); readyObservable.subscribe(() => { this.ready.emit(); }); - }); + } + }); } } - private handleError(template: string, error, styles?: string[]): Observable { + private handleError(template: string, error: any, styles?: string[]): Observable { this.error = (error ? error + '' : 'Failed to render markdown!').replace(/\n/g, '
'); this.markdownContainer.clear(); - if (this.fallbackToPlainMarkdownValue) { + if (this.fallbackToPlainMarkdown) { return this.plainMarkdown(template, styles); } else { return of(null); @@ -209,7 +216,7 @@ export class TbMarkdownComponent implements OnChanges { private plainMarkdown(template: string, styles?: string[]): Observable { const element = this.fallbackElement.nativeElement; - let styleElement; + let styleElement: any; if (styles?.length) { const markdownClass = 'tb-markdown-view-' + guid(); let innerStyle = styles.join('\n'); @@ -244,7 +251,7 @@ export class TbMarkdownComponent implements OnChanges { if (imgs.length) { let totalImages = imgs.length; const imagesLoadedSubject = new ReplaySubject(); - imgs.each((index, img) => { + imgs.each((_index, img) => { $(img).one('load error', () => { totalImages--; if (totalImages === 0) { diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 04e0c4f77a..602839b2da 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -4798,12 +4798,12 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -"echarts@https://github.com/thingsboard/echarts/archive/5.5.0-TB.tar.gz": - version "5.5.0-TB" - resolved "https://github.com/thingsboard/echarts/archive/5.5.0-TB.tar.gz#0b707b5cd2ae4699e9ced8b07ca49cb70189ae2a" +"echarts@https://github.com/thingsboard/echarts/archive/5.5.1-TB.tar.gz": + version "5.5.1-TB" + resolved "https://github.com/thingsboard/echarts/archive/5.5.1-TB.tar.gz#8cf0cbb1b4c6161f0b587a1a649ff4f8eecbbf42" dependencies: tslib "2.3.0" - zrender "5.5.0" + zrender "https://github.com/thingsboard/zrender/archive/5.5.0-TB.tar.gz" editorconfig@^1.0.4: version "1.0.4" @@ -10102,9 +10102,8 @@ zone.js@~0.14.10: resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.14.10.tgz#23b8b29687c6bffece996e5ee5b854050e7775c8" integrity sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ== -zrender@5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.5.0.tgz#54d0d6c4eda81a96d9f60a9cd74dc48ea026bc1e" - integrity sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w== +"zrender@https://github.com/thingsboard/zrender/archive/5.5.0-TB.tar.gz": + version "5.5.0-TB" + resolved "https://github.com/thingsboard/zrender/archive/5.5.0-TB.tar.gz#9605f08284436a9be86085e27f1c01b29a9923bf" dependencies: tslib "2.3.0"