Browse Source

UI: Improve image gallery browse action -> switch from popover to responsive dialog.

pull/9966/merge
Igor Kulikov 3 years ago
parent
commit
bd2c991eb2
  1. 5
      ui-ngx/src/app/shared/components/image/gallery-image-input.component.html
  2. 56
      ui-ngx/src/app/shared/components/image/gallery-image-input.component.ts
  3. 32
      ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.html
  4. 32
      ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.scss
  5. 62
      ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.ts
  6. 16
      ui-ngx/src/app/shared/components/image/image-gallery.component.html
  7. 34
      ui-ngx/src/app/shared/components/image/image-gallery.component.scss
  8. 21
      ui-ngx/src/app/shared/components/image/image-gallery.component.ts
  9. 7
      ui-ngx/src/app/shared/shared.module.ts

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

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

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

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

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

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

32
ui-ngx/src/app/shared/components/image/image-gallery-dialog.component.scss

@ -0,0 +1,32 @@
@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);
}
}
}

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

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

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

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

34
ui-ngx/src/app/shared/components/image/image-gallery.component.scss

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

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

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

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

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

Loading…
Cancel
Save