Merge remote-tracking branch 'upstream/master' into feature/edge-alarm-comment-support

This commit is contained in:
Andrii Landiak 2024-01-10 10:47:25 +02:00
commit 92c23aa0cb
9 changed files with 191 additions and 89 deletions

View File

@ -66,12 +66,11 @@
</button> </button>
</div> </div>
<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 mat-stroked-button
mat-stroked-button
type="button" type="button"
color="primary" color="primary"
class="tb-image-select-button" class="tb-image-select-button"
(click)="toggleGallery($event, browseGalleryButton)"> (click)="openGallery($event)">
<tb-icon matButtonIcon>filter</tb-icon> <tb-icon matButtonIcon>filter</tb-icon>
<span translate>image.browse-from-gallery</span> <span translate>image.browse-from-gallery</span>
</button> </button>

View File

@ -14,16 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
ChangeDetectorRef,
Component,
forwardRef,
Input,
OnDestroy,
OnInit,
Renderer2,
ViewContainerRef
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -33,12 +24,13 @@ import {
extractParamsFromImageResourceUrl, extractParamsFromImageResourceUrl,
ImageResourceInfo, ImageResourceInfo,
isBase64DataImageUrl, isBase64DataImageUrl,
isImageResourceUrl, prependTbImagePrefix, removeTbImagePrefix isImageResourceUrl,
prependTbImagePrefix,
removeTbImagePrefix
} from '@shared/models/resource.models'; } from '@shared/models/resource.models';
import { ImageService } from '@core/http/image.service'; import { ImageService } from '@core/http/image.service';
import { MatButton } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog';
import { TbPopoverService } from '@shared/components/popover.service'; import { ImageGalleryDialogComponent } from '@shared/components/image/image-gallery-dialog.component';
import { ImageGalleryComponent } from '@shared/components/image/image-gallery.component';
export enum ImageLinkType { export enum ImageLinkType {
none = 'none', none = 'none',
@ -87,10 +79,8 @@ export class GalleryImageInputComponent extends PageComponent implements OnInit,
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private imageService: ImageService, private imageService: ImageService,
private cd: ChangeDetectorRef, private dialog: MatDialog,
private renderer: Renderer2, private cd: ChangeDetectorRef) {
private viewContainerRef: ViewContainerRef,
private popoverService: TbPopoverService) {
super(store); super(store);
} }
@ -194,32 +184,22 @@ export class GalleryImageInputComponent extends PageComponent implements OnInit,
this.linkType = ImageLinkType.external; this.linkType = ImageLinkType.external;
} }
toggleGallery($event: Event, browseGalleryButton: MatButton) { openGallery($event: Event): void {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const trigger = browseGalleryButton._elementRef.nativeElement; this.dialog.open<ImageGalleryDialogComponent, any,
if (this.popoverService.hasPopover(trigger)) { ImageResourceInfo>(ImageGalleryDialogComponent, {
this.popoverService.hidePopover(trigger); autoFocus: false,
} else { disableClose: false,
const ctx: any = { panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
pageMode: false, }).afterClosed().subscribe((image) => {
popoverMode: true, if (image) {
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.linkType = ImageLinkType.resource; this.linkType = ImageLinkType.resource;
this.imageResource = image; this.imageResource = image;
this.updateModel(image.link); this.updateModel(image.link);
});
} }
});
} }
} }

View File

@ -0,0 +1,32 @@
<!--
Copyright © 2016-2024 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-image-gallery-dialog">
<button class="tb-image-gallery-close tb-mat-20"
mat-icon-button
(click)="cancel()"
type="button">
<mat-icon>close</mat-icon>
</button>
<tb-image-gallery
pageMode="false"
dialogMode
mode="grid"
selectionMode
(imageSelected)="imageSelected($event)">
</tb-image-gallery>
</div>

View File

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2024 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';
:host {
.tb-image-gallery-dialog {
position: relative;
padding: 24px 32px 16px 32px;
width: 100vw;
height: 100vh;
max-height: 100vh;
@media #{$mat-gt-xs} {
width: 80vw;
height: 80vh;
max-height: 80vh;
}
@media #{$mat-gt-sm} {
width: 700px;
}
@media #{$mat-gt-md} {
width: 900px;
}
@media #{$mat-gt-xl} {
width: 900px;
}
.tb-image-gallery-close {
position: absolute;
top: 12px;
right: 12px;
z-index: 1;
color: rgba(0, 0, 0, 0.38);
}
}
}

View File

@ -0,0 +1,62 @@
///
/// Copyright © 2016-2024 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 { Component, Inject, OnInit, SkipSelf } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import { ImageService } from '@core/http/image.service';
import { ImageResourceInfo, imageResourceType } from '@shared/models/resource.models';
import {
UploadImageDialogComponent,
UploadImageDialogData
} from '@shared/components/image/upload-image-dialog.component';
import { UrlHolder } from '@shared/pipe/image.pipe';
import { ImportExportService } from '@shared/import-export/import-export.service';
import { EmbedImageDialogComponent, EmbedImageDialogData } from '@shared/components/image/embed-image-dialog.component';
@Component({
selector: 'tb-image-gallery-dialog',
templateUrl: './image-gallery-dialog.component.html',
styleUrls: ['./image-gallery-dialog.component.scss']
})
export class ImageGalleryDialogComponent extends
DialogComponent<ImageGalleryDialogComponent, ImageResourceInfo> implements OnInit {
constructor(protected store: Store<AppState>,
protected router: Router,
private imageService: ImageService,
private dialog: MatDialog,
public dialogRef: MatDialogRef<ImageGalleryDialogComponent, ImageResourceInfo>) {
super(store, router, dialogRef);
}
ngOnInit(): void {
}
cancel(): void {
this.dialogRef.close(null);
}
imageSelected(image: ImageResourceInfo): void {
this.dialogRef.close(image);
}
}

View File

@ -15,11 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-images tb-absolute-fill" [ngClass]="{'tb-popover-mode': popoverMode, 'mat-padding': !popoverMode}"> <div class="tb-images tb-absolute-fill" [ngClass]="{'tb-dialog-mode': dialogMode, 'mat-padding': !dialogMode}">
<div fxFlex fxLayout="column" class="tb-images-content" [ngClass]="{'tb-outlined-border': pageMode}"> <div fxFlex fxLayout="column" class="tb-images-content" [ngClass]="{'tb-outlined-border': pageMode}">
<mat-toolbar class="mat-mdc-table-toolbar" <mat-toolbar class="mat-mdc-table-toolbar"
fxLayout.lt-md="column" fxLayoutAlign.lt-md="start stretch" fxLayout.lt-lg="column" fxLayoutAlign.lt-lg="start stretch"
[fxShow]="!textSearchMode && (mode === 'grid' || dataSource?.selection.isEmpty())" [ngClass.lt-md]="{'multi-row': !isSysAdmin}"> [fxShow]="!textSearchMode && (mode === 'grid' || dataSource?.selection.isEmpty())" [ngClass.lt-lg]="{'multi-row': !isSysAdmin}">
<div fxFlex fxLayout="row" fxLayoutAlign="start center"> <div fxFlex fxLayout="row" fxLayoutAlign="start center">
<div class="mat-toolbar-tools"> <div class="mat-toolbar-tools">
<span fxHide fxShow.gt-sm class="tb-images-title" translate>image.gallery</span> <span fxHide fxShow.gt-sm class="tb-images-title" translate>image.gallery</span>
@ -44,11 +44,11 @@
</div> </div>
</div> </div>
<mat-slide-toggle *ngIf="!isSysAdmin" <mat-slide-toggle *ngIf="!isSysAdmin"
fxHide fxShow.gt-sm fxHide fxShow.gt-md
[ngModel]="includeSystemImages" [ngModel]="includeSystemImages"
(ngModelChange)="includeSystemImagesChanged($event)">{{ 'image.include-system-images' | translate }}</mat-slide-toggle> (ngModelChange)="includeSystemImagesChanged($event)">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
</div> </div>
<section fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px"> <section fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap.gt-md="16px">
<span fxFlex></span> <span fxFlex></span>
<section fxLayout="row" fxLayoutAlign="start center"> <section fxLayout="row" fxLayoutAlign="start center">
<button [disabled]="isLoading$ | async" <button [disabled]="isLoading$ | async"
@ -79,7 +79,7 @@
(click)="uploadImage()" (click)="uploadImage()"
matTooltip="{{'image.upload-image' | translate }}" matTooltip="{{'image.upload-image' | translate }}"
matTooltipPosition="above"> matTooltipPosition="above">
<mat-icon>upload</mat-icon> <mat-icon>add</mat-icon>
</button> </button>
</section> </section>
<button fxHide fxShow.gt-md <button fxHide fxShow.gt-md
@ -91,7 +91,7 @@
</button> </button>
</section> </section>
</div> </div>
<mat-slide-toggle *ngIf="!isSysAdmin" fxHide fxShow.lt-md <mat-slide-toggle *ngIf="!isSysAdmin" fxHide fxShow.lt-lg
[ngModel]="includeSystemImages" [ngModel]="includeSystemImages"
(ngModelChange)="includeSystemImagesChanged($event)">{{ 'image.include-system-images' | translate }}</mat-slide-toggle> (ngModelChange)="includeSystemImagesChanged($event)">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
</mat-toolbar> </mat-toolbar>
@ -293,7 +293,7 @@
[hidePageSize]="hidePageSize" [hidePageSize]="hidePageSize"
showFirstLastButtons></mat-paginator> showFirstLastButtons></mat-paginator>
</div> </div>
<div fxFlex class="mat-padding" *ngIf="mode === 'grid'" fxLayout="column"> <div *ngIf="mode === 'grid'" fxFlex [ngClass]="{'mat-padding': !dialogMode}" fxLayout="column">
<tb-scroll-grid fxFlex <tb-scroll-grid fxFlex
[columns]="gridColumns" [columns]="gridColumns"
[itemSize]="gridImagesItemSizeStrategy" [itemSize]="gridImagesItemSizeStrategy"

View File

@ -19,36 +19,12 @@ $tb-button-selected-color: rgb(255, 110, 64) !default;
.tb-images { .tb-images {
&.tb-popover-mode { &.tb-dialog-mode {
padding-top: 8px;
position: relative; position: relative;
width: 300px; width: 100%;
height: 500px; height: 100%;
max-width: calc(100vw - 50px); .mat-toolbar.mat-mdc-table-toolbar {
max-height: calc(100vh - 100px); padding: 0;
@media #{$mat-gt-xs} {
width: 500px;
height: 600px;
}
@media #{$mat-gt-sm} {
width: 700px;
height: 600px;
}
@media #{$mat-gt-md} {
width: 1000px;
height: 700px;
}
@media #{$mat-gt-xmd} {
width: 1300px;
height: 700px;
}
@media #{$mat-gt-xl} {
width: 1600px;
height: 800px;
}
@media screen and (min-width: 2320px) {
width: 2000px;
height: 800px;
} }
} }

View File

@ -88,15 +88,11 @@ const pageGridColumns: ScrollGridColumns = {
} }
}; };
const popoverGridColumns: ScrollGridColumns = { const dialogGridColumns: ScrollGridColumns = {
columns: 1, columns: 2,
breakpoints: { breakpoints: {
'screen and (min-width: 2320px)': 8,
'gt-lg': 6,
'screen and (min-width: 1600px)': 5,
'gt-md': 4, 'gt-md': 4,
'gt-sm': 3, 'gt-xs': 3
'gt-xs': 2
} }
}; };
@ -111,18 +107,25 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
@HostBinding('style.display') @HostBinding('style.display')
private display = 'block'; private display = 'block';
@HostBinding('style.width')
private width = '100%';
@HostBinding('style.height')
private height = '100%';
@Input() @Input()
@coerceBoolean() @coerceBoolean()
pageMode = true; pageMode = true;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
popoverMode = false; dialogMode = false;
@Input() @Input()
mode: 'list' | 'grid' = 'list'; mode: 'list' | 'grid' = 'list';
@Input() @Input()
@coerceBoolean()
selectionMode = false; selectionMode = false;
@Output() @Output()
@ -194,7 +197,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
} }
ngOnInit(): void { ngOnInit(): void {
this.gridColumns = this.popoverMode ? popoverGridColumns : pageGridColumns; this.gridColumns = this.dialogMode ? dialogGridColumns : pageGridColumns;
this.displayedColumns = this.computeDisplayedColumns(); this.displayedColumns = this.computeDisplayedColumns();
let sortOrder: SortOrder = this.defaultSortOrder; let sortOrder: SortOrder = this.defaultSortOrder;
this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];

View File

@ -215,6 +215,7 @@ import { ImagesInUseDialogComponent } from '@shared/components/image/images-in-u
import { GalleryImageInputComponent } from '@shared/components/image/gallery-image-input.component'; import { GalleryImageInputComponent } from '@shared/components/image/gallery-image-input.component';
import { MultipleGalleryImageInputComponent } from '@shared/components/image/multiple-gallery-image-input.component'; import { MultipleGalleryImageInputComponent } from '@shared/components/image/multiple-gallery-image-input.component';
import { EmbedImageDialogComponent } from '@shared/components/image/embed-image-dialog.component'; import { EmbedImageDialogComponent } from '@shared/components/image/embed-image-dialog.component';
import { ImageGalleryDialogComponent } from '@shared/components/image/image-gallery-dialog.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService; return markedOptionsService;
@ -410,7 +411,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
ImagesInUseDialogComponent, ImagesInUseDialogComponent,
GalleryImageInputComponent, GalleryImageInputComponent,
MultipleGalleryImageInputComponent, MultipleGalleryImageInputComponent,
EmbedImageDialogComponent EmbedImageDialogComponent,
ImageGalleryDialogComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -660,7 +662,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
ImagesInUseDialogComponent, ImagesInUseDialogComponent,
GalleryImageInputComponent, GalleryImageInputComponent,
MultipleGalleryImageInputComponent, MultipleGalleryImageInputComponent,
EmbedImageDialogComponent EmbedImageDialogComponent,
ImageGalleryDialogComponent
] ]
}) })
export class SharedModule { } export class SharedModule { }