From 32d34e0acc54a24ecc6068dabb392d49c1506e6a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 21 May 2024 19:26:48 +0300 Subject: [PATCH] UI: SCADA symbol preview mode. --- ui-ngx/src/app/core/utils.ts | 5 + .../lib/svg/iot-svg-widget.component.html | 2 +- .../lib/svg/iot-svg-widget.component.ts | 39 ++++- .../widget/lib/svg/iot-svg-widget.models.ts | 4 +- .../widget/lib/svg/iot-svg.models.ts | 87 +++++----- .../scada-symbol/scada-symbol.component.html | 44 +++-- .../scada-symbol/scada-symbol.component.ts | 150 ++++++++++++++++-- .../pages/scada-symbol/scada-symbol.models.ts | 63 +++----- 8 files changed, 269 insertions(+), 125 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 04c0a9fc9b..545f82ddf5 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -229,6 +229,11 @@ export const updateFileContent = (file: File, newContent: string): File => { return new File([blob], file.name, {type: file.type}); }; +export const createFileFromContent = (content: string, name: string, type: string): File => { + const blob = new Blob([content], { type }); + return new File([blob], name, { type }); +}; + const scrollRegex = /(auto|scroll)/; function parentNodes(node: Node, nodes: Node[]): Node[] { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.html index b2f4ce1815..ee005b98c0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.html @@ -22,5 +22,5 @@
- + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts index c7b5e87723..608feee493 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.component.ts @@ -35,8 +35,9 @@ import { iotSvgWidgetDefaultSettings, IotSvgWidgetSettings } from '@home/components/widget/lib/svg/iot-svg-widget.models'; -import { Observable } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models'; +import { ImageService } from '@core/http/image.service'; @Component({ selector: 'tb-iot-svg-widget', @@ -63,6 +64,7 @@ export class IotSvgWidgetComponent extends constructor(protected imagePipe: ImagePipe, protected sanitizer: DomSanitizer, private renderer: Renderer2, + private imageService: ImageService, protected cd: ChangeDetectorRef, private http: HttpClient) { super(cd); @@ -75,15 +77,40 @@ export class IotSvgWidgetComponent extends this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); - this.iotSvgObject = new IotSvgObject(this.ctx, this.settings.iotSvg, this.settings.iotSvgObject); + let svgContent$: Observable; + if (this.settings.iotSvgContent) { + svgContent$ = of(this.settings.iotSvgContent); + } else if (this.settings.iotSvgUrl) { + svgContent$ = this.imageService.getImageString(this.settings.iotSvgUrl); + } else { + svgContent$ = this.http.get(this.settings.iotSvg, {responseType: 'text'}); + } + + svgContent$.subscribe( + (content) => { + this.initObject(content); + } + ); + } + + private initObject(svgContent: string) { + this.iotSvgObject = new IotSvgObject(this.ctx, svgContent, this.settings.iotSvgObject); this.iotSvgObject.onError((error) => { this.ctx.showErrorToast(error, 'bottom', 'center', this.ctx.toastTargetId, true); }); - this.iotSvgObject.init().subscribe(); + this.iotSvgObject.init(); + if (this.iotSvgShape) { + this.iotSvgObject.addTo(this.iotSvgShape.nativeElement); + if (this.autoScale) { + this.onResize(); + } + } } ngAfterViewInit(): void { - this.iotSvgObject.addTo(this.iotSvgShape.nativeElement); + if (this.iotSvgObject) { + this.iotSvgObject.addTo(this.iotSvgShape.nativeElement); + } if (this.autoScale) { this.shapeResize$ = new ResizeObserver(() => { this.onResize(); @@ -114,6 +141,8 @@ export class IotSvgWidgetComponent extends private onResize() { const shapeWidth = this.iotSvgShape.nativeElement.getBoundingClientRect().width; const shapeHeight = this.iotSvgShape.nativeElement.getBoundingClientRect().height; - this.iotSvgObject.setSize(shapeWidth, shapeHeight); + if (this.iotSvgObject) { + this.iotSvgObject.setSize(shapeWidth, shapeHeight); + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts index e4992ae62f..b3223c5b79 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg-widget.models.ts @@ -18,7 +18,9 @@ import { IotSvgObjectSettings } from '@home/components/widget/lib/svg/iot-svg.mo import { BackgroundSettings, BackgroundType } from '@shared/models/widget-settings.models'; export interface IotSvgWidgetSettings { - iotSvg: string; + iotSvg?: string; + iotSvgUrl?: string; + iotSvgContent?: string; iotSvgObject: IotSvgObjectSettings; background: BackgroundSettings; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts index 7277ea0751..b037b947ed 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts @@ -34,7 +34,7 @@ import { parseFunction } from '@core/utils'; import { BehaviorSubject, forkJoin, Observable, Observer } from 'rxjs'; -import { map, share } from 'rxjs/operators'; +import { share } from 'rxjs/operators'; import { ValueAction, ValueGetter, ValueSetter } from '@home/components/widget/lib/action/action-widget.models'; import { WidgetContext } from '@home/models/widget-component.models'; import { ColorProcessor, constantColor, Font } from '@shared/models/widget-settings.models'; @@ -196,45 +196,38 @@ const updateIotSvgMetadataInDom = (svgDoc: Document, metadata: IotSvgMetadata) = }; const svgPartsRegex = /()(.*)<\/svg>/gms; +const tbMetadataRegex = /.*<\/tb:metadata>/gs; -const svgMetadataRegex = /.*<\/tb:metadata>/gs; - -export const svgRootNodePart = (svgContent: string): string => { - let result = svgContent; - svgPartsRegex.lastIndex = 0; - const match = svgPartsRegex.exec(svgContent); - if (match != null && match.length > 1) { - result = match[1]; - } - return result; -}; +export interface IoTSvgContentData { + svgRootNode: string; + innerSvg: string; +} -export const innerSvgContent = (svgContent: string): string => { - let result = svgContent; +export const iotSvgContentData = (svgContent: string): IoTSvgContentData => { + const result: IoTSvgContentData = { + svgRootNode: '', + innerSvg: '' + }; svgPartsRegex.lastIndex = 0; const match = svgPartsRegex.exec(svgContent); - if (match != null && match.length > 2) { - result = match[2]; - } - return result; -}; - -export const stripSvgMetadata = (svgContent: string) => { - let result = svgContent; - svgMetadataRegex.lastIndex = 0; - const match = svgMetadataRegex.exec(svgContent); - if (match !== null && match.length) { - const metadata = match[0]; - result = result.replace(metadata, ''); + if (match != null) { + if (match.length > 1) { + result.svgRootNode = match[1]; + } + if (match.length > 2) { + let innerSvgContent = match[2]; + tbMetadataRegex.lastIndex = 0; + const metadataMatch = tbMetadataRegex.exec(svgContent); + if (metadataMatch !== null && metadataMatch.length) { + const metadata = metadataMatch[0]; + innerSvgContent = innerSvgContent.replace(metadata, ''); + } + result.innerSvg = innerSvgContent; + } } return result; }; -export const innerSvgContentFromSvgjs = (svgContent: string): string => { - const result = innerSvgContent(svgContent); - return result.replace(/svgjs:.*?=".*?"/gs, ''); -}; - const defaultGetValueSettings = (get: IotSvgBehaviorValue): GetValueSettings => ({ action: GetValueAction.DO_NOTHING, defaultValue: get.defaultValue, @@ -335,21 +328,18 @@ export class IotSvgObject { private _onError: (error: string) => void = () => {}; constructor(private ctx: WidgetContext, - private svgPath: string, - private inputSettings: IotSvgObjectSettings) {} - - public init(): Observable { - return this.ctx.http.get(this.svgPath, {responseType: 'text'}).pipe( - map((inputSvgContent) => { - const doc: XMLDocument = new DOMParser().parseFromString(inputSvgContent, 'image/svg+xml'); - this.metadata = parseIotSvgMetadataFromDom(doc); - const defaults = defaultIotSvgObjectSettings(this.metadata); - this.settings = mergeDeep({}, defaults, this.inputSettings || {}); - this.prepareMetadata(); - this.prepareSvgShape(doc); - this.initialize(); - }) - ); + private svgContent: string, + private inputSettings: IotSvgObjectSettings) { + } + + public init() { + const doc: XMLDocument = new DOMParser().parseFromString(this.svgContent, 'image/svg+xml'); + this.metadata = parseIotSvgMetadataFromDom(doc); + const defaults = defaultIotSvgObjectSettings(this.metadata); + this.settings = mergeDeep({}, defaults, this.inputSettings || {}); + this.prepareMetadata(); + this.prepareSvgShape(doc); + this.initialize(); } public onError(onError: (error: string) => void) { @@ -371,6 +361,9 @@ export class IotSvgObject { this.valueActions.forEach(v => v.destroy()); this.loadingSubject.complete(); this.loadingSubject.unsubscribe(); + if (this.svgShape) { + this.svgShape.remove(); + } } public setSize(targetWidth: number, targetHeight: number) { diff --git a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html index fb32b1decc..b04247b916 100644 --- a/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html +++ b/ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.html @@ -29,41 +29,39 @@ [showCloseDetails]="false" headerHeightPx="64" headerTitle="{{symbolData?.imageResource?.title}}"> -
+ +