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 // PE: generic permission
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
TenantId tenantId = getTenantId(); TenantId tenantId = getTenantId();
if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN || includeSystemImages) { if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN || !includeSystemImages) {
return checkNotNull(imageService.getImagesByTenantId(tenantId, pageLink)); return checkNotNull(imageService.getImagesByTenantId(tenantId, pageLink));
} else { } else {
return checkNotNull(imageService.getAllImagesByTenantId(tenantId, pageLink)); 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)); imageInfo, defaultHttpOptionsFromConfig(config));
} }
public getImages(pageLink: PageLink, config?: RequestConfig): Observable<PageData<ImageResourceInfo>> { public getImages(pageLink: PageLink, includeSystemImages = false, config?: RequestConfig): Observable<PageData<ImageResourceInfo>> {
return this.http.get<PageData<ImageResourceInfo>>(`${IMAGES_URL_PREFIX}${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); 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> { 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 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}"> <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()"> <mat-toolbar class="mat-mdc-table-toolbar"
<div class="mat-toolbar-tools"> fxLayout.lt-md="column" fxLayoutAlign.lt-md="start stretch"
<span fxHide fxShow.gt-sm class="tb-images-title" translate>image.gallery</span> [fxShow]="!textSearchMode && dataSource.selection.isEmpty()" [ngClass.lt-md]="{'multi-row': !isSysAdmin}">
<div class="tb-images-view-type-toolbar" <div fxFlex fxLayout="row" fxLayoutAlign="start center">
fxLayout="row" <div class="mat-toolbar-tools">
fxLayoutAlign="start center"> <span fxHide fxShow.gt-sm class="tb-images-title" translate>image.gallery</span>
<div class="tb-toolbar-button" [ngClass]="{'tb-selected' : mode === 'list'}"> <div class="tb-images-view-type-toolbar"
<button mat-icon-button fxLayout="row"
(click)="setMode('list')" fxLayoutAlign="start center">
matTooltip="{{'image.list-mode' | translate }}" <div class="tb-toolbar-button" [ngClass]="{'tb-selected' : mode === 'list'}">
matTooltipPosition="below"> <button mat-icon-button
<mat-icon>view_list</mat-icon> (click)="setMode('list')"
</button> matTooltip="{{'image.list-mode' | translate }}"
</div> matTooltipPosition="below">
<div class="tb-toolbar-button" [ngClass]="{'tb-selected' : mode === 'grid'}"> <mat-icon>view_list</mat-icon>
<button mat-icon-button </button>
(click)="setMode('grid');" </div>
matTooltip="{{'image.grid-mode' | translate }}" <div class="tb-toolbar-button" [ngClass]="{'tb-selected' : mode === 'grid'}">
matTooltipPosition="below"> <button mat-icon-button
<tb-icon>mdi:view-grid</tb-icon> (click)="setMode('grid');"
</button> matTooltip="{{'image.grid-mode' | translate }}"
matTooltipPosition="below">
<tb-icon>mdi:view-grid</tb-icon>
</button>
</div>
</div> </div>
<mat-slide-toggle *ngIf="!isSysAdmin"
fxHide fxShow.gt-sm
[formControl]="includeSystemImages">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
</div> </div>
</div> <section fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<section fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="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" mat-icon-button
mat-icon-button (click)="enterFilterMode()"
(click)="enterFilterMode()" matTooltip="{{'action.search' | translate }}"
matTooltip="{{'action.search' | translate }}" matTooltipPosition="above">
matTooltipPosition="above"> <mat-icon>search</mat-icon>
<mat-icon>search</mat-icon> </button>
</button> <button [disabled]="isLoading$ | async"
<button [disabled]="isLoading$ | async" mat-icon-button
mat-icon-button (click)="updateData()"
(click)="updateData()" matTooltip="{{'action.refresh' | translate }}"
matTooltip="{{'action.refresh' | translate }}" matTooltipPosition="above">
matTooltipPosition="above"> <mat-icon>refresh</mat-icon>
<mat-icon>refresh</mat-icon> </button>
</button> <button [disabled]="isLoading$ | async"
<button [disabled]="isLoading$ | async" mat-icon-button
mat-icon-button (click)="importImage()"
(click)="importImage()" matTooltip="{{'image.import-image' | translate }}"
matTooltip="{{'image.import-image' | translate }}" matTooltipPosition="above">
matTooltipPosition="above"> <tb-icon>mdi:file-import</tb-icon>
<tb-icon>mdi:file-import</tb-icon> </button>
</button> <button fxHide fxShow.lt-lg
<button fxHide fxShow.lt-sm [disabled]="isLoading$ | async" [disabled]="isLoading$ | async"
mat-icon-button 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" color="primary"
(click)="uploadImage()" (click)="uploadImage()">
matTooltip="{{'image.upload-image' | translate }}" {{ 'image.upload-image' | translate }}
matTooltipPosition="above">
<mat-icon>upload</mat-icon>
</button> </button>
</section> </section>
<button fxHide fxShow.gt-xs [disabled]="isLoading$ | async" </div>
mat-flat-button <mat-slide-toggle *ngIf="!isSysAdmin" fxHide fxShow.lt-md
color="primary" [formControl]="includeSystemImages">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
(click)="uploadImage()">
{{ 'image.upload-image' | translate }}
</button>
</section>
</mat-toolbar> </mat-toolbar>
<mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="textSearchMode && dataSource.selection.isEmpty()"> <mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="textSearchMode && dataSource.selection.isEmpty()">
<div class="mat-toolbar-tools"> <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; 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 { .tb-images-title {
padding-right: 20px; padding-right: 20px;
white-space: nowrap; 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'; } from '@home/components/image/images-in-use-dialog.component';
import { ImagesDatasource } from '@home/components/image/images-datasource'; import { ImagesDatasource } from '@home/components/image/images-datasource';
interface GridImagesFilter {
search: string;
includeSystemImages: boolean;
}
@Component({ @Component({
selector: 'tb-image-gallery', selector: 'tb-image-gallery',
templateUrl: './image-gallery.component.html', templateUrl: './image-gallery.component.html',
@ -104,6 +109,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
dataSource: ImagesDatasource; dataSource: ImagesDatasource;
textSearch = this.fb.control('', {nonNullable: true}); textSearch = this.fb.control('', {nonNullable: true});
includeSystemImages = this.fb.control(false);
gridColumns: ScrollGridColumns = { gridColumns: ScrollGridColumns = {
columns: 2, columns: 2,
@ -118,8 +124,11 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
} }
}; };
gridImagesFetchFunction: GridEntitiesFetchFunction<ImageResourceInfo, string>; gridImagesFetchFunction: GridEntitiesFetchFunction<ImageResourceInfo, GridImagesFilter>;
gridImagesFilter = ''; gridImagesFilter: GridImagesFilter = {
search: '',
includeSystemImages: false
};
gridImagesItemSizeStrategy: ItemSizeStrategy = { gridImagesItemSizeStrategy: ItemSizeStrategy = {
defaultItemSize: 200, defaultItemSize: 200,
@ -148,16 +157,16 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
super(store); super(store);
this.gridImagesFetchFunction = (pageSize, page, filter) => { this.gridImagesFetchFunction = (pageSize, page, filter) => {
const pageLink = new PageLink(pageSize, page, filter, { const pageLink = new PageLink(pageSize, page, filter.search, {
property: 'createdTime', property: 'createdTime',
direction: Direction.DESC direction: Direction.DESC
}); });
return this.imageService.getImages(pageLink); return this.imageService.getImages(pageLink, filter.includeSystemImages);
}; };
} }
ngOnInit(): void { ngOnInit(): void {
this.displayedColumns = ['select', 'preview', 'title', 'createdTime', 'resolution', 'size', 'system', 'actions']; 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];
const routerQueryParams: PageQueryParam = this.route.snapshot.queryParams; const routerQueryParams: PageQueryParam = this.route.snapshot.queryParams;
@ -208,7 +217,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
this.textSearch.valueChanges.pipe( this.textSearch.valueChanges.pipe(
debounceTime(150), debounceTime(150),
distinctUntilChanged((prev, current) => 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$) takeUntil(this.destroy$)
).subscribe(value => { ).subscribe(value => {
if (this.mode === 'list') { if (this.mode === 'list') {
@ -224,7 +233,25 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
this.updateData(); this.updateData();
} }
} else { } 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(); 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() { private updateMode() {
if (this.mode === 'list') { if (this.mode === 'list') {
this.initListMode(); this.initListMode();
@ -346,7 +386,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
} else { } else {
this.pageLink.sortOrder = null; this.pageLink.sortOrder = null;
} }
this.dataSource.loadEntities(this.pageLink); this.dataSource.loadEntities(this.pageLink, this.includeSystemImages.value);
} else { } else {
this.gridComponent.update(); this.gridComponent.update();
} }
@ -386,7 +426,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
} }
isSystem(image?: ImageResourceInfo): boolean { isSystem(image?: ImageResourceInfo): boolean {
return image?.tenantId?.id === NULL_UUID; return !this.isSysAdmin && image?.tenantId?.id === NULL_UUID;
} }
readonly(image?: ImageResourceInfo): boolean { 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; this.dataLoading = true;
const result = new ReplaySubject<PageData<ImageResourceInfo>>(); const result = new ReplaySubject<PageData<ImageResourceInfo>>();
this.fetchEntities(pageLink).pipe( this.fetchEntities(pageLink, includeSystemImages).pipe(
tap(() => { tap(() => {
this.selection.clear(); this.selection.clear();
}), }),
@ -83,8 +83,8 @@ export class ImagesDatasource implements DataSource<ImageResourceInfo> {
return result; return result;
} }
fetchEntities(pageLink: PageLink): Observable<PageData<ImageResourceInfo>> { fetchEntities(pageLink: PageLink, includeSystemImages = false): Observable<PageData<ImageResourceInfo>> {
return this.imageService.getImages(pageLink); return this.imageService.getImages(pageLink, includeSystemImages);
} }
isAllSelected(): Observable<boolean> { 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.", "delete-image-in-use-text": "If you still want to delete the image, click the <b>Delete anyway</b> button.",
"system-entities": "System entities:", "system-entities": "System entities:",
"entities": "entities:", "entities": "entities:",
"references": "References" "references": "References",
"include-system-images": "Include system images"
}, },
"image-input": { "image-input": {
"drop-images-or": "Drag and drop an images or", "drop-images-or": "Drag and drop an images or",

Loading…
Cancel
Save