From c8b4d95c2b2a63ee96ccc6c00f661b1529bf7526 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 9 May 2024 20:38:41 +0300 Subject: [PATCH] UI: Implement new IoT SVG upload. --- ui-ngx/src/app/core/utils.ts | 5 +++ .../widget/lib/svg/iot-svg.models.ts | 32 +++++++++++++--- .../image/image-gallery.component.ts | 8 +++- .../image/upload-image-dialog.component.ts | 37 ++++++++++++++++++- 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index c9aebfc39b..04c0a9fc9b 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -224,6 +224,11 @@ export const blobToText = (blob: Blob): Observable => from(new Promise { + const blob = new Blob([newContent], { type: file.type }); + return new File([blob], file.name, {type: file.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.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts index 8e369cf580..08840351c3 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 @@ -142,12 +142,12 @@ export interface IotSvgMetadata { properties: IotSvgProperty[]; } -export const emptyMetadata: IotSvgMetadata = { +export const emptyMetadata = (): IotSvgMetadata => ({ title: '', tags: [], behavior: [], properties: [] -}; +}); export const parseIotSvgMetadataFromContent = (svgContent: string): IotSvgMetadata => { @@ -155,7 +155,7 @@ export const parseIotSvgMetadataFromContent = (svgContent: string): IotSvgMetada const svgDoc = new DOMParser().parseFromString(svgContent, 'image/svg+xml'); return parseIotSvgMetadataFromDom(svgDoc); } catch (_e) { - return emptyMetadata; + return emptyMetadata(); } }; @@ -165,14 +165,36 @@ const parseIotSvgMetadataFromDom = (svgDoc: Document): IotSvgMetadata => { if (elements.length) { return JSON.parse(elements[0].textContent); } else { - return emptyMetadata; + return emptyMetadata(); } } catch (_e) { console.error(_e); - return emptyMetadata; + return emptyMetadata(); } }; +export const updateIotSvgMetadataInContent = (svgContent: string, metadata: IotSvgMetadata): string => { + const svgDoc = new DOMParser().parseFromString(svgContent, 'image/svg+xml'); + updateIotSvgMetadataInDom(svgDoc, metadata); + return svgDoc.documentElement.outerHTML; +}; + +const updateIotSvgMetadataInDom = (svgDoc: Document, metadata: IotSvgMetadata) => { + svgDoc.documentElement.setAttribute('xmlns:tb', 'https://thingsboard.io/svg'); + let metadataElement: Node; + const elements = svgDoc.getElementsByTagName('tb:metadata'); + if (elements?.length) { + metadataElement = elements[0]; + metadataElement.textContent = ''; + } else { + metadataElement = svgDoc.createElement('tb:metadata'); + svgDoc.documentElement.insertBefore(metadataElement, svgDoc.documentElement.firstChild); + } + const content = JSON.stringify(metadata, null, 2); + const cdata = svgDoc.createCDATASection(content); + metadataElement.appendChild(cdata); +}; + const defaultGetValueSettings = (get: IotSvgBehaviorValue): GetValueSettings => ({ action: GetValueAction.DO_NOTHING, defaultValue: get.defaultValue, diff --git a/ui-ngx/src/app/shared/components/image/image-gallery.component.ts b/ui-ngx/src/app/shared/components/image/image-gallery.component.ts index 30bc061994..b0d4217eaa 100644 --- a/ui-ngx/src/app/shared/components/image/image-gallery.component.ts +++ b/ui-ngx/src/app/shared/components/image/image-gallery.component.ts @@ -661,7 +661,13 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe if (this.selectionMode) { this.imageSelected.next(result); } else { - this.updateData(); + if (this.isScada) { + const type = imageResourceType(result); + const key = encodeURIComponent(result.resourceKey); + this.router.navigateByUrl(`resources/scada-symbols/${type}/${key}`); + } else { + this.updateData(); + } } } }); diff --git a/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts b/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts index 31272ee7d6..4438034062 100644 --- a/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts @@ -33,7 +33,13 @@ import { ImageService } from '@core/http/image.service'; import { ImageResource, ImageResourceInfo, imageResourceType, ResourceSubType } from '@shared/models/resource.models'; import { getCurrentAuthState } from '@core/auth/auth.selectors'; import { forkJoin } from 'rxjs'; -import { blobToBase64 } from '@core/utils'; +import { blobToBase64, blobToText, updateFileContent } from '@core/utils'; +import { + emptyMetadata, + IotSvgMetadata, + parseIotSvgMetadataFromContent, + updateIotSvgMetadataInContent +} from '@home/components/widget/lib/svg/iot-svg.models'; export interface UploadImageDialogData { imageSubType: ResourceSubType; @@ -61,6 +67,9 @@ export class UploadImageDialogComponent extends return this.data.imageSubType === ResourceSubType.IOT_SVG; } + private iotSvgContent: string; + private iotSvgMetadata: IotSvgMetadata; + constructor(protected store: Store, protected router: Router, private imageService: ImageService, @@ -78,6 +87,20 @@ export class UploadImageDialogComponent extends }); if (this.uploadImage) { this.uploadImageFormGroup.addControl('title', this.fb.control(null, [Validators.required])); + if (this.isScada) { + this.uploadImageFormGroup.get('file').valueChanges.subscribe((file: File) => { + if (file) { + blobToText(file).subscribe(content => { + this.iotSvgContent = content; + this.iotSvgMetadata = parseIotSvgMetadataFromContent(this.iotSvgContent); + const titleControl = this.uploadImageFormGroup.get('title'); + if (this.iotSvgMetadata.title && (!titleControl.value || !titleControl.touched)) { + titleControl.setValue(this.iotSvgMetadata.title); + } + }); + } + }); + } } } @@ -102,9 +125,19 @@ export class UploadImageDialogComponent extends upload(): void { this.submitted = true; - const file: File = this.uploadImageFormGroup.get('file').value; + let file: File = this.uploadImageFormGroup.get('file').value; if (this.uploadImage) { const title: string = this.uploadImageFormGroup.get('title').value; + if (this.isScada) { + if (!this.iotSvgMetadata) { + this.iotSvgMetadata = emptyMetadata(); + } + if (this.iotSvgMetadata.title !== title) { + this.iotSvgMetadata.title = title; + } + const newContent = updateIotSvgMetadataInContent(this.iotSvgContent, this.iotSvgMetadata); + file = updateFileContent(file, newContent); + } forkJoin([ this.imageService.uploadImage(file, title, this.data.imageSubType), blobToBase64(file)