Browse Source

UI: Implement multiple gallery images input. Replace image inputs to image gallery inputs.

pull/9542/head
Igor Kulikov 3 years ago
parent
commit
35de014fe1
  1. 8
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html
  2. 8
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.ts
  3. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  4. 8
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  5. 8
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html
  6. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html
  7. 13
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts
  8. 25
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  9. 2
      ui-ngx/src/app/modules/home/components/image/gallery-image-input.component.html
  10. 2
      ui-ngx/src/app/modules/home/components/image/gallery-image-input.component.ts
  11. 111
      ui-ngx/src/app/modules/home/components/image/multiple-gallery-image-input.component.html
  12. 225
      ui-ngx/src/app/modules/home/components/image/multiple-gallery-image-input.component.scss
  13. 180
      ui-ngx/src/app/modules/home/components/image/multiple-gallery-image-input.component.ts
  14. 5
      ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html
  15. 5
      ui-ngx/src/app/modules/home/components/profile/asset-profile.component.html
  16. 5
      ui-ngx/src/app/modules/home/components/profile/device-profile.component.html
  17. 28
      ui-ngx/src/app/modules/home/components/shared-home-components.module.ts
  18. 24
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  19. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
  20. 5
      ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts
  21. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/map/image-map-provider-settings.component.html
  22. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/map/markers-settings.component.html
  23. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/map/trip-animation-marker-settings.component.html
  24. 2
      ui-ngx/src/app/modules/home/pages/admin/admin.module.ts
  25. 5
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.html
  26. 2
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts
  27. 5
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html
  28. 2
      ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts
  29. 5
      ui-ngx/src/app/modules/home/pages/widget/widget-type.component.html
  30. 5
      ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html

8
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html

@ -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"

8
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.ts

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

2
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -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">

8
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -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 {

8
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html

@ -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">

4
ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html

@ -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"

13
ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts

@ -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() {

25
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -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,

2
ui-ngx/src/app/modules/home/components/image/gallery-image-input.component.html

@ -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)">

2
ui-ngx/src/app/modules/home/components/image/gallery-image-input.component.ts

@ -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',

111
ui-ngx/src/app/modules/home/components/image/multiple-gallery-image-input.component.html

@ -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>

225
ui-ngx/src/app/modules/home/components/image/multiple-gallery-image-input.component.scss

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

180
ui-ngx/src/app/modules/home/components/image/multiple-gallery-image-input.component.ts

@ -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();
}
}

5
ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html

@ -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>

5
ui-ngx/src/app/modules/home/components/profile/asset-profile.component.html

@ -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>

5
ui-ngx/src/app/modules/home/components/profile/device-profile.component.html

@ -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>

28
ui-ngx/src/app/modules/home/components/shared-home-components.module.ts

@ -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 { }

24
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts

@ -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);

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts

@ -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 = {

5
ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts

@ -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,

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/map/image-map-provider-settings.component.html

@ -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">

12
ui-ngx/src/app/modules/home/components/widget/lib/settings/map/markers-settings.component.html

@ -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>

12
ui-ngx/src/app/modules/home/components/widget/lib/settings/map/trip-animation-marker-settings.component.html

@ -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>

2
ui-ngx/src/app/modules/home/pages/admin/admin.module.ts

@ -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
] ]

5
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.html

@ -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>

2
ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts

@ -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

5
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html

@ -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

2
ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts

@ -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
] ]

5
ui-ngx/src/app/modules/home/pages/widget/widget-type.component.html

@ -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>

5
ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html

@ -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>

Loading…
Cancel
Save