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">
</mat-form-field>
<div class="tb-image-preview-container">
<div *ngIf="!safeImageUrl; else elseBlock">{{ 'dashboard.no-image' | translate }}</div>
<ng-template #elseBlock><img class="tb-image-preview" [src]="safeImageUrl" /></ng-template>
<div *ngIf="!imageUrl; else elseBlock">{{ 'dashboard.no-image' | translate }}</div>
<ng-template #elseBlock><img class="tb-image-preview" [src]="imageUrl | image | async" /></ng-template>
</div>
<mat-form-field class="rect-field">
<mat-label>Right %</mat-label>
@ -62,9 +62,9 @@
</button>
</div>
<div [formGroup]="dashboardImageFormGroup">
<tb-image-input [showPreview]="false" label="{{'dashboard.image' | translate}}"
<tb-gallery-image-input label="{{'dashboard.image' | translate}}"
formControlName="dashboardImage">
</tb-image-input>
</tb-gallery-image-input>
</div>
</fieldset>
<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;
safeImageUrl?: SafeUrl;
imageUrl?: string;
dashboardElement: HTMLElement;
dashboardRectFormGroup: UntypedFormGroup;
@ -165,10 +165,6 @@ export class DashboardImageDialogComponent extends DialogComponent<DashboardImag
}
private updateImage(imageUrl: string) {
if (imageUrl) {
this.safeImageUrl = this.sanitizer.bypassSecurityTrustUrl(imageUrl);
} else {
this.safeImageUrl = null;
}
this.imageUrl = imageUrl;
}
}

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

@ -70,7 +70,7 @@
[syncStateWithQueryParam]="syncStateWithQueryParam"
[states]="dashboardConfiguration.states">
</tb-states-component>
<img *ngIf="showDashboardLogo()" [src]="dashboardLogo"
<img *ngIf="showDashboardLogo()" [src]="dashboardLogo | image | async"
aria-label="dashboard_logo" class="dashboard_logo"/>
</div>
<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 {
if (!this.dashboardLogoCache) {
const logo = this.dashboard.configuration.settings.dashboardLogoUrl || this.defaultDashboardLogo;
this.dashboardLogoCache = this.sanitizer.bypassSecurityTrustUrl(logo);
}
return this.dashboardLogoCache;
public get dashboardLogo(): string {
return this.dashboard.configuration.settings.dashboardLogoUrl || this.defaultDashboardLogo;
}
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">
{{ 'dashboard.display-dashboard-logo' | translate }}
</mat-slide-toggle>
<tb-image-input fxFlex
<tb-gallery-image-input fxFlex
label="{{'dashboard.dashboard-logo-image' | translate}}"
formControlName="dashboardLogoUrl">
</tb-image-input>
</tb-gallery-image-input>
</fieldset>
<fieldset class="fields-group" fxLayout="column" fxLayoutGap="8px">
<legend class="group-title" translate>dashboard.toolbar-settings</legend>
@ -144,10 +144,10 @@
openOnInput
formControlName="backgroundColor">
</tb-color-input>
<tb-image-input fxFlex
<tb-gallery-image-input fxFlex
label="{{'dashboard.background-image' | translate}}"
formControlName="backgroundImageUrl">
</tb-image-input>
</tb-gallery-image-input>
<mat-form-field class="mat-block">
<mat-label translate>dashboard.background-size-mode</mat-label>
<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"
style="position: relative; width: 100%; height: 100%;" tb-hotkeys [hotkeys]="hotKeys"
[cheatSheet]="dashboardCheatSheet"
[style.backgroundImage]="backgroundImage"
[style.backgroundImage]="backgroundImage$ | async"
[ngStyle]="dashboardStyle">
<section *ngIf="layoutCtx.widgets.isEmpty()" fxLayoutAlign="center center"
style="display: flex; z-index: 1; pointer-events: none;"
@ -38,7 +38,7 @@
</button>
</section>
<tb-dashboard #dashboard [dashboardStyle]="dashboardStyle"
[backgroundImage]="backgroundImage"
[backgroundImage]="backgroundImage$ | async"
[widgets]="layoutCtx.widgets"
[widgetLayouts]="layoutCtx.widgetLayouts"
[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,
WidgetContextMenuItem
} from '@home/models/dashboard-component.models';
import { Subscription } from 'rxjs';
import { Observable, of, Subscription } from 'rxjs';
import { Hotkey } from 'angular2-hotkeys';
import { TranslateService } from '@ngx-translate/core';
import { ItemBufferService } from '@app/core/services/item-buffer.service';
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { map } from 'rxjs/operators';
@Component({
selector: 'tb-dashboard-layout',
@ -44,7 +46,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
layoutCtxValue: DashboardPageLayoutContext;
dashboardStyle: {[klass: string]: any} = null;
backgroundImage: SafeStyle | string;
backgroundImage$: Observable<SafeStyle | string>;
hotKeys: Hotkey[] = [];
@ -92,6 +94,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
constructor(protected store: Store<AppState>,
private translate: TranslateService,
private itembuffer: ItemBufferService,
private imagePipe: ImagePipe,
private sanitizer: DomSanitizer) {
super(store);
this.initHotKeys();
@ -188,8 +191,10 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
'background-attachment': 'scroll',
'background-size': this.layoutCtx.gridSettings.backgroundSizeMode || '100%',
'background-position': '0% 0%'};
this.backgroundImage = this.layoutCtx.gridSettings.backgroundImageUrl ?
this.sanitizer.bypassSecurityTrustStyle('url(' + this.layoutCtx.gridSettings.backgroundImageUrl + ')') : 'none';
this.backgroundImage$ = this.layoutCtx.gridSettings.backgroundImageUrl ?
this.imagePipe.transform(this.layoutCtx.gridSettings.backgroundImageUrl, {asString: true, ignoreLoadingImage: true}).pipe(
map((imageUrl) => this.sanitizer.bypassSecurityTrustStyle('url(' + imageUrl + ')'))
) : of('none');
}
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 {
ExportWidgetsBundleDialogComponent
} 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({
declarations:
@ -332,14 +325,7 @@ import { GalleryImageInputComponent } from '@home/components/image/gallery-image
RateLimitsComponent,
RateLimitsTextComponent,
RateLimitsDetailsDialogComponent,
SendNotificationButtonComponent,
ScrollGridComponent,
ImageGalleryComponent,
UploadImageDialogComponent,
ImageDialogComponent,
ImageReferencesComponent,
ImagesInUseDialogComponent,
GalleryImageInputComponent
SendNotificationButtonComponent
],
imports: [
CommonModule,
@ -477,14 +463,7 @@ import { GalleryImageInputComponent } from '@home/components/image/gallery-image
RateLimitsComponent,
RateLimitsTextComponent,
RateLimitsDetailsDialogComponent,
SendNotificationButtonComponent,
ScrollGridComponent,
ImageGalleryComponent,
UploadImageDialogComponent,
ImageDialogComponent,
ImageReferencesComponent,
ImagesInUseDialogComponent,
GalleryImageInputComponent
SendNotificationButtonComponent
],
providers: [
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">
<button #browseGalleryButton
mat-stroked-button
type="button"
color="primary"
class="tb-image-select-button"
(click)="toggleGallery($event, browseGalleryButton)">
@ -75,6 +76,7 @@
<span translate>image.browse-from-gallery</span>
</button>
<button mat-stroked-button
type="button"
color="primary"
class="tb-image-select-button"
(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 { ImageGalleryComponent } from '@home/components/image/image-gallery.component';
enum ImageLinkType {
export enum ImageLinkType {
none = 'none',
base64 = 'base64',
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 }}
</mat-error>
</mat-form-field>
<tb-image-input fxFlex
<tb-gallery-image-input fxFlex
label="{{'device-profile.image' | translate}}"
maxSizeByte="524288"
formControlName="image">
</tb-image-input>
</tb-gallery-image-input>
<mat-form-field class="mat-block">
<mat-label translate>device-profile.description</mat-label>
<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">
<span tb-hint>{{'asset-profile.default-edge-rule-chain-hint' | translate}}</span>
</tb-rule-chain-autocomplete>
<tb-image-input fxFlex
<tb-gallery-image-input fxFlex
label="{{'asset-profile.image' | translate}}"
maxSizeByte="524288"
formControlName="image">
</tb-image-input>
</tb-gallery-image-input>
<mat-form-field class="mat-block">
<mat-label translate>asset-profile.description</mat-label>
<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 }}
</mat-error>
</mat-form-field>
<tb-image-input fxFlex
<tb-gallery-image-input fxFlex
label="{{'device-profile.image' | translate}}"
maxSizeByte="524288"
formControlName="image">
</tb-image-input>
</tb-gallery-image-input>
<mat-form-field class="mat-block">
<mat-label translate>device-profile.description</mat-label>
<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 { AlarmCommentDialogComponent } from '@home/components/alarm/alarm-comment-dialog.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({
providers: [
@ -32,7 +40,15 @@ import { AlarmAssigneeComponent } from '@home/components/alarm/alarm-assignee.co
AlarmDetailsDialogComponent,
AlarmCommentComponent,
AlarmCommentDialogComponent,
AlarmAssigneeComponent
AlarmAssigneeComponent,
ScrollGridComponent,
ImageGalleryComponent,
UploadImageDialogComponent,
ImageDialogComponent,
ImageReferencesComponent,
ImagesInUseDialogComponent,
GalleryImageInputComponent,
MultipleGalleryImageInputComponent
],
imports: [
CommonModule,
@ -42,7 +58,15 @@ import { AlarmAssigneeComponent } from '@home/components/alarm/alarm-assignee.co
AlarmDetailsDialogComponent,
AlarmCommentComponent,
AlarmCommentDialogComponent,
AlarmAssigneeComponent
AlarmAssigneeComponent,
ScrollGridComponent,
ImageGalleryComponent,
UploadImageDialogComponent,
ImageDialogComponent,
ImageReferencesComponent,
ImagesInUseDialogComponent,
GalleryImageInputComponent,
MultipleGalleryImageInputComponent
]
})
export class SharedHomeComponentsModule { }

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

@ -32,7 +32,7 @@ import {
WidgetUnitedMapSettings
} from './map-models';
import { Marker } from './markers';
import { Observable, of } from 'rxjs';
import { map, Observable, of, switchMap } from 'rxjs';
import { Polyline } from './polyline';
import { Polygon } from './polygon';
import { Circle } from './circle';
@ -63,6 +63,7 @@ import {
import { MatDialog } from '@angular/material/dialog';
import { FormattedData, ReplaceInfo } from '@shared/models/widget.models';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
import { ImagePipe } from '@shared/pipe/image.pipe';
export default abstract class LeafletMap {
@ -895,16 +896,27 @@ export default abstract class LeafletMap {
const currentImage: MarkerImageInfo = this.options.useMarkerImageFunction ?
safeExecute(this.options.parsedMarkerImageFunction,
[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 style = currentImage ? 'background-image: url(' + currentImage.url + '); ' + imageSize : '';
this.options.icon = { icon: L.divIcon({
html: `<div class="arrow"
const imageUrl$ =
currentImage
? this.ctx.$injector.get(ImagePipe).transform(currentImage.url, {asString: true, ignoreLoadingImage: true})
: 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)
rotate(${data.rotationAngle}deg);
${style}"><div>`
}), size: [30, 30]};
}), size: [size, size]};
})
);
this.options.icon = null;
} else {
this.options.icon = null;
this.options.icon$ = null;
}
if (this.markers.get(data.entityName)) {
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 tinycolor from 'tinycolor2';
import { BaseIconOptions, Icon } from 'leaflet';
import { Observable } from 'rxjs';
export const DEFAULT_MAP_PAGE_SIZE = 16384;
export const DEFAULT_ZOOM_LEVEL = 8;
@ -337,6 +338,7 @@ export interface WidgetMarkersSettings extends MarkersSettings, WidgetToolipSett
currentImage: MarkerImageInfo;
tinyColor: tinycolor.Instance;
icon: MarkerIconInfo;
icon$?: Observable<MarkerIconInfo>;
}
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) {
onMarkerIconReady(this.settings.icon);
return;
} else if (this.settings.icon$) {
this.settings.icon$.subscribe((res) => {
onMarkerIconReady(res);
});
return;
}
const currentImage: MarkerImageInfo = this.settings.useMarkerImageFunction ?
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">
<tb-image-input label="{{ 'widgets.maps.image-map-background' | translate }}"
<tb-gallery-image-input label="{{ 'widgets.maps.image-map-background' | translate }}"
formControlName="mapImageUrl">
</tb-image-input>
</tb-gallery-image-input>
<fieldset class="fields-group">
<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">

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

@ -170,10 +170,10 @@
</mat-panel-description>
</mat-expansion-panel-header>
<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 }}"
formControlName="markerImage">
</tb-image-input>
</tb-gallery-image-input>
<mat-form-field [fxShow]="!markersSettingsFormGroup.get('useMarkerImageFunction').value"
fxFlex class="mat-block">
<mat-label translate>widgets.maps.custom-marker-image-size</mat-label>
@ -186,10 +186,10 @@
functionTitle="{{ 'widgets.maps.marker-image-function' | translate }}"
helpId="widget/lib/map/marker_image_fn">
</tb-js-func>
<tb-multiple-image-input [fxShow]="markersSettingsFormGroup.get('useMarkerImageFunction').value"
label="{{ 'widgets.maps.marker-images' | translate }}"
formControlName="markerImages">
</tb-multiple-image-input>
<tb-multiple-gallery-image-input [fxShow]="markersSettingsFormGroup.get('useMarkerImageFunction').value"
label="{{ 'widgets.maps.marker-images' | translate }}"
formControlName="markerImages">
</tb-multiple-gallery-image-input>
</ng-template>
</mat-expansion-panel>
</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-expansion-panel-header>
<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 }}"
formControlName="markerImage">
</tb-image-input>
</tb-gallery-image-input>
<mat-form-field [fxShow]="!tripAnimationMarkerSettingsFormGroup.get('useMarkerImageFunction').value"
fxFlex class="mat-block">
<mat-label translate>widgets.maps.custom-marker-image-size</mat-label>
@ -86,10 +86,10 @@
functionTitle="{{ 'widgets.maps.marker-image-function' | translate }}"
helpId="widget/lib/map/marker_image_fn">
</tb-js-func>
<tb-multiple-image-input [fxShow]="tripAnimationMarkerSettingsFormGroup.get('useMarkerImageFunction').value"
label="{{ 'widgets.maps.marker-images' | translate }}"
formControlName="markerImages">
</tb-multiple-image-input>
<tb-multiple-gallery-image-input [fxShow]="tripAnimationMarkerSettingsFormGroup.get('useMarkerImageFunction').value"
label="{{ 'widgets.maps.marker-images' | translate }}"
formControlName="markerImages">
</tb-multiple-gallery-image-input>
</ng-template>
</mat-expansion-panel>
</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 { AutoCommitAdminSettingsComponent } from '@home/pages/admin/auto-commit-admin-settings.component';
import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-auth-settings.component';
import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module';
@NgModule({
declarations:
@ -54,6 +55,7 @@ import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-aut
imports: [
CommonModule,
SharedModule,
SharedHomeComponentsModule,
HomeComponentsModule,
AdminRoutingModule
]

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

@ -125,11 +125,10 @@
</mat-form-field>
</div>
<div translate>dashboard.mobile-app-settings</div>
<tb-image-input fxFlex
<tb-gallery-image-input fxFlex
label="{{'dashboard.image' | translate}}"
maxSizeByte="524288"
formControlName="image">
</tb-image-input>
</tb-gallery-image-input>
<mat-checkbox fxFlex formControlName="mobileHide">
{{ 'dashboard.mobile-hide' | translate }}
</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 { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.component';
import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module';
@NgModule({
declarations: [
@ -35,6 +36,7 @@ import { DashboardTabsComponent } from '@home/pages/dashboard/dashboard-tabs.com
imports: [
CommonModule,
SharedModule,
SharedHomeComponentsModule,
HomeComponentsModule,
HomeDialogsModule,
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 }}">
<div class="tb-resize-container" style="background-color: #fff;">
<div class="mat-padding">
<tb-image-input fxFlex label="{{'widget.image-preview' | translate}}"
maxSizeByte="524288"
<tb-gallery-image-input fxFlex label="{{'widget.image-preview' | translate}}"
[(ngModel)]="widget.image"
(ngModelChange)="isDirty = true" >
</tb-image-input>
</tb-gallery-image-input>
<mat-form-field class="mat-block">
<mat-label translate>widget.description</mat-label>
<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 { WidgetsBundleWidgetsComponent } from '@home/pages/widget/widgets-bundle-widgets.component';
import { WidgetTypeAutocompleteComponent } from '@home/pages/widget/widget-type-autocomplete.component';
import { SharedHomeComponentsModule } from '@home/components/shared-home-components.module';
@NgModule({
declarations: [
@ -45,6 +46,7 @@ import { WidgetTypeAutocompleteComponent } from '@home/pages/widget/widget-type-
imports: [
CommonModule,
SharedModule,
SharedHomeComponentsModule,
HomeComponentsModule,
WidgetLibraryRoutingModule
]

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

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

Loading…
Cancel
Save