Browse Source

UI: Include system images switch.

pull/9542/head
Igor Kulikov 3 years ago
parent
commit
271da436ac
  1. 2
      application/src/main/java/org/thingsboard/server/controller/ImageController.java
  2. 6
      ui-ngx/src/app/core/http/image.service.ts
  3. 129
      ui-ngx/src/app/modules/home/components/image/image-gallery.component.html
  4. 12
      ui-ngx/src/app/modules/home/components/image/image-gallery.component.scss
  5. 58
      ui-ngx/src/app/modules/home/components/image/image-gallery.component.ts
  6. 8
      ui-ngx/src/app/modules/home/components/image/images-datasource.ts
  7. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
application/src/main/java/org/thingsboard/server/controller/ImageController.java

@ -208,7 +208,7 @@ public class ImageController extends BaseController {
// PE: generic permission
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
TenantId tenantId = getTenantId();
if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN || includeSystemImages) {
if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN || !includeSystemImages) {
return checkNotNull(imageService.getImagesByTenantId(tenantId, pageLink));
} else {
return checkNotNull(imageService.getAllImagesByTenantId(tenantId, pageLink));

6
ui-ngx/src/app/core/http/image.service.ts

@ -69,8 +69,10 @@ export class ImageService {
imageInfo, defaultHttpOptionsFromConfig(config));
}
public getImages(pageLink: PageLink, config?: RequestConfig): Observable<PageData<ImageResourceInfo>> {
return this.http.get<PageData<ImageResourceInfo>>(`${IMAGES_URL_PREFIX}${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
public getImages(pageLink: PageLink, includeSystemImages = false, config?: RequestConfig): Observable<PageData<ImageResourceInfo>> {
return this.http.get<PageData<ImageResourceInfo>>(
`${IMAGES_URL_PREFIX}${pageLink.toQuery()}&includeSystemImages=${includeSystemImages}`,
defaultHttpOptionsFromConfig(config));
}
public getImageInfo(type: ImageResourceType, key: string, config?: RequestConfig): Observable<ImageResourceInfo> {

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

@ -17,70 +17,81 @@
-->
<div class="mat-padding tb-images tb-absolute-fill" [ngClass]="{'mat-padding': pageMode}">
<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 fxHide fxShow.gt-sm class="tb-images-title" translate>image.gallery</span>
<div class="tb-images-view-type-toolbar"
fxLayout="row"
fxLayoutAlign="start center">
<div class="tb-toolbar-button" [ngClass]="{'tb-selected' : mode === 'list'}">
<button mat-icon-button
(click)="setMode('list')"
matTooltip="{{'image.list-mode' | translate }}"
matTooltipPosition="below">
<mat-icon>view_list</mat-icon>
</button>
</div>
<div class="tb-toolbar-button" [ngClass]="{'tb-selected' : mode === 'grid'}">
<button mat-icon-button
(click)="setMode('grid');"
matTooltip="{{'image.grid-mode' | translate }}"
matTooltipPosition="below">
<tb-icon>mdi:view-grid</tb-icon>
</button>
<mat-toolbar class="mat-mdc-table-toolbar"
fxLayout.lt-md="column" fxLayoutAlign.lt-md="start stretch"
[fxShow]="!textSearchMode && dataSource.selection.isEmpty()" [ngClass.lt-md]="{'multi-row': !isSysAdmin}">
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<div class="mat-toolbar-tools">
<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">
<div class="tb-toolbar-button" [ngClass]="{'tb-selected' : mode === 'list'}">
<button mat-icon-button
(click)="setMode('list')"
matTooltip="{{'image.list-mode' | translate }}"
matTooltipPosition="below">
<mat-icon>view_list</mat-icon>
</button>
</div>
<div class="tb-toolbar-button" [ngClass]="{'tb-selected' : mode === 'grid'}">
<button mat-icon-button
(click)="setMode('grid');"
matTooltip="{{'image.grid-mode' | translate }}"
matTooltipPosition="below">
<tb-icon>mdi:view-grid</tb-icon>
</button>
</div>
</div>
<mat-slide-toggle *ngIf="!isSysAdmin"
fxHide fxShow.gt-sm
[formControl]="includeSystemImages">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
</div>
</div>
<section fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<span fxFlex></span>
<section fxLayout="row" fxLayoutAlign="start center">
<button [disabled]="isLoading$ | async"
mat-icon-button
(click)="enterFilterMode()"
matTooltip="{{'action.search' | translate }}"
matTooltipPosition="above">
<mat-icon>search</mat-icon>
</button>
<button [disabled]="isLoading$ | async"
mat-icon-button
(click)="updateData()"
matTooltip="{{'action.refresh' | translate }}"
matTooltipPosition="above">
<mat-icon>refresh</mat-icon>
</button>
<button [disabled]="isLoading$ | async"
mat-icon-button
(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
<section fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<span fxFlex></span>
<section fxLayout="row" fxLayoutAlign="start center">
<button [disabled]="isLoading$ | async"
mat-icon-button
(click)="enterFilterMode()"
matTooltip="{{'action.search' | translate }}"
matTooltipPosition="above">
<mat-icon>search</mat-icon>
</button>
<button [disabled]="isLoading$ | async"
mat-icon-button
(click)="updateData()"
matTooltip="{{'action.refresh' | translate }}"
matTooltipPosition="above">
<mat-icon>refresh</mat-icon>
</button>
<button [disabled]="isLoading$ | async"
mat-icon-button
(click)="importImage()"
matTooltip="{{'image.import-image' | translate }}"
matTooltipPosition="above">
<tb-icon>mdi:file-import</tb-icon>
</button>
<button fxHide fxShow.lt-lg
[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 fxHide fxShow.gt-md
[disabled]="isLoading$ | async"
mat-flat-button
color="primary"
(click)="uploadImage()"
matTooltip="{{'image.upload-image' | translate }}"
matTooltipPosition="above">
<mat-icon>upload</mat-icon>
(click)="uploadImage()">
{{ 'image.upload-image' | translate }}
</button>
</section>
<button fxHide fxShow.gt-xs [disabled]="isLoading$ | async"
mat-flat-button
color="primary"
(click)="uploadImage()">
{{ 'image.upload-image' | translate }}
</button>
</section>
</div>
<mat-slide-toggle *ngIf="!isSysAdmin" fxHide fxShow.lt-md
[formControl]="includeSystemImages">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
</mat-toolbar>
<mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="textSearchMode && dataSource.selection.isEmpty()">
<div class="mat-toolbar-tools">

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

@ -28,6 +28,18 @@ $tb-button-selected-color: rgb(255, 110, 64) !default;
border-radius: 4px;
}
.mat-mdc-table-toolbar {
&.multi-row {
&.mat-toolbar-single-row {
height: 112px;
}
.mat-mdc-slide-toggle {
display: flex;
min-height: 48px;
}
}
}
.tb-images-title {
padding-right: 20px;
white-space: nowrap;

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

@ -69,6 +69,11 @@ import {
} from '@home/components/image/images-in-use-dialog.component';
import { ImagesDatasource } from '@home/components/image/images-datasource';
interface GridImagesFilter {
search: string;
includeSystemImages: boolean;
}
@Component({
selector: 'tb-image-gallery',
templateUrl: './image-gallery.component.html',
@ -104,6 +109,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
dataSource: ImagesDatasource;
textSearch = this.fb.control('', {nonNullable: true});
includeSystemImages = this.fb.control(false);
gridColumns: ScrollGridColumns = {
columns: 2,
@ -118,8 +124,11 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
}
};
gridImagesFetchFunction: GridEntitiesFetchFunction<ImageResourceInfo, string>;
gridImagesFilter = '';
gridImagesFetchFunction: GridEntitiesFetchFunction<ImageResourceInfo, GridImagesFilter>;
gridImagesFilter: GridImagesFilter = {
search: '',
includeSystemImages: false
};
gridImagesItemSizeStrategy: ItemSizeStrategy = {
defaultItemSize: 200,
@ -148,16 +157,16 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
super(store);
this.gridImagesFetchFunction = (pageSize, page, filter) => {
const pageLink = new PageLink(pageSize, page, filter, {
const pageLink = new PageLink(pageSize, page, filter.search, {
property: 'createdTime',
direction: Direction.DESC
});
return this.imageService.getImages(pageLink);
return this.imageService.getImages(pageLink, filter.includeSystemImages);
};
}
ngOnInit(): void {
this.displayedColumns = ['select', 'preview', 'title', 'createdTime', 'resolution', 'size', 'system', 'actions'];
this.displayedColumns = this.computeDisplayedColumns();
let sortOrder: SortOrder = this.defaultSortOrder;
this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
const routerQueryParams: PageQueryParam = this.route.snapshot.queryParams;
@ -208,7 +217,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
this.textSearch.valueChanges.pipe(
debounceTime(150),
distinctUntilChanged((prev, current) =>
((this.mode === 'list' ? this.pageLink.textSearch : this.gridImagesFilter) ?? '') === current.trim()),
((this.mode === 'list' ? this.pageLink.textSearch : this.gridImagesFilter.search) ?? '') === current.trim()),
takeUntil(this.destroy$)
).subscribe(value => {
if (this.mode === 'list') {
@ -224,7 +233,25 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
this.updateData();
}
} else {
this.gridImagesFilter = isNotEmptyStr(value) ? value.trim() : null;
this.gridImagesFilter = {
search: isNotEmptyStr(value) ? encodeURI(value) : null,
includeSystemImages: this.includeSystemImages.value
};
this.cd.markForCheck();
}
});
this.includeSystemImages.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
this.displayedColumns = this.computeDisplayedColumns();
this.gridImagesFilter = {
search: this.gridImagesFilter.search,
includeSystemImages: value
};
if (this.mode === 'list') {
this.paginator.pageIndex = 0;
this.updateData();
} else {
this.cd.markForCheck();
}
});
@ -253,6 +280,19 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
}
}
public get isSysAdmin(): boolean {
return this.authUser.authority === Authority.SYS_ADMIN;
}
private computeDisplayedColumns(): string[] {
const columns = ['select', 'preview', 'title', 'createdTime', 'resolution', 'size'];
if (!this.isSysAdmin && this.includeSystemImages.value) {
columns.push('system');
}
columns.push('actions');
return columns;
}
private updateMode() {
if (this.mode === 'list') {
this.initListMode();
@ -346,7 +386,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
} else {
this.pageLink.sortOrder = null;
}
this.dataSource.loadEntities(this.pageLink);
this.dataSource.loadEntities(this.pageLink, this.includeSystemImages.value);
} else {
this.gridComponent.update();
}
@ -386,7 +426,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
}
isSystem(image?: ImageResourceInfo): boolean {
return image?.tenantId?.id === NULL_UUID;
return !this.isSysAdmin && image?.tenantId?.id === NULL_UUID;
}
readonly(image?: ImageResourceInfo): boolean {

8
ui-ngx/src/app/modules/home/components/image/images-datasource.ts

@ -64,10 +64,10 @@ export class ImagesDatasource implements DataSource<ImageResourceInfo> {
}
}
loadEntities(pageLink: PageLink): Observable<PageData<ImageResourceInfo>> {
loadEntities(pageLink: PageLink, includeSystemImages = false): Observable<PageData<ImageResourceInfo>> {
this.dataLoading = true;
const result = new ReplaySubject<PageData<ImageResourceInfo>>();
this.fetchEntities(pageLink).pipe(
this.fetchEntities(pageLink, includeSystemImages).pipe(
tap(() => {
this.selection.clear();
}),
@ -83,8 +83,8 @@ export class ImagesDatasource implements DataSource<ImageResourceInfo> {
return result;
}
fetchEntities(pageLink: PageLink): Observable<PageData<ImageResourceInfo>> {
return this.imageService.getImages(pageLink);
fetchEntities(pageLink: PageLink, includeSystemImages = false): Observable<PageData<ImageResourceInfo>> {
return this.imageService.getImages(pageLink, includeSystemImages);
}
isAllSelected(): Observable<boolean> {

3
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2981,7 +2981,8 @@
"delete-image-in-use-text": "If you still want to delete the image, click the <b>Delete anyway</b> button.",
"system-entities": "System entities:",
"entities": "entities:",
"references": "References"
"references": "References",
"include-system-images": "Include system images"
},
"image-input": {
"drop-images-or": "Drag and drop an images or",

Loading…
Cancel
Save