Merge branch 'feature/image-resources' of github.com:thingsboard/thingsboard into feature/image-resources

This commit is contained in:
Andrii Shvaika 2023-11-27 18:39:52 +02:00
commit 25a1c551c7
30 changed files with 628 additions and 96 deletions

View File

@ -40,8 +40,8 @@
<input matInput formControlName="left" type="number" step="1" min="0" max="100"> <input matInput formControlName="left" type="number" step="1" min="0" max="100">
</mat-form-field> </mat-form-field>
<div class="tb-image-preview-container"> <div class="tb-image-preview-container">
<div *ngIf="!safeImageUrl; else elseBlock">{{ 'dashboard.no-image' | translate }}</div> <div *ngIf="!imageUrl; else elseBlock">{{ 'dashboard.no-image' | translate }}</div>
<ng-template #elseBlock><img class="tb-image-preview" [src]="safeImageUrl" /></ng-template> <ng-template #elseBlock><img class="tb-image-preview" [src]="imageUrl | image | async" /></ng-template>
</div> </div>
<mat-form-field class="rect-field"> <mat-form-field class="rect-field">
<mat-label>Right %</mat-label> <mat-label>Right %</mat-label>
@ -62,9 +62,9 @@
</button> </button>
</div> </div>
<div [formGroup]="dashboardImageFormGroup"> <div [formGroup]="dashboardImageFormGroup">
<tb-image-input [showPreview]="false" label="{{'dashboard.image' | translate}}" <tb-gallery-image-input label="{{'dashboard.image' | translate}}"
formControlName="dashboardImage"> formControlName="dashboardImage">
</tb-image-input> </tb-gallery-image-input>
</div> </div>
</fieldset> </fieldset>
<div *ngIf="takingScreenshot$ | async" class="taking-screenshot-progress tb-absolute-fill" fxLayout="column" <div *ngIf="takingScreenshot$ | async" class="taking-screenshot-progress tb-absolute-fill" fxLayout="column"

View File

@ -53,7 +53,7 @@ export class DashboardImageDialogComponent extends DialogComponent<DashboardImag
); );
dashboardId: DashboardId; dashboardId: DashboardId;
safeImageUrl?: SafeUrl; imageUrl?: string;
dashboardElement: HTMLElement; dashboardElement: HTMLElement;
dashboardRectFormGroup: UntypedFormGroup; dashboardRectFormGroup: UntypedFormGroup;
@ -165,10 +165,6 @@ export class DashboardImageDialogComponent extends DialogComponent<DashboardImag
} }
private updateImage(imageUrl: string) { private updateImage(imageUrl: string) {
if (imageUrl) { this.imageUrl = imageUrl;
this.safeImageUrl = this.sanitizer.bypassSecurityTrustUrl(imageUrl);
} else {
this.safeImageUrl = null;
}
} }
} }

View File

@ -70,7 +70,7 @@
[syncStateWithQueryParam]="syncStateWithQueryParam" [syncStateWithQueryParam]="syncStateWithQueryParam"
[states]="dashboardConfiguration.states"> [states]="dashboardConfiguration.states">
</tb-states-component> </tb-states-component>
<img *ngIf="showDashboardLogo()" [src]="dashboardLogo" <img *ngIf="showDashboardLogo()" [src]="dashboardLogo | image | async"
aria-label="dashboard_logo" class="dashboard_logo"/> aria-label="dashboard_logo" class="dashboard_logo"/>
</div> </div>
<div class="tb-dashboard-action-panel"> <div class="tb-dashboard-action-panel">

View File

@ -668,12 +668,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
} }
} }
public get dashboardLogo(): SafeUrl { public get dashboardLogo(): string {
if (!this.dashboardLogoCache) { return this.dashboard.configuration.settings.dashboardLogoUrl || this.defaultDashboardLogo;
const logo = this.dashboard.configuration.settings.dashboardLogoUrl || this.defaultDashboardLogo;
this.dashboardLogoCache = this.sanitizer.bypassSecurityTrustUrl(logo);
}
return this.dashboardLogoCache;
} }
public showRightLayoutSwitch(): boolean { public showRightLayoutSwitch(): boolean {

View File

@ -55,10 +55,10 @@
<mat-slide-toggle formControlName="showDashboardLogo"> <mat-slide-toggle formControlName="showDashboardLogo">
{{ 'dashboard.display-dashboard-logo' | translate }} {{ 'dashboard.display-dashboard-logo' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<tb-image-input fxFlex <tb-gallery-image-input fxFlex
label="{{'dashboard.dashboard-logo-image' | translate}}" label="{{'dashboard.dashboard-logo-image' | translate}}"
formControlName="dashboardLogoUrl"> formControlName="dashboardLogoUrl">
</tb-image-input> </tb-gallery-image-input>
</fieldset> </fieldset>
<fieldset class="fields-group" fxLayout="column" fxLayoutGap="8px"> <fieldset class="fields-group" fxLayout="column" fxLayoutGap="8px">
<legend class="group-title" translate>dashboard.toolbar-settings</legend> <legend class="group-title" translate>dashboard.toolbar-settings</legend>
@ -144,10 +144,10 @@
openOnInput openOnInput
formControlName="backgroundColor"> formControlName="backgroundColor">
</tb-color-input> </tb-color-input>
<tb-image-input fxFlex <tb-gallery-image-input fxFlex
label="{{'dashboard.background-image' | translate}}" label="{{'dashboard.background-image' | translate}}"
formControlName="backgroundImageUrl"> formControlName="backgroundImageUrl">
</tb-image-input> </tb-gallery-image-input>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>dashboard.background-size-mode</mat-label> <mat-label translate>dashboard.background-size-mode</mat-label>
<mat-select formControlName="backgroundSizeMode"> <mat-select formControlName="backgroundSizeMode">

View File

@ -23,7 +23,7 @@
<div class="mat-content" <div class="mat-content"
style="position: relative; width: 100%; height: 100%;" tb-hotkeys [hotkeys]="hotKeys" style="position: relative; width: 100%; height: 100%;" tb-hotkeys [hotkeys]="hotKeys"
[cheatSheet]="dashboardCheatSheet" [cheatSheet]="dashboardCheatSheet"
[style.backgroundImage]="backgroundImage" [style.backgroundImage]="backgroundImage$ | async"
[ngStyle]="dashboardStyle"> [ngStyle]="dashboardStyle">
<section *ngIf="layoutCtx.widgets.isEmpty()" fxLayoutAlign="center center" <section *ngIf="layoutCtx.widgets.isEmpty()" fxLayoutAlign="center center"
style="display: flex; z-index: 1; pointer-events: none;" style="display: flex; z-index: 1; pointer-events: none;"
@ -38,7 +38,7 @@
</button> </button>
</section> </section>
<tb-dashboard #dashboard [dashboardStyle]="dashboardStyle" <tb-dashboard #dashboard [dashboardStyle]="dashboardStyle"
[backgroundImage]="backgroundImage" [backgroundImage]="backgroundImage$ | async"
[widgets]="layoutCtx.widgets" [widgets]="layoutCtx.widgets"
[widgetLayouts]="layoutCtx.widgetLayouts" [widgetLayouts]="layoutCtx.widgetLayouts"
[columns]="layoutCtx.gridSettings.columns" [columns]="layoutCtx.gridSettings.columns"

View File

@ -27,13 +27,15 @@ import {
IDashboardComponent, IDashboardComponent,
WidgetContextMenuItem WidgetContextMenuItem
} from '@home/models/dashboard-component.models'; } from '@home/models/dashboard-component.models';
import { Subscription } from 'rxjs'; import { Observable, of, Subscription } from 'rxjs';
import { Hotkey } from 'angular2-hotkeys'; import { Hotkey } from 'angular2-hotkeys';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ItemBufferService } from '@app/core/services/item-buffer.service'; import { ItemBufferService } from '@app/core/services/item-buffer.service';
import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component'; import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { map } from 'rxjs/operators';
@Component({ @Component({
selector: 'tb-dashboard-layout', selector: 'tb-dashboard-layout',
@ -44,7 +46,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
layoutCtxValue: DashboardPageLayoutContext; layoutCtxValue: DashboardPageLayoutContext;
dashboardStyle: {[klass: string]: any} = null; dashboardStyle: {[klass: string]: any} = null;
backgroundImage: SafeStyle | string; backgroundImage$: Observable<SafeStyle | string>;
hotKeys: Hotkey[] = []; hotKeys: Hotkey[] = [];
@ -92,6 +94,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private translate: TranslateService, private translate: TranslateService,
private itembuffer: ItemBufferService, private itembuffer: ItemBufferService,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer) { private sanitizer: DomSanitizer) {
super(store); super(store);
this.initHotKeys(); this.initHotKeys();
@ -188,8 +191,10 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
'background-attachment': 'scroll', 'background-attachment': 'scroll',
'background-size': this.layoutCtx.gridSettings.backgroundSizeMode || '100%', 'background-size': this.layoutCtx.gridSettings.backgroundSizeMode || '100%',
'background-position': '0% 0%'}; 'background-position': '0% 0%'};
this.backgroundImage = this.layoutCtx.gridSettings.backgroundImageUrl ? this.backgroundImage$ = this.layoutCtx.gridSettings.backgroundImageUrl ?
this.sanitizer.bypassSecurityTrustStyle('url(' + this.layoutCtx.gridSettings.backgroundImageUrl + ')') : 'none'; this.imagePipe.transform(this.layoutCtx.gridSettings.backgroundImageUrl, {asString: true, ignoreLoadingImage: true}).pipe(
map((imageUrl) => this.sanitizer.bypassSecurityTrustStyle('url(' + imageUrl + ')'))
) : of('none');
} }
reload() { reload() {

View File

@ -181,13 +181,6 @@ import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delet
import { import {
ExportWidgetsBundleDialogComponent ExportWidgetsBundleDialogComponent
} from '@home/components/import-export/export-widgets-bundle-dialog.component'; } from '@home/components/import-export/export-widgets-bundle-dialog.component';
import { ScrollGridComponent } from '@home/components/grid/scroll-grid.component';
import { ImageGalleryComponent } from '@home/components/image/image-gallery.component';
import { UploadImageDialogComponent } from '@home/components/image/upload-image-dialog.component';
import { ImageDialogComponent } from '@home/components/image/image-dialog.component';
import { ImagesInUseDialogComponent } from '@home/components/image/images-in-use-dialog.component';
import { ImageReferencesComponent } from '@home/components/image/image-references.component';
import { GalleryImageInputComponent } from '@home/components/image/gallery-image-input.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -332,14 +325,7 @@ import { GalleryImageInputComponent } from '@home/components/image/gallery-image
RateLimitsComponent, RateLimitsComponent,
RateLimitsTextComponent, RateLimitsTextComponent,
RateLimitsDetailsDialogComponent, RateLimitsDetailsDialogComponent,
SendNotificationButtonComponent, SendNotificationButtonComponent
ScrollGridComponent,
ImageGalleryComponent,
UploadImageDialogComponent,
ImageDialogComponent,
ImageReferencesComponent,
ImagesInUseDialogComponent,
GalleryImageInputComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -477,14 +463,7 @@ import { GalleryImageInputComponent } from '@home/components/image/gallery-image
RateLimitsComponent, RateLimitsComponent,
RateLimitsTextComponent, RateLimitsTextComponent,
RateLimitsDetailsDialogComponent, RateLimitsDetailsDialogComponent,
SendNotificationButtonComponent, SendNotificationButtonComponent
ScrollGridComponent,
ImageGalleryComponent,
UploadImageDialogComponent,
ImageDialogComponent,
ImageReferencesComponent,
ImagesInUseDialogComponent,
GalleryImageInputComponent
], ],
providers: [ providers: [
WidgetComponentService, WidgetComponentService,

View File

@ -68,6 +68,7 @@
<div *ngIf="linkType === ImageLinkType.none && !disabled" class="tb-image-select-buttons-container"> <div *ngIf="linkType === ImageLinkType.none && !disabled" class="tb-image-select-buttons-container">
<button #browseGalleryButton <button #browseGalleryButton
mat-stroked-button mat-stroked-button
type="button"
color="primary" color="primary"
class="tb-image-select-button" class="tb-image-select-button"
(click)="toggleGallery($event, browseGalleryButton)"> (click)="toggleGallery($event, browseGalleryButton)">
@ -75,6 +76,7 @@
<span translate>image.browse-from-gallery</span> <span translate>image.browse-from-gallery</span>
</button> </button>
<button mat-stroked-button <button mat-stroked-button
type="button"
color="primary" color="primary"
class="tb-image-select-button" class="tb-image-select-button"
(click)="setLink($event)"> (click)="setLink($event)">

View File

@ -40,7 +40,7 @@ import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service'; import { TbPopoverService } from '@shared/components/popover.service';
import { ImageGalleryComponent } from '@home/components/image/image-gallery.component'; import { ImageGalleryComponent } from '@home/components/image/image-gallery.component';
enum ImageLinkType { export enum ImageLinkType {
none = 'none', none = 'none',
base64 = 'base64', base64 = 'base64',
external = 'external', external = 'external',

View File

@ -0,0 +1,111 @@
<!--
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.
-->
<div class="tb-container">
<label class="tb-title" *ngIf="label" [ngClass]="{'tb-error': !disabled && (required && !imageUrls.length), 'tb-required': !disabled && required}">{{label}}</label>
<div class="images-container" dndDropzone [dndHorizontal]="true" dndEffectAllowed="move"
[dndDisableIf]="disabled" (dndDrop)="imageDrop($event)"
fxLayout="row" fxLayoutGap="8px" [ngClass]="{'no-images': !imageUrls.length}">
<div dndPlaceholderRef class="image-card image-dnd-placeholder"></div>
<div *ngFor="let imageUrl of imageUrls; let $index = index;"
(dndStart)="imageDragStart($index)"
(dndEnd)="imageDragEnd()"
[dndDraggable]="imageUrl"
[dndDisableIf]="disabled"
dndEffectAllowed="move"
[ngClass]="{'image-dragging': dragIndex === $index}"
class="image-card" fxLayout="column">
<span class="image-title">{{ 'image-input.images' | translate }} [{{ $index }}]</span>
<div class="image-content-container" fxLayout="row">
<div dndHandle *ngIf="!disabled" class="tb-image-action-container tb-drag-handle">
<mat-icon color="primary">drag_indicator</mat-icon>
</div>
<div class="tb-image-preview-container">
<img class="tb-image-preview" [src]="imageUrl | image: {preview: true} | async" />
</div>
<div *ngIf="!disabled" class="tb-image-action-container">
<button mat-icon-button color="primary"
type="button"
(click)="clearImage($index)"
matTooltip="{{ 'action.remove' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</div>
<div class="no-images-prompt" *ngIf="!imageUrls.length">{{ 'image-input.no-images' | translate }}</div>
</div>
<div *ngIf="!disabled" class="tb-image-select-container">
<div class="tb-image-container" *ngIf="linkType === ImageLinkType.external">
<img *ngIf="externalLinkControl.value; else noImage" class="tb-image-preview" [src]="externalLinkControl.value | image | async">
</div>
<div *ngIf="linkType === ImageLinkType.external" class="tb-image-info-container">
<div class="tb-external-image-container">
<div class="tb-external-link-label">
{{ 'image.image-link' | translate }}
</div>
<div class="tb-external-link-input-container">
<mat-form-field class="tb-inline-field" appearance="outline" subscriptSizing="dynamic">
<input matInput [formControl]="externalLinkControl" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<button class="tb-image-decline-btn"
type="button"
mat-icon-button
matTooltip="{{ 'action.decline' | translate }}"
matTooltipPosition="above"
(click)="declineLink($event)">
<mat-icon>close</mat-icon>
</button>
<button class="tb-image-apply-btn"
type="button"
[disabled]="!externalLinkControl.value"
color="primary"
mat-icon-button
matTooltip="{{ 'action.apply' | translate }}"
matTooltipPosition="above"
(click)="applyLink($event)">
<mat-icon>check</mat-icon>
</button>
</div>
</div>
</div>
<div *ngIf="linkType === ImageLinkType.none" class="tb-image-select-buttons-container">
<button #browseGalleryButton
mat-stroked-button
type="button"
color="primary"
class="tb-image-select-button"
(click)="toggleGallery($event, browseGalleryButton)">
<tb-icon matButtonIcon>filter</tb-icon>
<span translate>image.browse-from-gallery</span>
</button>
<button mat-stroked-button
type="button"
color="primary"
class="tb-image-select-button"
(click)="setLink($event)">
<tb-icon matButtonIcon>link</tb-icon>
<span translate>image.set-link</span>
</button>
</div>
</div>
</div>
<ng-template #noImage>
<div class="tb-no-image">{{ 'image.no-image-selected' | translate }}</div>
</ng-template>

View File

@ -0,0 +1,225 @@
/**
* 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 "../../../../../scss/constants";
$imagesContainerHeight: 106px !default;
$selectContainerHeight: 96px !default;
$previewSize: 64px !default;
.image-card {
margin-bottom: 8px;
&.image-dnd-placeholder {
height: 82px;
width: 146px;
border: 2px dashed rgba(0, 0, 0, 0.2);
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
&.image-dragging {
display: none !important;
}
.image-title {
font-size: 11px;
font-weight: 400;
line-height: 14px;
color: rgba(0, 0, 0, 0.6);
padding-bottom: 4px;
}
.image-content-container {
background: #FFFFFF;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
height: $previewSize;
}
.tb-image-preview {
width: auto;
max-width: $previewSize - 2px;
height: auto;
max-height: $previewSize - 2px;
}
.tb-image-preview-container {
position: relative;
width: $previewSize;
height: $previewSize;
margin-top: -1px;
margin-bottom: -1px;
border: 1px solid rgba(0, 0, 0, 0.54);
.tb-image-preview {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.tb-image-action-container {
position: relative;
height: $previewSize - 2px;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
}
}
:host {
.tb-container {
margin-top: 0;
padding: 0 0 16px;
display: flex;
flex-direction: column;
gap: 8px;
label.tb-title {
display: block;
padding-bottom: 0;
}
}
.images-container {
padding: 12px 12px 4px;
background: rgba(0, 0, 0, 0.03);
border-radius: 4px;
flex-wrap: wrap;
margin-bottom: 8px;
&.no-images {
height: $imagesContainerHeight;
padding-bottom: 12px;
align-items: center;
justify-content: center;
}
}
.no-images-prompt {
font-size: 18px;
color: rgba(0, 0, 0, 0.54);
}
.tb-image-select-container {
width: 100%;
height: $selectContainerHeight;
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.12);
display: flex;
align-items: center;
.tb-image-container {
width: $selectContainerHeight - 1px;
height: $selectContainerHeight - 2px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px;
border-right: 1px solid rgba(0, 0, 0, 0.12);
background: #fff;
overflow: hidden;
.tb-image-preview {
width: auto;
max-width: $selectContainerHeight - 2px;
height: auto;
max-height: $selectContainerHeight - 2px;
}
.tb-no-image {
text-align: center;
color: rgba(0, 0, 0, 0.38);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
letter-spacing: 0.4px;
}
}
.tb-image-info-container {
display: flex;
flex: 1;
align-self: stretch;
padding: 0 8px;
justify-content: flex-end;
align-items: center;
gap: 4px;
.tb-external-image-container {
display: flex;
flex: 1;
align-self: stretch;
padding: 16px 8px 0 16px;
flex-direction: column;
align-items: flex-start;
gap: 4px;
.tb-external-link-label {
color: rgba(0, 0, 0, 0.54);
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
letter-spacing: 0.4px;
}
.tb-external-link-input-container {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 4px;
align-self: stretch;
.tb-inline-field {
width: 100%;
}
.tb-image-decline-btn {
color: rgba(0,0,0,0.38);
}
}
}
}
.tb-image-select-buttons-container {
display: flex;
flex: 1;
align-self: stretch;
padding: 8px;
gap: 8px;
justify-content: center;
align-items: flex-start;
.tb-image-select-button {
width: 100%;
height: 100%;
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 4px;
padding: 8px;
line-height: normal;
font-size: 12px;
@media #{$mat-gt-xs} {
padding: 16px;
}
.mat-icon {
width: 24px;
height: 24px;
font-size: 24px;
margin: 0;
}
}
}
}
}

View File

@ -0,0 +1,180 @@
///
/// 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 { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, } from '@angular/forms';
import { moveItemInArray } from '@angular/cdk/drag-drop';
import { DndDropEvent } from 'ngx-drag-drop';
import { isUndefined } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
import { ImageLinkType } from '@home/components/image/gallery-image-input.component';
import { TbPopoverService } from '@shared/components/popover.service';
import { MatButton } from '@angular/material/button';
import { ImageGalleryComponent } from '@home/components/image/image-gallery.component';
@Component({
selector: 'tb-multiple-gallery-image-input',
templateUrl: './multiple-gallery-image-input.component.html',
styleUrls: ['./multiple-gallery-image-input.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MultipleGalleryImageInputComponent),
multi: true
}
]
})
export class MultipleGalleryImageInputComponent extends PageComponent implements OnDestroy, ControlValueAccessor {
@Input()
label: string;
@Input()
@coerceBoolean()
required = false;
@Input()
disabled: boolean;
imageUrls: string[];
ImageLinkType = ImageLinkType;
linkType: ImageLinkType = ImageLinkType.none;
externalLinkControl = new FormControl(null);
dragIndex: number;
private propagateChange = null;
constructor(protected store: Store<AppState>,
private cd: ChangeDetectorRef,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef,
private popoverService: TbPopoverService) {
super(store);
}
ngOnDestroy() {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
writeValue(value: string[]): void {
this.reset();
this.imageUrls = value || [];
}
private updateModel() {
this.cd.markForCheck();
this.propagateChange(this.imageUrls);
}
private reset() {
this.linkType = ImageLinkType.none;
this.externalLinkControl.setValue(null, {emitEvent: false});
}
clearImage(index: number) {
this.imageUrls.splice(index, 1);
this.updateModel();
}
setLink($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.linkType = ImageLinkType.external;
}
declineLink($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.reset();
}
applyLink($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.imageUrls.push(this.externalLinkControl.value);
this.reset();
this.updateModel();
}
toggleGallery($event: Event, browseGalleryButton: MatButton) {
if ($event) {
$event.stopPropagation();
}
const trigger = browseGalleryButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const ctx: any = {
pageMode: false,
popoverMode: true,
mode: 'grid',
selectionMode: true
};
const imageGalleryPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, ImageGalleryComponent, 'top', true, null,
ctx,
{},
{}, {}, true);
imageGalleryPopover.tbComponentRef.instance.imageSelected.subscribe((image) => {
imageGalleryPopover.hide();
this.imageUrls.push(image.link);
this.updateModel();
});
}
}
imageDragStart(index: number) {
setTimeout(() => {
this.dragIndex = index;
this.cd.markForCheck();
});
}
imageDragEnd() {
this.dragIndex = -1;
this.cd.markForCheck();
}
imageDrop(event: DndDropEvent) {
let index = event.index;
if (isUndefined(index)) {
index = this.imageUrls.length;
}
moveItemInArray(this.imageUrls, this.dragIndex, index);
this.dragIndex = -1;
this.updateModel();
}
}

View File

@ -78,11 +78,10 @@
{{ 'device-profile.type-required' | translate }} {{ 'device-profile.type-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<tb-image-input fxFlex <tb-gallery-image-input fxFlex
label="{{'device-profile.image' | translate}}" label="{{'device-profile.image' | translate}}"
maxSizeByte="524288"
formControlName="image"> formControlName="image">
</tb-image-input> </tb-gallery-image-input>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>device-profile.description</mat-label> <mat-label translate>device-profile.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea> <textarea matInput formControlName="description" rows="2"></textarea>

View File

@ -83,11 +83,10 @@
[ruleChainType]="edgeRuleChainType"> [ruleChainType]="edgeRuleChainType">
<span tb-hint>{{'asset-profile.default-edge-rule-chain-hint' | translate}}</span> <span tb-hint>{{'asset-profile.default-edge-rule-chain-hint' | translate}}</span>
</tb-rule-chain-autocomplete> </tb-rule-chain-autocomplete>
<tb-image-input fxFlex <tb-gallery-image-input fxFlex
label="{{'asset-profile.image' | translate}}" label="{{'asset-profile.image' | translate}}"
maxSizeByte="524288"
formControlName="image"> formControlName="image">
</tb-image-input> </tb-gallery-image-input>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>asset-profile.description</mat-label> <mat-label translate>asset-profile.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea> <textarea matInput formControlName="description" rows="2"></textarea>

View File

@ -108,11 +108,10 @@
{{ 'device-profile.type-required' | translate }} {{ 'device-profile.type-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<tb-image-input fxFlex <tb-gallery-image-input fxFlex
label="{{'device-profile.image' | translate}}" label="{{'device-profile.image' | translate}}"
maxSizeByte="524288"
formControlName="image"> formControlName="image">
</tb-image-input> </tb-gallery-image-input>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>device-profile.description</mat-label> <mat-label translate>device-profile.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea> <textarea matInput formControlName="description" rows="2"></textarea>

View File

@ -22,6 +22,14 @@ import { SHARED_HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component';
import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-comment-dialog.component'; import { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-comment-dialog.component';
import { AlarmAssigneeComponent } from '@home/components/alarm/alarm-assignee.component'; import { AlarmAssigneeComponent } from '@home/components/alarm/alarm-assignee.component';
import { ScrollGridComponent } from '@home/components/grid/scroll-grid.component';
import { ImageGalleryComponent } from '@home/components/image/image-gallery.component';
import { UploadImageDialogComponent } from '@home/components/image/upload-image-dialog.component';
import { ImageDialogComponent } from '@home/components/image/image-dialog.component';
import { ImageReferencesComponent } from '@home/components/image/image-references.component';
import { ImagesInUseDialogComponent } from '@home/components/image/images-in-use-dialog.component';
import { GalleryImageInputComponent } from '@home/components/image/gallery-image-input.component';
import { MultipleGalleryImageInputComponent } from '@home/components/image/multiple-gallery-image-input.component';
@NgModule({ @NgModule({
providers: [ providers: [
@ -32,7 +40,15 @@ import { AlarmAssigneeComponent } from '@home/components/alarm/alarm-assignee.co
AlarmDetailsDialogComponent, AlarmDetailsDialogComponent,
AlarmCommentComponent, AlarmCommentComponent,
AlarmCommentDialogComponent, AlarmCommentDialogComponent,
AlarmAssigneeComponent AlarmAssigneeComponent,
ScrollGridComponent,
ImageGalleryComponent,
UploadImageDialogComponent,
ImageDialogComponent,
ImageReferencesComponent,
ImagesInUseDialogComponent,
GalleryImageInputComponent,
MultipleGalleryImageInputComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -42,7 +58,15 @@ import { AlarmAssigneeComponent } from '@home/components/alarm/alarm-assignee.co
AlarmDetailsDialogComponent, AlarmDetailsDialogComponent,
AlarmCommentComponent, AlarmCommentComponent,
AlarmCommentDialogComponent, AlarmCommentDialogComponent,
AlarmAssigneeComponent AlarmAssigneeComponent,
ScrollGridComponent,
ImageGalleryComponent,
UploadImageDialogComponent,
ImageDialogComponent,
ImageReferencesComponent,
ImagesInUseDialogComponent,
GalleryImageInputComponent,
MultipleGalleryImageInputComponent
] ]
}) })
export class SharedHomeComponentsModule { } export class SharedHomeComponentsModule { }

View File

@ -32,7 +32,7 @@ import {
WidgetUnitedMapSettings WidgetUnitedMapSettings
} from './map-models'; } from './map-models';
import { Marker } from './markers'; import { Marker } from './markers';
import { Observable, of } from 'rxjs'; import { map, Observable, of, switchMap } from 'rxjs';
import { Polyline } from './polyline'; import { Polyline } from './polyline';
import { Polygon } from './polygon'; import { Polygon } from './polygon';
import { Circle } from './circle'; import { Circle } from './circle';
@ -63,6 +63,7 @@ import {
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { FormattedData, ReplaceInfo } from '@shared/models/widget.models'; import { FormattedData, ReplaceInfo } from '@shared/models/widget.models';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
import { ImagePipe } from '@shared/pipe/image.pipe';
export default abstract class LeafletMap { export default abstract class LeafletMap {
@ -895,16 +896,27 @@ export default abstract class LeafletMap {
const currentImage: MarkerImageInfo = this.options.useMarkerImageFunction ? const currentImage: MarkerImageInfo = this.options.useMarkerImageFunction ?
safeExecute(this.options.parsedMarkerImageFunction, safeExecute(this.options.parsedMarkerImageFunction,
[data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage; [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage;
const imageSize = `height: ${this.options.markerImageSize || 34}px; width: ${this.options.markerImageSize || 34}px;`; const imageUrl$ =
const style = currentImage ? 'background-image: url(' + currentImage.url + '); ' + imageSize : ''; currentImage
this.options.icon = { icon: L.divIcon({ ? this.ctx.$injector.get(ImagePipe).transform(currentImage.url, {asString: true, ignoreLoadingImage: true})
html: `<div class="arrow" : of(null);
this.options.icon$ = imageUrl$.pipe(
map((imageUrl) => {
const size = this.options.useMarkerImageFunction && currentImage ? currentImage.size : this.options.markerImageSize;
const imageSize = `height: ${size || 34}px; width: ${size || 34}px;`;
const style = imageUrl ? 'background-image: url(' + imageUrl + '); ' + imageSize : '';
return { icon: L.divIcon({
html: `<div class="arrow"
style="transform: translate(-10px, -10px) style="transform: translate(-10px, -10px)
rotate(${data.rotationAngle}deg); rotate(${data.rotationAngle}deg);
${style}"><div>` ${style}"><div>`
}), size: [30, 30]}; }), size: [size, size]};
})
);
this.options.icon = null;
} else { } else {
this.options.icon = null; this.options.icon = null;
this.options.icon$ = null;
} }
if (this.markers.get(data.entityName)) { if (this.markers.get(data.entityName)) {
m = this.updateMarker(data.entityName, data, markersData, this.options); m = this.updateMarker(data.entityName, data, markersData, this.options);

View File

@ -17,6 +17,7 @@
import { Datasource, FormattedData } from '@app/shared/models/widget.models'; import { Datasource, FormattedData } from '@app/shared/models/widget.models';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { BaseIconOptions, Icon } from 'leaflet'; import { BaseIconOptions, Icon } from 'leaflet';
import { Observable } from 'rxjs';
export const DEFAULT_MAP_PAGE_SIZE = 16384; export const DEFAULT_MAP_PAGE_SIZE = 16384;
export const DEFAULT_ZOOM_LEVEL = 8; export const DEFAULT_ZOOM_LEVEL = 8;
@ -337,6 +338,7 @@ export interface WidgetMarkersSettings extends MarkersSettings, WidgetToolipSett
currentImage: MarkerImageInfo; currentImage: MarkerImageInfo;
tinyColor: tinycolor.Instance; tinyColor: tinycolor.Instance;
icon: MarkerIconInfo; icon: MarkerIconInfo;
icon$?: Observable<MarkerIconInfo>;
} }
export const defaultMarkersSettings: MarkersSettings = { export const defaultMarkersSettings: MarkersSettings = {

View File

@ -155,6 +155,11 @@ export class Marker {
if (this.settings.icon) { if (this.settings.icon) {
onMarkerIconReady(this.settings.icon); onMarkerIconReady(this.settings.icon);
return; return;
} else if (this.settings.icon$) {
this.settings.icon$.subscribe((res) => {
onMarkerIconReady(res);
});
return;
} }
const currentImage: MarkerImageInfo = this.settings.useMarkerImageFunction ? const currentImage: MarkerImageInfo = this.settings.useMarkerImageFunction ?
safeExecute(this.settings.parsedMarkerImageFunction, safeExecute(this.settings.parsedMarkerImageFunction,

View File

@ -16,9 +16,9 @@
--> -->
<section class="tb-widget-settings" [formGroup]="providerSettingsFormGroup"> <section class="tb-widget-settings" [formGroup]="providerSettingsFormGroup">
<tb-image-input label="{{ 'widgets.maps.image-map-background' | translate }}" <tb-gallery-image-input label="{{ 'widgets.maps.image-map-background' | translate }}"
formControlName="mapImageUrl"> formControlName="mapImageUrl">
</tb-image-input> </tb-gallery-image-input>
<fieldset class="fields-group"> <fieldset class="fields-group">
<legend class="group-title" translate>widgets.maps.image-map-background-from-entity-attribute</legend> <legend class="group-title" translate>widgets.maps.image-map-background-from-entity-attribute</legend>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px"> <section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px">

View File

@ -170,10 +170,10 @@
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<tb-image-input [fxShow]="!markersSettingsFormGroup.get('useMarkerImageFunction').value" <tb-gallery-image-input [fxShow]="!markersSettingsFormGroup.get('useMarkerImageFunction').value"
label="{{ 'widgets.maps.custom-marker-image' | translate }}" label="{{ 'widgets.maps.custom-marker-image' | translate }}"
formControlName="markerImage"> formControlName="markerImage">
</tb-image-input> </tb-gallery-image-input>
<mat-form-field [fxShow]="!markersSettingsFormGroup.get('useMarkerImageFunction').value" <mat-form-field [fxShow]="!markersSettingsFormGroup.get('useMarkerImageFunction').value"
fxFlex class="mat-block"> fxFlex class="mat-block">
<mat-label translate>widgets.maps.custom-marker-image-size</mat-label> <mat-label translate>widgets.maps.custom-marker-image-size</mat-label>
@ -186,10 +186,10 @@
functionTitle="{{ 'widgets.maps.marker-image-function' | translate }}" functionTitle="{{ 'widgets.maps.marker-image-function' | translate }}"
helpId="widget/lib/map/marker_image_fn"> helpId="widget/lib/map/marker_image_fn">
</tb-js-func> </tb-js-func>
<tb-multiple-image-input [fxShow]="markersSettingsFormGroup.get('useMarkerImageFunction').value" <tb-multiple-gallery-image-input [fxShow]="markersSettingsFormGroup.get('useMarkerImageFunction').value"
label="{{ 'widgets.maps.marker-images' | translate }}" label="{{ 'widgets.maps.marker-images' | translate }}"
formControlName="markerImages"> formControlName="markerImages">
</tb-multiple-image-input> </tb-multiple-gallery-image-input>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</fieldset> </fieldset>

View File

@ -70,10 +70,10 @@
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<tb-image-input [fxShow]="!tripAnimationMarkerSettingsFormGroup.get('useMarkerImageFunction').value" <tb-gallery-image-input [fxShow]="!tripAnimationMarkerSettingsFormGroup.get('useMarkerImageFunction').value"
label="{{ 'widgets.maps.custom-marker-image' | translate }}" label="{{ 'widgets.maps.custom-marker-image' | translate }}"
formControlName="markerImage"> formControlName="markerImage">
</tb-image-input> </tb-gallery-image-input>
<mat-form-field [fxShow]="!tripAnimationMarkerSettingsFormGroup.get('useMarkerImageFunction').value" <mat-form-field [fxShow]="!tripAnimationMarkerSettingsFormGroup.get('useMarkerImageFunction').value"
fxFlex class="mat-block"> fxFlex class="mat-block">
<mat-label translate>widgets.maps.custom-marker-image-size</mat-label> <mat-label translate>widgets.maps.custom-marker-image-size</mat-label>
@ -86,10 +86,10 @@
functionTitle="{{ 'widgets.maps.marker-image-function' | translate }}" functionTitle="{{ 'widgets.maps.marker-image-function' | translate }}"
helpId="widget/lib/map/marker_image_fn"> helpId="widget/lib/map/marker_image_fn">
</tb-js-func> </tb-js-func>
<tb-multiple-image-input [fxShow]="tripAnimationMarkerSettingsFormGroup.get('useMarkerImageFunction').value" <tb-multiple-gallery-image-input [fxShow]="tripAnimationMarkerSettingsFormGroup.get('useMarkerImageFunction').value"
label="{{ 'widgets.maps.marker-images' | translate }}" label="{{ 'widgets.maps.marker-images' | translate }}"
formControlName="markerImages"> formControlName="markerImages">
</tb-multiple-image-input> </tb-multiple-gallery-image-input>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</fieldset> </fieldset>

View File

@ -33,6 +33,7 @@ import { QueueComponent } from '@home/pages/admin/queue/queue.component';
import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component'; import { RepositoryAdminSettingsComponent } from '@home/pages/admin/repository-admin-settings.component';
import { AutoCommitAdminSettingsComponent } from '@home/pages/admin/auto-commit-admin-settings.component'; import { AutoCommitAdminSettingsComponent } from '@home/pages/admin/auto-commit-admin-settings.component';
import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-auth-settings.component'; import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-auth-settings.component';
import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module';
@NgModule({ @NgModule({
declarations: declarations:
@ -54,6 +55,7 @@ import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-aut
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
SharedHomeComponentsModule,
HomeComponentsModule, HomeComponentsModule,
AdminRoutingModule AdminRoutingModule
] ]

View File

@ -125,11 +125,10 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div translate>dashboard.mobile-app-settings</div> <div translate>dashboard.mobile-app-settings</div>
<tb-image-input fxFlex <tb-gallery-image-input fxFlex
label="{{'dashboard.image' | translate}}" label="{{'dashboard.image' | translate}}"
maxSizeByte="524288"
formControlName="image"> formControlName="image">
</tb-image-input> </tb-gallery-image-input>
<mat-checkbox fxFlex formControlName="mobileHide"> <mat-checkbox fxFlex formControlName="mobileHide">
{{ 'dashboard.mobile-hide' | translate }} {{ 'dashboard.mobile-hide' | translate }}
</mat-checkbox> </mat-checkbox>

View File

@ -24,6 +24,7 @@ import { DashboardRoutingModule } from './dashboard-routing.module';
import { MakeDashboardPublicDialogComponent } from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component'; import { MakeDashboardPublicDialogComponent } from '@modules/home/pages/dashboard/make-dashboard-public-dialog.component';
import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.component'; import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.component';
import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -35,6 +36,7 @@ import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.com
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
SharedHomeComponentsModule,
HomeComponentsModule, HomeComponentsModule,
HomeDialogsModule, HomeDialogsModule,
DashboardRoutingModule DashboardRoutingModule

View File

@ -240,11 +240,10 @@
<mat-tab label="{{ 'widget.widget-settings' | translate }}"> <mat-tab label="{{ 'widget.widget-settings' | translate }}">
<div class="tb-resize-container" style="background-color: #fff;"> <div class="tb-resize-container" style="background-color: #fff;">
<div class="mat-padding"> <div class="mat-padding">
<tb-image-input fxFlex label="{{'widget.image-preview' | translate}}" <tb-gallery-image-input fxFlex label="{{'widget.image-preview' | translate}}"
maxSizeByte="524288"
[(ngModel)]="widget.image" [(ngModel)]="widget.image"
(ngModelChange)="isDirty = true" > (ngModelChange)="isDirty = true" >
</tb-image-input> </tb-gallery-image-input>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>widget.description</mat-label> <mat-label translate>widget.description</mat-label>
<textarea matInput #descriptionInput <textarea matInput #descriptionInput

View File

@ -28,6 +28,7 @@ import { WidgetTypeComponent } from '@home/pages/widget/widget-type.component';
import { WidgetTypeTabsComponent } from '@home/pages/widget/widget-type-tabs.component'; import { WidgetTypeTabsComponent } from '@home/pages/widget/widget-type-tabs.component';
import { WidgetsBundleWidgetsComponent } from '@home/pages/widget/widgets-bundle-widgets.component'; import { WidgetsBundleWidgetsComponent } from '@home/pages/widget/widgets-bundle-widgets.component';
import { WidgetTypeAutocompleteComponent } from '@home/pages/widget/widget-type-autocomplete.component'; import { WidgetTypeAutocompleteComponent } from '@home/pages/widget/widget-type-autocomplete.component';
import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -45,6 +46,7 @@ import { WidgetTypeAutocompleteComponent } from '@home/pages/widget/widget-type-
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
SharedHomeComponentsModule,
HomeComponentsModule, HomeComponentsModule,
WidgetLibraryRoutingModule WidgetLibraryRoutingModule
] ]

View File

@ -54,11 +54,10 @@
{{ 'widget.title-max-length' | translate }} {{ 'widget.title-max-length' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<tb-image-input fxFlex <tb-gallery-image-input fxFlex
label="{{'widget.image-preview' | translate}}" label="{{'widget.image-preview' | translate}}"
maxSizeByte="524288"
formControlName="image"> formControlName="image">
</tb-image-input> </tb-gallery-image-input>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>widget.description</mat-label> <mat-label translate>widget.description</mat-label>
<textarea matInput formControlName="description" rows="2" maxlength="1024" #descriptionInput></textarea> <textarea matInput formControlName="description" rows="2" maxlength="1024" #descriptionInput></textarea>

View File

@ -58,11 +58,6 @@
label="{{'widgets-bundle.image-preview' | translate}}" label="{{'widgets-bundle.image-preview' | translate}}"
formControlName="image"> formControlName="image">
</tb-gallery-image-input> </tb-gallery-image-input>
<!--tb-image-input fxFlex
label="{{'widgets-bundle.image-preview' | translate}}"
maxSizeByte="524288"
formControlName="image">
</tb-image-input-->
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>widgets-bundle.description</mat-label> <mat-label translate>widgets-bundle.description</mat-label>
<textarea matInput formControlName="description" rows="2" maxlength="1024" #descriptionInput></textarea> <textarea matInput formControlName="description" rows="2" maxlength="1024" #descriptionInput></textarea>