From 562838cfdbd6da0ff565009fb345f65e239d985b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 15 Dec 2023 17:10:33 +0200 Subject: [PATCH] UI: Add Embed Image dialog. --- .../server/common/data/TbResourceInfo.java | 16 ++-- .../server/dao/resource/ImageCacheKey.java | 4 + ui-ngx/src/app/core/http/image.service.ts | 10 +- .../image/embed-image-dialog.component.html | 60 ++++++++++++ .../image/embed-image-dialog.component.scss | 71 ++++++++++++++ .../image/embed-image-dialog.component.ts | 94 +++++++++++++++++++ .../image/image-dialog.component.html | 19 ++-- .../image/image-dialog.component.ts | 33 +++++-- .../image/image-gallery.component.html | 22 ++++- .../image/image-gallery.component.ts | 20 ++++ .../src/app/shared/models/resource.models.ts | 7 ++ ui-ngx/src/app/shared/shared.module.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 12 ++- 13 files changed, 343 insertions(+), 32 deletions(-) create mode 100644 ui-ngx/src/app/shared/components/image/embed-image-dialog.component.html create mode 100644 ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss create mode 100644 ui-ngx/src/app/shared/components/image/embed-image-dialog.component.ts diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java index 96d258907e..6b485488b0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java @@ -113,12 +113,16 @@ public class TbResourceInfo extends BaseData implements HasName, H @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getLink() { if (resourceType == ResourceType.IMAGE) { - if (isPublic) { - return "/api/images/public/" + getPublicResourceKey(); - } else { - String type = (tenantId != null && tenantId.isSysTenantId()) ? "system" : "tenant"; // tenantId is null in case of export to git - return "/api/images/" + type + "/" + resourceKey; - } + String type = (tenantId != null && tenantId.isSysTenantId()) ? "system" : "tenant"; // tenantId is null in case of export to git + return "/api/images/" + type + "/" + resourceKey; + } + return null; + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getPublicLink() { + if (resourceType == ResourceType.IMAGE && isPublic) { + return "/api/images/public/" + getPublicResourceKey(); } return null; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/ImageCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/resource/ImageCacheKey.java index 5a1dc108bd..82efa3c434 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/ImageCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/ImageCacheKey.java @@ -55,4 +55,8 @@ public class ImageCacheKey { return msg.build(); } + public boolean isPublic() { + return this.publicResourceKey != null; + } + } diff --git a/ui-ngx/src/app/core/http/image.service.ts b/ui-ngx/src/app/core/http/image.service.ts index 3fcba170f4..b839e0975c 100644 --- a/ui-ngx/src/app/core/http/image.service.ts +++ b/ui-ngx/src/app/core/http/image.service.ts @@ -41,13 +41,14 @@ export class ImageService { ) { } - public uploadImage(file: File, title: string, config?: RequestConfig): Observable { + public uploadImage(file: File, title: string, isPublic = true, config?: RequestConfig): Observable { if (!config) { config = {}; } const formData = new FormData(); formData.append('file', file); formData.append('title', title); + formData.append('isPublic', isPublic ? 'true' : 'false'); return this.http.post('/api/image', formData, defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest)); } @@ -69,6 +70,13 @@ export class ImageService { imageInfo, defaultHttpOptionsFromConfig(config)); } + public updateImagePublicStatus(imageInfo: ImageResourceInfo, isPublic: boolean, config?: RequestConfig): Observable { + const type = imageResourceType(imageInfo); + const key = encodeURIComponent(imageInfo.resourceKey); + return this.http.put(`${IMAGES_URL_PREFIX}/${type}/${key}/public/${isPublic}`, + imageInfo, defaultHttpOptionsFromConfig(config)); + } + public getImages(pageLink: PageLink, includeSystemImages = false, config?: RequestConfig): Observable> { return this.http.get>( `${IMAGES_URL_PREFIX}${pageLink.toQuery()}&includeSystemImages=${includeSystemImages}`, diff --git a/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.html b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.html new file mode 100644 index 0000000000..bd12430e98 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.html @@ -0,0 +1,60 @@ + + +

{{ 'image.embed-image' | translate }}

+ + +
+ + +
+
+
+ + + +
+ +
{{ 'image.embed-to-html' | translate }}
+
+
+
image.embed-to-html
+
+
+ +
+ +
+
+
+
+
image.embed-to-angular-template
+
+ +
+
diff --git a/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss new file mode 100644 index 0000000000..b769de7840 --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss @@ -0,0 +1,71 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .mat-mdc-dialog-content { + display: flex; + flex-direction: column; + gap: 16px; + .tb-embed-image-text { + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: rgba(0,0,0,0.54); + letter-spacing: 0.2px; + } + .tb-form-panel-title { + color: rgba(0, 0, 0, 0.87); + } + } +} + +:host ::ng-deep { + .tb-markdown-view { + max-width: 700px; + .tb-embed-image-code { + .code-wrapper { + padding: 0; + pre[class*=language-] { + margin: 0; + padding: 9px 38px 9px 16px; + } + code[class*="language-"], pre[class*="language-"] { + font-size: 12px; + overflow: hidden; + white-space: normal; + word-break: break-word; + } + button.clipboard-btn { + right: 0; + height: 36px; + p, div { + background: transparent; + } + p { + margin: 0; + padding: 6px; + font-size: 14px; + } + div { + top: 0; + padding: 8px; + height: 38px; + width: 38px; + } + } + } + } + } +} diff --git a/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.ts b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.ts new file mode 100644 index 0000000000..b2c1d19e1f --- /dev/null +++ b/ui-ngx/src/app/shared/components/image/embed-image-dialog.component.ts @@ -0,0 +1,94 @@ +/// +/// Copyright © 2016-2023 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ImageResourceInfo } from '@shared/models/resource.models'; +import { Component, Inject, OnInit } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { ImageService } from '@core/http/image.service'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { FormControl, UntypedFormBuilder } from '@angular/forms'; + +export interface EmbedImageDialogData { + readonly: boolean; + image: ImageResourceInfo; +} + +@Component({ + selector: 'tb-embed-image-dialog', + templateUrl: './embed-image-dialog.component.html', + styleUrls: ['./embed-image-dialog.component.scss'] +}) +export class EmbedImageDialogComponent extends + DialogComponent implements OnInit { + + image = this.data.image; + + readonly = this.data.readonly; + + imageChanged = false; + + publicStatusControl = new FormControl(this.image.public); + + constructor(protected store: Store, + protected router: Router, + private imageService: ImageService, + @Inject(MAT_DIALOG_DATA) private data: EmbedImageDialogData, + public dialogRef: MatDialogRef, + public fb: UntypedFormBuilder) { + super(store, router, dialogRef); + } + + ngOnInit(): void { + if (!this.readonly) { + this.publicStatusControl.valueChanges.subscribe( + (isPublic) => { + this.updateImagePublicStatus(isPublic); + } + ); + } + } + + cancel(): void { + this.dialogRef.close(this.imageChanged ? this.image : null); + } + + embedToHtmlCode(): string { + return '```html\n' + + ''+this.image.title.replace(/' + + '{:copy-code}\n' + + '```'; + } + + embedToAngularTemplateCode(): string { + return '```html\n' + + '' + + '{:copy-code}\n' + + '```'; + } + + private updateImagePublicStatus(isPublic: boolean): void { + this.imageService.updateImagePublicStatus(this.image, isPublic).subscribe( + (image) => { + this.image = image; + this.imageChanged = true; + } + ); + } + +} diff --git a/ui-ngx/src/app/shared/components/image/image-dialog.component.html b/ui-ngx/src/app/shared/components/image/image-dialog.component.html index 05bc4e943c..5341c94ad5 100644 --- a/ui-ngx/src/app/shared/components/image/image-dialog.component.html +++ b/ui-ngx/src/app/shared/components/image/image-dialog.component.html @@ -56,18 +56,6 @@ check - - image.link - - - -
@@ -86,6 +74,13 @@ (click)="exportImage($event)"> mdi:file-export +
+ + +