Browse Source

UI: Improve edit image. Improve image gallery grid view.

pull/9542/head
Igor Kulikov 3 years ago
parent
commit
3e6f101a88
  1. 7
      ui-ngx/src/app/core/services/menu.service.ts
  2. 6
      ui-ngx/src/app/modules/home/components/grid/scroll-grid.component.html
  3. 8
      ui-ngx/src/app/modules/home/components/grid/scroll-grid.component.ts
  4. 102
      ui-ngx/src/app/modules/home/components/image/image-dialog.component.html
  5. 32
      ui-ngx/src/app/modules/home/components/image/image-dialog.component.scss
  6. 52
      ui-ngx/src/app/modules/home/components/image/image-dialog.component.ts
  7. 98
      ui-ngx/src/app/modules/home/components/image/image-gallery.component.html
  8. 61
      ui-ngx/src/app/modules/home/components/image/image-gallery.component.scss
  9. 70
      ui-ngx/src/app/modules/home/components/image/image-gallery.component.ts
  10. 22
      ui-ngx/src/app/modules/home/models/datasource/scroll-grid-datasource.ts

7
ui-ngx/src/app/core/services/menu.service.ts

@ -534,6 +534,13 @@ export class MenuService {
}
]
},
{
id: 'images',
name: 'image.gallery',
type: 'link',
path: '/resources/images',
icon: 'filter'
},
{
id: 'resources_library',
name: 'resource.resources-library',

6
ui-ngx/src/app/modules/home/components/grid/scroll-grid.component.html

@ -16,14 +16,14 @@
-->
<cdk-virtual-scroll-viewport #viewport class="tb-scroll-grid-viewport" [itemSize]="itemSize" appendOnly>
<ng-container *cdkVirtualFor="let itemsRow of dataSource; trackBy: trackByItemsRow">
<ng-container *cdkVirtualFor="let itemsRow of dataSource; let row = index; trackBy: trackByItemsRow">
<div *ngIf="itemsRow" class="tb-scroll-grid-items-row" [style]="{gap: gap+'px'}">
<div *ngFor="let item of itemsRow; trackBy: trackByItem" class="tb-scroll-grid-item-container">
<div *ngFor="let item of itemsRow; let col = index; trackBy: trackByItem" class="tb-scroll-grid-item-container">
<ng-container *ngIf="item === 'loadingCell'">
<ng-container *ngTemplateOutlet="loadingCell ? loadingCell : defaultLoadingCell"></ng-container>
</ng-container>
<ng-container *ngIf="isObject(item)">
<ng-container *ngTemplateOutlet="itemCard; context:{ item: item }"></ng-container>
<ng-container *ngTemplateOutlet="itemCard; context:{ item: item, itemIndex: row * dataSource.currentColumns + col }"></ng-container>
</ng-container>
</div>
</div>

8
ui-ngx/src/app/modules/home/components/grid/scroll-grid.component.ts

@ -112,4 +112,12 @@ export class ScrollGridComponent<T, F> implements OnInit, AfterViewInit, OnChang
public update() {
this.dataSource.update();
}
public updateItem(index: number, item: T) {
this.dataSource.updateItem(index, item);
}
public deleteItem(index: number) {
this.dataSource.deleteItem(index);
}
}

102
ui-ngx/src/app/modules/home/components/image/image-dialog.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<form [formGroup]="imageFormGroup" (ngSubmit)="save()" style="width: 560px;">
<form class="tb-image-dialog" [formGroup]="imageFormGroup">
<mat-toolbar color="primary">
<h2>{{ (readonly ? 'image.image-details' : 'image.edit-image') | translate }}</h2>
<span fxFlex></span>
@ -30,65 +30,73 @@
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async">
<mat-form-field class="mat-block">
<mat-label translate>image.name</mat-label>
<input matInput formControlName="title" required>
<mat-error *ngIf="imageFormGroup.get('title').hasError('required')">
{{ 'image.name-required' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="row" fxLayoutGap="8px">
<mat-form-field fxFlex>
<mat-label translate>image.name</mat-label>
<input matInput formControlName="title" required>
<mat-error *ngIf="imageFormGroup.get('title').hasError('required')">
{{ 'image.name-required' | translate }}
</mat-error>
<button *ngIf="!readonly && imageFormGroup.dirty"
matIconSuffix
type="button"
mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'action.undo' | translate }}"
matTooltipPosition="above"
(click)="revertInfo()">
<mat-icon>close</mat-icon>
</button>
<button *ngIf="!readonly && !imageFormGroup.invalid && imageFormGroup.dirty"
matIconSuffix
type="button"
color="primary"
mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'action.save' | translate }}"
matTooltipPosition="above"
(click)="saveInfo()">
<mat-icon>check</mat-icon>
</button>
</mat-form-field>
</div>
<div class="tb-image-container tb-primary-fill">
<div class="tb-image-content">
<div class="tb-image-preview-title" translate>image.image-preview</div>
<div class="tb-image-preview-container">
<div class="tb-image-preview-spacer"></div>
<img class="tb-image-preview" [src]="imagePreviewData | image: {preview: true} | async">
</div>
<div class="tb-image-preview-details">
<div>{{ image.descriptor.width }}x{{ image.descriptor.height }}</div>
<mat-divider vertical></mat-divider>
<div>{{ image.descriptor.size | fileSize }}</div>
</div>
<div class="tb-image-actions">
<button type="button"
mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.download-image' | translate }}"
matTooltipPosition="above"
(click)="downloadImage($event)">
<mat-icon>file_download</mat-icon>
</button>
<button type="button"
mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.export-image' | translate }}"
matTooltipPosition="above"
(click)="exportImage($event)">
<tb-icon>mdi:file-export</tb-icon>
</button>
<div class="tb-image-actions">
<button type="button"
mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.download-image' | translate }}"
matTooltipPosition="above"
(click)="downloadImage($event)">
<mat-icon>file_download</mat-icon>
</button>
<button type="button"
mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.export-image' | translate }}"
matTooltipPosition="above"
(click)="exportImage($event)">
<tb-icon>mdi:file-export</tb-icon>
</button>
</div>
<button *ngIf="!readonly"
type="button"
mat-stroked-button
mat-flat-button
color="primary"
[disabled]="isLoading$ | async"
(click)="updateImage($event)">
{{ 'image.update-image' | translate }}
</button>
</div>
<div class="tb-image-preview-container">
<div class="tb-image-preview-spacer"></div>
<img class="tb-image-preview" [src]="imagePreviewData | image | async">
</div>
<div class="tb-image-preview-details">
<div>{{ image.descriptor.width }}x{{ image.descriptor.height }}</div>
<mat-divider vertical></mat-divider>
<div>{{ image.descriptor.size | fileSize }}</div>
</div>
</div>
</div>
</fieldset>
</div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ (readonly ? 'action.close' : 'action.cancel') | translate }}
</button>
<button *ngIf="!readonly" mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || imageFormGroup.invalid
|| !imageFormGroup.dirty">
{{ 'action.save' | translate }}
</button>
</div>
</form>

32
ui-ngx/src/app/modules/home/components/image/image-dialog.component.scss

@ -14,9 +14,24 @@
* limitations under the License.
*/
@import '../../../../../scss/constants';
:host {
.tb-image-dialog {
@media #{$mat-gt-xs} {
width: 50vh;
}
.mat-mdc-dialog-content {
max-height: 100%;
}
fieldset {
height: 100%;
display: flex;
flex-direction: column;
}
}
.tb-image-container {
width: fit-content;
height: 100%;
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.05);
padding: 12px;
@ -26,17 +41,12 @@
gap: 12px;
.tb-image-content {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
.tb-image-preview-title {
color: rgba(0, 0, 0, 0.54);
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 16px;
}
.tb-image-preview-container {
position: relative;
width: 100%;
@ -49,7 +59,9 @@
justify-content: center;
z-index: 1;
.tb-image-preview-spacer {
margin-top: 100%;
@media #{$mat-gt-xs} {
margin-top: 100%;
}
}
.tb-image-preview {
position: absolute;
@ -75,6 +87,8 @@
.tb-image-actions {
display: flex;
align-items: center;
align-self: stretch;
justify-content: space-between;
gap: 8px;
color: rgba(0,0,0,0.54);
}

52
ui-ngx/src/app/modules/home/components/image/image-dialog.component.ts

@ -14,19 +14,12 @@
/// limitations under the License.
///
import { ChangeDetectorRef, Component, Inject, OnInit, SkipSelf } from '@angular/core';
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 {
FormGroupDirective,
NgForm,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validators
} from '@angular/forms';
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';
@ -45,11 +38,10 @@ export interface ImageDialogData {
@Component({
selector: 'tb-image-dialog',
templateUrl: './image-dialog.component.html',
providers: [{provide: ErrorStateMatcher, useExisting: ImageDialogComponent}],
styleUrls: ['./image-dialog.component.scss']
})
export class ImageDialogComponent extends
DialogComponent<ImageDialogComponent, boolean> implements OnInit, ErrorStateMatcher {
DialogComponent<ImageDialogComponent, ImageResourceInfo> implements OnInit {
image: ImageResourceInfo;
@ -57,8 +49,6 @@ export class ImageDialogComponent extends
imageFormGroup: UntypedFormGroup;
submitted = false;
imageChanged = false;
imagePreviewData: UrlHolder;
@ -68,8 +58,7 @@ export class ImageDialogComponent extends
private imageService: ImageService,
private dialog: MatDialog,
@Inject(MAT_DIALOG_DATA) private data: ImageDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<ImageDialogComponent, boolean>,
public dialogRef: MatDialogRef<ImageDialogComponent, ImageResourceInfo>,
public fb: UntypedFormBuilder) {
super(store, router, dialogRef);
this.image = data.image;
@ -88,14 +77,25 @@ export class ImageDialogComponent extends
}
}
isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const originalErrorState = this.errorStateMatcher.isErrorState(control, form);
const customErrorState = !!(control && control.invalid && this.submitted);
return originalErrorState || customErrorState;
cancel(): void {
this.dialogRef.close(this.imageChanged ? this.image : null);
}
cancel(): void {
this.dialogRef.close(this.imageChanged);
revertInfo(): void {
this.imageFormGroup.get('title').setValue(this.image.title);
this.imageFormGroup.markAsPristine();
}
saveInfo(): void {
const title: string = this.imageFormGroup.get('title').value;
const image = {...this.image, ...{title}};
this.imageService.updateImageInfo(image).subscribe(
(saved) => {
this.image = saved;
this.imageChanged = true;
this.imageFormGroup.markAsPristine();
}
);
}
downloadImage($event) {
@ -134,14 +134,4 @@ export class ImageDialogComponent extends
});
}
save(): void {
this.submitted = true;
const title: string = this.imageFormGroup.get('title').value;
const image = {...this.image, ...{title}};
this.imageService.updateImageInfo(image).subscribe(
() => {
this.dialogRef.close(true);
}
);
}
}

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

@ -19,7 +19,7 @@
<div fxFlex fxLayout="column" class="tb-images-content" [ngClass]="{'tb-outlined-border': pageMode}">
<mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="!textSearchMode && dataSource.selection.isEmpty()">
<div class="mat-toolbar-tools">
<span class="tb-images-title" translate>image.gallery</span>
<span fxHide fxShow.gt-sm class="tb-images-title" translate>image.gallery</span>
<div class="tb-images-view-type-toolbar"
fxLayout="row"
fxLayoutAlign="start center">
@ -43,7 +43,7 @@
</div>
<section fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<span fxFlex></span>
<section fxLayout="row">
<section fxLayout="row" fxLayoutAlign="start center">
<button [disabled]="isLoading$ | async"
mat-icon-button
(click)="enterFilterMode()"
@ -63,10 +63,18 @@
(click)="importImage()"
matTooltip="{{'image.import-image' | translate }}"
matTooltipPosition="above">
<tb-icon>mdi:file-import</tb-icon>
</button>
<button fxHide fxShow.lt-sm [disabled]="isLoading$ | async"
mat-icon-button
color="primary"
(click)="uploadImage()"
matTooltip="{{'image.upload-image' | translate }}"
matTooltipPosition="above">
<mat-icon>upload</mat-icon>
</button>
</section>
<button [disabled]="isLoading$ | async"
<button fxHide fxShow.gt-xs [disabled]="isLoading$ | async"
mat-flat-button
color="primary"
(click)="uploadImage()">
@ -121,6 +129,7 @@
</mat-header-cell>
<mat-cell *matCellDef="let image">
<mat-checkbox (click)="$event.stopPropagation()"
[fxShow]="!readonly(image)"
(change)="$event ? dataSource.selection.toggle(image) : null"
[checked]="dataSource.selection.isSelected(image)">
</mat-checkbox>
@ -139,19 +148,19 @@
</mat-cell>
</ng-container>
<ng-container matColumnDef="createdTime">
<mat-header-cell *matHeaderCellDef mat-sort-header style="width: 200px; min-width: 200px;"> {{ 'image.created-time' | translate }} </mat-header-cell>
<mat-header-cell *matHeaderCellDef mat-sort-header style="width: 160px; min-width: 160px;"> {{ 'image.created-time' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let image">
{{ image.createdTime | date:'yyyy-MM-dd HH:mm:ss' }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="resolution">
<mat-header-cell *matHeaderCellDef style="width: 160px; min-width: 160px;"> {{ 'image.resolution' | translate }} </mat-header-cell>
<mat-header-cell *matHeaderCellDef style="width: 80px; min-width: 80px;"> {{ 'image.resolution' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let image">
{{ image.descriptor.width }}x{{ image.descriptor.height }}
</mat-cell>
</ng-container>
<ng-container matColumnDef="size">
<mat-header-cell *matHeaderCellDef style="width: 160px; min-width: 160px;"> {{ 'image.size' | translate }} </mat-header-cell>
<mat-header-cell *matHeaderCellDef style="width: 80px; min-width: 80px;"> {{ 'image.size' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let image">
{{ image.descriptor.size | fileSize }}
</mat-cell>
@ -163,10 +172,10 @@
</mat-cell>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<mat-header-cell *matHeaderCellDef style="min-width: 192px; max-width: 192px; width: 192px">
<mat-header-cell *matHeaderCellDef [ngStyle.gt-md]="{ minWidth: '192px', maxWidth: '192px', width: '192px' }">
</mat-header-cell>
<mat-cell *matCellDef="let image">
<div fxFlex fxLayout="row" fxLayoutAlign="end">
<mat-cell *matCellDef="let image" [ngStyle.gt-md]="{ minWidth: '192px', maxWidth: '192px', width: '192px' }">
<div fxHide fxShow.gt-md fxFlex fxLayout="row" fxLayoutAlign="end">
<button mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.download-image' | translate }}"
matTooltipPosition="above"
@ -180,7 +189,7 @@
<tb-icon>mdi:file-export</tb-icon>
</button>
<button mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.edit-image' | translate }}"
matTooltip="{{ (readonly(image) ? 'image.image-details' : 'image.edit-image') | translate }}"
matTooltipPosition="above"
(click)="editImage($event, image)">
<mat-icon>edit</mat-icon>
@ -192,6 +201,40 @@
<mat-icon>delete</mat-icon>
</button>
</div>
<div fxHide fxShow.lt-lg>
<button mat-icon-button
(click)="$event.stopPropagation()"
[matMenuTriggerFor]="cellActionsMenu">
<mat-icon class="material-icons">more_vert</mat-icon>
</button>
<mat-menu #cellActionsMenu="matMenu" xPosition="before">
<button mat-menu-item
[disabled]="isLoading$ | async"
(click)="downloadImage($event, image)">
<mat-icon>file_download</mat-icon>
<span translate>image.download-image</span>
</button>
<button mat-menu-item
[disabled]="isLoading$ | async"
(click)="exportImage($event, image)">
<tb-icon matMenuItemIcon>mdi:file-export</tb-icon>
<span translate>image.export-image</span>
</button>
<button mat-menu-item
[disabled]="isLoading$ | async"
(click)="editImage($event, image)">
<mat-icon>edit</mat-icon>
<span>{{ (readonly(image) ? 'image.image-details' : 'image.edit-image') | translate }}</span>
</button>
<button mat-menu-item
[disabled]="isLoading$ | async"
[fxShow]="!readonly(image)"
(click)="deleteImage($event, image)">
<mat-icon>delete</mat-icon>
<span translate>image.delete-image</span>
</button>
</mat-menu>
</div>
</mat-cell>
</ng-container>
<mat-header-row [ngClass]="{'mat-row-select': true}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
@ -243,9 +286,40 @@
<div class="tb-no-data-text" translate>image.no-images</div>
</div>
</ng-template>
<ng-template #imageCard let-item="item">
<div class="tb-image-card">
<ng-template #imageCard let-item="item" let-itemIndex="itemIndex">
<div class="tb-image-card tb-primary-fill">
<div class="tb-image-card-overlay">
<div class="tb-image-card-overlay-buttons">
<button mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.download-image' | translate }}"
matTooltipPosition="above"
(click)="downloadImage($event, item)">
<mat-icon>file_download</mat-icon>
</button>
<button mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'image.export-image' | translate }}"
matTooltipPosition="above"
(click)="exportImage($event, item)">
<tb-icon>mdi:file-export</tb-icon>
</button>
<button *ngIf="!readonly(item)"
mat-icon-button [disabled]="(isLoading$ | async)"
matTooltip="{{ 'image.delete-image' | translate }}"
matTooltipPosition="above"
(click)="deleteImage($event, item, itemIndex)">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
<div class="tb-image-preview-container">
<div class="tb-image-preview-overlay">
<button
mat-flat-button
color="primary"
(click)="editImage($event, item, itemIndex)">
{{ (readonly(item) ? 'image.image-details' : 'image.edit-image') | translate }}
</button>
</div>
<div class="tb-image-preview-spacer"></div>
<img class="tb-image-preview" [src]="item.link | image: {preview: true} | async" alt="{{ item.title }}">
</div>

61
ui-ngx/src/app/modules/home/components/image/image-gallery.component.scss

@ -77,17 +77,70 @@ $tb-button-selected-color: rgb(255, 110, 64) !default;
}
.tb-image-card {
position: relative;
height: 100%;
border-radius: 8px;
border-radius: 4px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.24);
padding: 8px;
display: flex;
gap: 8px;
flex-direction: column;
cursor: pointer;
transition: box-shadow 0.2s;
.tb-image-card-overlay {
position: absolute;
pointer-events: none;
inset: 0;
border-radius: 4px;
z-index: 2;
display: flex;
flex-direction: column;
.tb-image-card-overlay-buttons {
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
color: rgba(0,0,0,0.78);
opacity: 0;
transition: opacity 0.5s;
}
}
&:hover {
.tb-image-card-overlay {
.tb-image-card-overlay-buttons {
opacity: 1;
}
}
.tb-image-preview-container {
.tb-image-preview-overlay {
background: rgba(245,245,245,0.6);
backdrop-filter: blur(4px);
.mdc-button {
opacity: 1;
}
}
}
}
.tb-image-preview-container {
position: relative;
.tb-image-preview-overlay {
position: absolute;
inset: 0;
z-index: 1;
background: rgba(245,245,245,0);
backdrop-filter: none;
transition: all 0.5s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.mdc-button {
opacity: 0;
transition: opacity 0.5s;
}
}
.tb-image-preview-spacer {
margin-top: 100%;
}
@ -152,10 +205,6 @@ $tb-button-selected-color: rgb(255, 110, 64) !default;
}
}
}
&:hover {
box-shadow: 0 0 28px 0 rgba(0, 0, 0, 0.24);
}
}
.table-container {

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

@ -21,7 +21,7 @@ import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { ImageService } from '@core/http/image.service';
import { TranslateService } from '@ngx-translate/core';
import { PageLink, PageQueryParam } from '@shared/models/page/page-link';
import { catchError, debounceTime, distinctUntilChanged, map, skip, takeUntil, tap } from 'rxjs/operators';
import { catchError, debounceTime, distinctUntilChanged, map, skip, take, takeUntil, tap } from 'rxjs/operators';
import {
AfterViewInit,
ChangeDetectorRef,
@ -38,7 +38,6 @@ import { MatPaginator } from '@angular/material/paginator';
import { MatSort, SortDirection } from '@angular/material/sort';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { UtilsService } from '@core/services/utils.service';
import { DialogService } from '@core/services/dialog.service';
import { FormBuilder } from '@angular/forms';
import { Direction, SortOrder } from '@shared/models/page/sort-order';
@ -52,19 +51,14 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
import { GridEntitiesFetchFunction, ScrollGridColumns } from '@home/models/datasource/scroll-grid-datasource';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { ScrollGridComponent } from '@home/components/grid/scroll-grid.component';
import {
AddWidgetDialogComponent,
AddWidgetDialogData
} from '@home/components/dashboard-page/add-widget-dialog.component';
import { Widget } from '@shared/models/widget.models';
import { MatDialog } from '@angular/material/dialog';
import {
UploadImageDialogComponent,
UploadImageDialogData
} from '@home/components/image/upload-image-dialog.component';
import { ImageDialogComponent, ImageDialogData } from '@home/components/image/image-dialog.component';
import { EntityBooleanFunction } from '@home/models/entity/entities-table-config.models';
@Component({
selector: 'tb-image-gallery',
@ -178,7 +172,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
}
}
if (this.mode === 'list') {
this.dataSource = new ImagesDatasource(this.imageService);
this.dataSource = new ImagesDatasource(this.imageService, entity => !this.readonly(entity));
}
}
@ -234,7 +228,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
}
this.mode = targetMode;
if (this.mode === 'list') {
this.dataSource = new ImagesDatasource(this.imageService);
this.dataSource = new ImagesDatasource(this.imageService, entity => !this.readonly(entity));
}
setTimeout(() => {
this.updateMode();
@ -341,6 +335,22 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
}
}
private imageUpdated(image: ImageResourceInfo, index = -1) {
if (this.mode === 'list') {
this.updateData();
} else {
this.gridComponent.updateItem(index, image);
}
}
private imageDeleted(index = -1) {
if (this.mode === 'list') {
this.updateData();
} else {
this.gridComponent.deleteItem(index);
}
}
enterFilterMode() {
this.textSearchMode = true;
setTimeout(() => {
@ -366,7 +376,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
return this.authUser.authority !== Authority.SYS_ADMIN && this.isSystem(image);
}
deleteImage($event: Event, image: ImageResourceInfo) {
deleteImage($event: Event, image: ImageResourceInfo, itemIndex = -1) {
if ($event) {
$event.stopPropagation();
}
@ -378,7 +388,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
if (result) {
this.imageService.deleteImage(imageResourceType(image), image.resourceKey).subscribe(
() => {
this.updateData();
this.imageDeleted(itemIndex);
}
);
}
@ -440,12 +450,12 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
});
}
editImage($event: Event, image: ImageResourceInfo) {
editImage($event: Event, image: ImageResourceInfo, itemIndex = -1) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<ImageDialogComponent, ImageDialogData,
boolean>(ImageDialogComponent, {
ImageResourceInfo>(ImageDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
@ -454,7 +464,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
}
}).afterClosed().subscribe((result) => {
if (result) {
this.updateData();
this.imageUpdated(result, itemIndex);
}
});
}
@ -486,7 +496,8 @@ class ImagesDatasource implements DataSource<ImageResourceInfo> {
public dataLoading = true;
constructor(private imageService: ImageService) {
constructor(private imageService: ImageService,
protected selectionEnabledFunction: EntityBooleanFunction<ImageResourceInfo>) {
}
connect(collectionViewer: CollectionViewer):
@ -548,15 +559,24 @@ class ImagesDatasource implements DataSource<ImageResourceInfo> {
}
masterToggle() {
const entities = this.entitiesSubject.getValue();
const numSelected = this.selection.selected.length;
if (numSelected === entities.length) {
this.selection.clear();
} else {
entities.forEach(row => {
this.selection.select(row);
});
}
this.entitiesSubject.pipe(
tap((entities) => {
const numSelected = this.selection.selected.length;
if (numSelected === this.selectableEntitiesCount(entities)) {
this.selection.clear();
} else {
entities.forEach(row => {
if (this.selectionEnabledFunction(row)) {
this.selection.select(row);
}
});
}
}),
take(1)
).subscribe();
}
private selectableEntitiesCount(entities: Array<ImageResourceInfo>): number {
return entities.filter((entity) => this.selectionEnabledFunction(entity)).length;
}
}

22
ui-ngx/src/app/modules/home/models/datasource/scroll-grid-datasource.ts

@ -88,6 +88,10 @@ export class ScrollGridDatasource<T, F> extends DataSource<(T | GridCellType)[]>
return !!this._subscription && !this._subscription.closed;
}
get currentColumns(): number {
return this._columns;
}
public updateFilter(filter: F) {
this.filter = filter;
this.update();
@ -123,6 +127,24 @@ export class ScrollGridDatasource<T, F> extends DataSource<(T | GridCellType)[]>
}
}
public updateItem(index: number, item: T) {
this._data[index] = item;
this._dataUpdated();
}
public deleteItem(index: number) {
if (index < this._data.length) {
this._data.splice(index, 1);
this._totalElements--;
const rowsLength = this._totalElements ? Math.ceil(this._totalElements / this._columns) : 100000;
this._rows = Array.from<T[]>({length: rowsLength});
this._dataUpdated();
if (this._hasNext) {
this._fetchDataFromRange(this._viewport.getRenderedRange());
}
}
}
private _detectColumns(): number {
if (typeof this.columns !== 'object') {
return this.columns;

Loading…
Cancel
Save