Browse Source

UI: Add Embed Image dialog.

pull/9846/head
Igor Kulikov 3 years ago
parent
commit
562838cfdb
  1. 16
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java
  2. 4
      dao/src/main/java/org/thingsboard/server/dao/resource/ImageCacheKey.java
  3. 10
      ui-ngx/src/app/core/http/image.service.ts
  4. 60
      ui-ngx/src/app/shared/components/image/embed-image-dialog.component.html
  5. 71
      ui-ngx/src/app/shared/components/image/embed-image-dialog.component.scss
  6. 94
      ui-ngx/src/app/shared/components/image/embed-image-dialog.component.ts
  7. 19
      ui-ngx/src/app/shared/components/image/image-dialog.component.html
  8. 33
      ui-ngx/src/app/shared/components/image/image-dialog.component.ts
  9. 22
      ui-ngx/src/app/shared/components/image/image-gallery.component.html
  10. 20
      ui-ngx/src/app/shared/components/image/image-gallery.component.ts
  11. 7
      ui-ngx/src/app/shared/models/resource.models.ts
  12. 7
      ui-ngx/src/app/shared/shared.module.ts
  13. 12
      ui-ngx/src/assets/locale/locale.constant-en_US.json

16
common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java

@ -113,12 +113,16 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getLink() { public String getLink() {
if (resourceType == ResourceType.IMAGE) { if (resourceType == ResourceType.IMAGE) {
if (isPublic) { String type = (tenantId != null && tenantId.isSysTenantId()) ? "system" : "tenant"; // tenantId is null in case of export to git
return "/api/images/public/" + getPublicResourceKey(); return "/api/images/" + type + "/" + resourceKey;
} else { }
String type = (tenantId != null && tenantId.isSysTenantId()) ? "system" : "tenant"; // tenantId is null in case of export to git return null;
return "/api/images/" + type + "/" + resourceKey; }
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getPublicLink() {
if (resourceType == ResourceType.IMAGE && isPublic) {
return "/api/images/public/" + getPublicResourceKey();
} }
return null; return null;
} }

4
dao/src/main/java/org/thingsboard/server/dao/resource/ImageCacheKey.java

@ -55,4 +55,8 @@ public class ImageCacheKey {
return msg.build(); return msg.build();
} }
public boolean isPublic() {
return this.publicResourceKey != null;
}
} }

10
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<ImageResourceInfo> { public uploadImage(file: File, title: string, isPublic = true, config?: RequestConfig): Observable<ImageResourceInfo> {
if (!config) { if (!config) {
config = {}; config = {};
} }
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
formData.append('title', title); formData.append('title', title);
formData.append('isPublic', isPublic ? 'true' : 'false');
return this.http.post<ImageResourceInfo>('/api/image', formData, return this.http.post<ImageResourceInfo>('/api/image', formData,
defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest)); defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest));
} }
@ -69,6 +70,13 @@ export class ImageService {
imageInfo, defaultHttpOptionsFromConfig(config)); imageInfo, defaultHttpOptionsFromConfig(config));
} }
public updateImagePublicStatus(imageInfo: ImageResourceInfo, isPublic: boolean, config?: RequestConfig): Observable<ImageResourceInfo> {
const type = imageResourceType(imageInfo);
const key = encodeURIComponent(imageInfo.resourceKey);
return this.http.put<ImageResourceInfo>(`${IMAGES_URL_PREFIX}/${type}/${key}/public/${isPublic}`,
imageInfo, defaultHttpOptionsFromConfig(config));
}
public getImages(pageLink: PageLink, includeSystemImages = false, config?: RequestConfig): Observable<PageData<ImageResourceInfo>> { public getImages(pageLink: PageLink, includeSystemImages = false, config?: RequestConfig): Observable<PageData<ImageResourceInfo>> {
return this.http.get<PageData<ImageResourceInfo>>( return this.http.get<PageData<ImageResourceInfo>>(
`${IMAGES_URL_PREFIX}${pageLink.toQuery()}&includeSystemImages=${includeSystemImages}`, `${IMAGES_URL_PREFIX}${pageLink.toQuery()}&includeSystemImages=${includeSystemImages}`,

60
ui-ngx/src/app/shared/components/image/embed-image-dialog.component.html

@ -0,0 +1,60 @@
<!--
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.
-->
<mat-toolbar color="primary">
<h2>{{ 'image.embed-image' | translate }}</h2>
<span fxFlex></span>
<button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<div class="tb-form-panel stroked tb-slide-toggle" *ngIf="!readonly || image.public">
<mat-expansion-panel class="tb-settings" [expanded]="image.public" disabled>
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<div *ngIf="!readonly" tb-hint-tooltip-icon="{{ 'image.embed-to-html-hint' | translate }}"
class="tb-form-row no-border no-padding">
<mat-slide-toggle class="mat-slide" [formControl]="publicStatusControl" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
<div class="tb-form-panel-title">{{ 'image.embed-to-html' | translate }}</div>
</mat-slide-toggle>
</div>
<div *ngIf="readonly" class="tb-form-panel-title" translate>image.embed-to-html</div>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-embed-image-text" [innerHTML]="'image.embed-to-html-text' | translate"></div>
<tb-markdown usePlainMarkdown
containerClass="tb-embed-image-code"
[data]="embedToHtmlCode()"></tb-markdown>
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>image.embed-to-angular-template</div>
<div class="tb-embed-image-text" [innerHTML]="'image.embed-to-angular-template-text' | translate"></div>
<tb-markdown usePlainMarkdown
containerClass="tb-embed-image-code"
[data]="embedToAngularTemplateCode()"></tb-markdown>
</div>
</div>

71
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;
}
}
}
}
}
}

94
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<EmbedImageDialogComponent, ImageResourceInfo> implements OnInit {
image = this.data.image;
readonly = this.data.readonly;
imageChanged = false;
publicStatusControl = new FormControl(this.image.public);
constructor(protected store: Store<AppState>,
protected router: Router,
private imageService: ImageService,
@Inject(MAT_DIALOG_DATA) private data: EmbedImageDialogData,
public dialogRef: MatDialogRef<EmbedImageDialogComponent, ImageResourceInfo>,
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' +
'<img src="'+this.image.publicLink+'" alt="'+this.image.title.replace(/"/g, '&quot;')+'" />' +
'{:copy-code}\n' +
'```';
}
embedToAngularTemplateCode(): string {
return '```html\n' +
'<img [src]="\''+this.image.link+'\' | image | async" />' +
'{:copy-code}\n' +
'```';
}
private updateImagePublicStatus(isPublic: boolean): void {
this.imageService.updateImagePublicStatus(this.image, isPublic).subscribe(
(image) => {
this.image = image;
this.imageChanged = true;
}
);
}
}

19
ui-ngx/src/app/shared/components/image/image-dialog.component.html

@ -56,18 +56,6 @@
<mat-icon>check</mat-icon> <mat-icon>check</mat-icon>
</button> </button>
</mat-form-field> </mat-form-field>
<mat-form-field>
<mat-label translate>image.link</mat-label>
<input matInput formControlName="link">
<tb-copy-button
matSuffix
miniButton="false"
[copyText]="imageFormGroup.get('link').value"
tooltipText="{{ 'image.copy-image-link' | translate }}"
tooltipPosition="above"
icon="content_copy">
</tb-copy-button>
</mat-form-field>
<div class="tb-image-container tb-primary-fill"> <div class="tb-image-container tb-primary-fill">
<div class="tb-image-content"> <div class="tb-image-content">
<div class="tb-image-actions"> <div class="tb-image-actions">
@ -86,6 +74,13 @@
(click)="exportImage($event)"> (click)="exportImage($event)">
<tb-icon>mdi:file-export</tb-icon> <tb-icon>mdi:file-export</tb-icon>
</button> </button>
<button type="button"
mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.embed-image' | translate }}"
matTooltipPosition="above"
(click)="embedImage($event)">
<mat-icon>code</mat-icon>
</button>
</div> </div>
<button *ngIf="!readonly" <button *ngIf="!readonly"
type="button" type="button"

33
ui-ngx/src/app/shared/components/image/image-dialog.component.ts

@ -30,6 +30,7 @@ import {
} from '@shared/components/image/upload-image-dialog.component'; } from '@shared/components/image/upload-image-dialog.component';
import { UrlHolder } from '@shared/pipe/image.pipe'; import { UrlHolder } from '@shared/pipe/image.pipe';
import { ImportExportService } from '@shared/import-export/import-export.service'; import { ImportExportService } from '@shared/import-export/import-export.service';
import { EmbedImageDialogComponent, EmbedImageDialogData } from '@shared/components/image/embed-image-dialog.component';
export interface ImageDialogData { export interface ImageDialogData {
readonly: boolean; readonly: boolean;
@ -66,19 +67,16 @@ export class ImageDialogComponent extends
this.image = data.image; this.image = data.image;
this.readonly = data.readonly; this.readonly = data.readonly;
this.imagePreviewData = { this.imagePreviewData = {
url: this.image.link url: this.image.public ? this.image.publicLink : this.image.link
}; };
} }
ngOnInit(): void { ngOnInit(): void {
this.imageFormGroup = this.fb.group({ this.imageFormGroup = this.fb.group({
title: [this.image.title, [Validators.required]], title: [this.image.title, [Validators.required]]
link: [this.image.link, []],
}); });
if (this.data.readonly) { if (this.data.readonly) {
this.imageFormGroup.disable(); this.imageFormGroup.disable();
} else {
this.imageFormGroup.get('link').disable();
} }
} }
@ -117,6 +115,29 @@ export class ImageDialogComponent extends
this.importExportService.exportImage(imageResourceType(this.image), this.image.resourceKey); this.importExportService.exportImage(imageResourceType(this.image), this.image.resourceKey);
} }
embedImage($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<EmbedImageDialogComponent, EmbedImageDialogData,
ImageResourceInfo>(EmbedImageDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
image: this.image,
readonly: this.readonly
}
}).afterClosed().subscribe((result) => {
if (result) {
this.imageChanged = true;
this.image = result;
this.imagePreviewData = {
url: this.image.public ? this.image.publicLink : this.image.link
};
}
});
}
updateImage($event): void { updateImage($event): void {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
@ -133,7 +154,7 @@ export class ImageDialogComponent extends
this.imageChanged = true; this.imageChanged = true;
this.image = result; this.image = result;
this.imagePreviewData = { this.imagePreviewData = {
url: this.image.link url: this.image.public ? this.image.publicLink : this.image.link
}; };
} }
}); });

22
ui-ngx/src/app/shared/components/image/image-gallery.component.html

@ -185,9 +185,9 @@
</mat-cell> </mat-cell>
</ng-container> </ng-container>
<ng-container matColumnDef="actions" stickyEnd> <ng-container matColumnDef="actions" stickyEnd>
<mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: '192px', maxWidth: '192px', width: '192px' }"> <mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: '240px', maxWidth: '240px', width: '240px' }">
</mat-header-cell> </mat-header-cell>
<mat-cell *matCellDef="let image" [ngStyle.gt-md]="{ minWidth: '192px', maxWidth: '192px', width: '192px' }"> <mat-cell *matCellDef="let image" [ngStyle.gt-md]="{ minWidth: '240px', maxWidth: '240px', width: '240px' }">
<div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end"> <div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
<button mat-icon-button [disabled]="isLoading$ | async" <button mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.download-image' | translate }}" matTooltip="{{ 'image.download-image' | translate }}"
@ -201,6 +201,12 @@
(click)="exportImage($event, image)"> (click)="exportImage($event, image)">
<tb-icon>mdi:file-export</tb-icon> <tb-icon>mdi:file-export</tb-icon>
</button> </button>
<button mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.embed-image' | translate }}"
matTooltipPosition="above"
(click)="embedImage($event, image)">
<mat-icon>code</mat-icon>
</button>
<button mat-icon-button [disabled]="isLoading$ | async" <button mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ (readonly(image) ? 'image.image-details' : 'image.edit-image') | translate }}" matTooltip="{{ (readonly(image) ? 'image.image-details' : 'image.edit-image') | translate }}"
matTooltipPosition="above" matTooltipPosition="above"
@ -233,6 +239,12 @@
<tb-icon matMenuItemIcon>mdi:file-export</tb-icon> <tb-icon matMenuItemIcon>mdi:file-export</tb-icon>
<span translate>image.export-image</span> <span translate>image.export-image</span>
</button> </button>
<button mat-menu-item
[disabled]="isLoading$ | async"
(click)="embedImage($event, image)">
<mat-icon>code</mat-icon>
<span>{{ 'image.embed-image' | translate }}</span>
</button>
<button mat-menu-item <button mat-menu-item
[disabled]="isLoading$ | async" [disabled]="isLoading$ | async"
(click)="editImage($event, image)"> (click)="editImage($event, image)">
@ -323,6 +335,12 @@
(click)="exportImage($event, item)"> (click)="exportImage($event, item)">
<tb-icon>mdi:file-export</tb-icon> <tb-icon>mdi:file-export</tb-icon>
</button> </button>
<button mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.embed-image' | translate }}"
matTooltipPosition="above"
(click)="embedImage($event, item, itemIndex)">
<mat-icon>code</mat-icon>
</button>
<button *ngIf="deleteEnabled(item)" <button *ngIf="deleteEnabled(item)"
mat-icon-button [disabled]="(isLoading$ | async)" mat-icon-button [disabled]="(isLoading$ | async)"
matTooltip="{{ 'image.delete-image' | translate }}" matTooltip="{{ 'image.delete-image' | translate }}"

20
ui-ngx/src/app/shared/components/image/image-gallery.component.ts

@ -68,6 +68,7 @@ import {
ImagesInUseDialogData ImagesInUseDialogData
} from '@shared/components/image/images-in-use-dialog.component'; } from '@shared/components/image/images-in-use-dialog.component';
import { ImagesDatasource } from '@shared/components/image/images-datasource'; import { ImagesDatasource } from '@shared/components/image/images-datasource';
import { EmbedImageDialogComponent, EmbedImageDialogData } from '@shared/components/image/embed-image-dialog.component';
interface GridImagesFilter { interface GridImagesFilter {
search: string; search: string;
@ -658,6 +659,25 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
}); });
} }
embedImage($event: Event, image: ImageResourceInfo, itemIndex = -1) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<EmbedImageDialogComponent, EmbedImageDialogData,
ImageResourceInfo>(EmbedImageDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
image,
readonly: this.readonly(image)
}
}).afterClosed().subscribe((result) => {
if (result) {
this.imageUpdated(result, itemIndex);
}
});
}
protected updatedRouterParamsAndData(queryParams: object, queryParamsHandling: QueryParamsHandling = 'merge') { protected updatedRouterParamsAndData(queryParams: object, queryParamsHandling: QueryParamsHandling = 'merge') {
if (this.pageMode) { if (this.pageMode) {
this.router.navigate([], { this.router.navigate([], {

7
ui-ngx/src/app/shared/models/resource.models.ts

@ -60,6 +60,8 @@ export interface TbResourceInfo<D> extends Omit<BaseData<TbResourceId>, 'name' |
title?: string; title?: string;
resourceType: ResourceType; resourceType: ResourceType;
fileName: string; fileName: string;
public: boolean;
publicResourceKey?: string;
descriptor?: D; descriptor?: D;
} }
@ -81,6 +83,7 @@ export interface ImageDescriptor {
export interface ImageResourceInfo extends TbResourceInfo<ImageDescriptor> { export interface ImageResourceInfo extends TbResourceInfo<ImageDescriptor> {
link?: string; link?: string;
publicLink?: string;
} }
export interface ImageExportData { export interface ImageExportData {
@ -88,6 +91,8 @@ export interface ImageExportData {
fileName: string; fileName: string;
title: string; title: string;
resourceKey: string; resourceKey: string;
public: boolean;
publicResourceKey: string;
data: string; data: string;
} }
@ -129,6 +134,8 @@ export const TB_IMAGE_PREFIX = 'tb-image;';
export const IMAGES_URL_REGEXP = /\/api\/images\/(tenant|system)\/(.*)/; export const IMAGES_URL_REGEXP = /\/api\/images\/(tenant|system)\/(.*)/;
export const IMAGES_URL_PREFIX = '/api/images'; export const IMAGES_URL_PREFIX = '/api/images';
export const PUBLIC_IMAGES_URL_PREFIX = '/api/images/public';
export const IMAGE_BASE64_URL_PREFIX = 'data:image/'; export const IMAGE_BASE64_URL_PREFIX = 'data:image/';
export const removeTbImagePrefix = (url: string): string => url ? url.replace(TB_IMAGE_PREFIX, '') : url; export const removeTbImagePrefix = (url: string): string => url ? url.replace(TB_IMAGE_PREFIX, '') : url;

7
ui-ngx/src/app/shared/shared.module.ts

@ -214,6 +214,7 @@ import { ImageReferencesComponent } from '@shared/components/image/image-referen
import { ImagesInUseDialogComponent } from '@shared/components/image/images-in-use-dialog.component'; import { ImagesInUseDialogComponent } from '@shared/components/image/images-in-use-dialog.component';
import { GalleryImageInputComponent } from '@shared/components/image/gallery-image-input.component'; import { GalleryImageInputComponent } from '@shared/components/image/gallery-image-input.component';
import { MultipleGalleryImageInputComponent } from '@shared/components/image/multiple-gallery-image-input.component'; import { MultipleGalleryImageInputComponent } from '@shared/components/image/multiple-gallery-image-input.component';
import { EmbedImageDialogComponent } from '@shared/components/image/embed-image-dialog.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService; return markedOptionsService;
@ -408,7 +409,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
ImageReferencesComponent, ImageReferencesComponent,
ImagesInUseDialogComponent, ImagesInUseDialogComponent,
GalleryImageInputComponent, GalleryImageInputComponent,
MultipleGalleryImageInputComponent MultipleGalleryImageInputComponent,
EmbedImageDialogComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -657,7 +659,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
ImageReferencesComponent, ImageReferencesComponent,
ImagesInUseDialogComponent, ImagesInUseDialogComponent,
GalleryImageInputComponent, GalleryImageInputComponent,
MultipleGalleryImageInputComponent MultipleGalleryImageInputComponent,
EmbedImageDialogComponent
] ]
}) })
export class SharedModule { } export class SharedModule { }

12
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2963,8 +2963,8 @@
"size": "Size", "size": "Size",
"system": "System", "system": "System",
"download-image": "Download image", "download-image": "Download image",
"export-image": "Export image", "export-image": "Export image to JSON",
"import-image": "Import image", "import-image": "Import image from JSON",
"upload-image": "Upload image", "upload-image": "Upload image",
"edit-image": "Edit image", "edit-image": "Edit image",
"image-details": "Image details", "image-details": "Image details",
@ -2997,7 +2997,13 @@
"set-link": "Set link", "set-link": "Set link",
"image-link": "Image link", "image-link": "Image link",
"link": "Link", "link": "Link",
"copy-image-link": "Copy image link" "copy-image-link": "Copy image link",
"embed-image": "Embed image",
"embed-to-html": "Embed to HTML",
"embed-to-html-hint": "This feature will make link available to any unauthorized user.",
"embed-to-html-text": "Using the following code snippet, you may embed an image into the components based on the plain HTML.<br/>Such components include HTML card widgets, cell content functions, etc.",
"embed-to-angular-template": "Embed to Angular HTML template",
"embed-to-angular-template-text": "Using the following code snippet, you may embed an image into the Angular HTML template.<br/>Such components include the Markdown widget, HTML section in the widget editor, custom actions, etc."
}, },
"image-input": { "image-input": {
"drop-images-or": "Drag and drop an images or", "drop-images-or": "Drag and drop an images or",

Loading…
Cancel
Save