UI: Improve image references component style. Improve browse image gallery popup.

This commit is contained in:
Igor Kulikov 2023-11-27 14:56:22 +02:00
parent bda319c3e8
commit cf942478b3
10 changed files with 192 additions and 59 deletions

View File

@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../../../../../scss/constants";
$containerHeight: 96px !default;
:host {
@ -94,6 +97,11 @@ $containerHeight: 96px !default;
gap: 4px;
.tb-resource-image-name {
color: rgba(0, 0, 0, 0.54);
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
font-size: 12px;
font-style: normal;
font-weight: 500;
@ -166,9 +174,12 @@ $containerHeight: 96px !default;
justify-content: center;
align-items: center;
gap: 4px;
padding: 16px;
padding: 8px;
line-height: normal;
font-size: 12px;
@media #{$mat-gt-xs} {
padding: 16px;
}
.mat-icon {
width: 24px;
height: 24px;

View File

@ -97,8 +97,7 @@ export class GalleryImageInputComponent extends PageComponent implements OnInit,
ngOnInit() {
this.externalLinkControl.valueChanges.subscribe((value) => {
if (this.linkType === ImageLinkType.external) {
this.imageUrl = value;
this.updateModel();
this.updateModel(value);
}
});
}
@ -168,21 +167,23 @@ export class GalleryImageInputComponent extends PageComponent implements OnInit,
}
}
private updateModel() {
private updateModel(value: string) {
this.cd.markForCheck();
if (this.imageUrl !== value) {
this.imageUrl = value;
this.propagateChange(this.imageUrl);
}
}
private reset() {
this.linkType = ImageLinkType.none;
this.imageUrl = null;
this.imageResource = null;
this.externalLinkControl.setValue(null, {emitEvent: false});
}
clearImage() {
this.reset();
this.updateModel();
this.updateModel(null);
}
setLink($event: Event) {
@ -214,9 +215,8 @@ export class GalleryImageInputComponent extends PageComponent implements OnInit,
imageGalleryPopover.tbComponentRef.instance.imageSelected.subscribe((image) => {
imageGalleryPopover.hide();
this.linkType = ImageLinkType.resource;
this.imageUrl = image.link;
this.imageResource = image;
this.updateModel();
this.updateModel(image.link);
});
}
}

View File

@ -45,7 +45,8 @@
</div>
<mat-slide-toggle *ngIf="!isSysAdmin"
fxHide fxShow.gt-sm
[formControl]="includeSystemImages">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
[ngModel]="includeSystemImages"
(ngModelChange)="includeSystemImagesChanged($event)">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
</div>
<section fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<span fxFlex></span>
@ -91,7 +92,8 @@
</section>
</div>
<mat-slide-toggle *ngIf="!isSysAdmin" fxHide fxShow.lt-md
[formControl]="includeSystemImages">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
[ngModel]="includeSystemImages"
(ngModelChange)="includeSystemImagesChanged($event)">{{ 'image.include-system-images' | translate }}</mat-slide-toggle>
</mat-toolbar>
<mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="textSearchMode && (mode === 'grid' || dataSource?.selection.isEmpty())">
<div class="mat-toolbar-tools">
@ -259,7 +261,7 @@
<mat-header-row [ngClass]="{'mat-row-select': true}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
<mat-row [ngClass]="{'mat-row-select': true,
'mat-selected': dataSource.selection.isSelected(image)}"
*matRowDef="let image; columns: displayedColumns;" (click)="dataSource.selection.toggle(image)"></mat-row>
*matRowDef="let image; columns: displayedColumns;" (click)="rowClick($event, image)"></mat-row>
</table>
<ng-container *ngIf="(dataSource.isEmpty() | async) && !dataSource.dataLoading">
<ng-container *ngTemplateOutlet="noImages"></ng-container>

View File

@ -28,23 +28,27 @@ $tb-button-selected-color: rgb(255, 110, 64) !default;
max-width: calc(100vw - 50px);
max-height: calc(100vh - 100px);
@media #{$mat-gt-xs} {
width: 400px;
height: 600px;
}
@media #{$mat-gt-sm} {
width: 500px;
height: 600px;
}
@media #{$mat-gt-md} {
@media #{$mat-gt-sm} {
width: 700px;
height: 600px;
}
@media #{$mat-gt-md} {
width: 1000px;
height: 700px;
}
@media #{$mat-gt-xmd} {
width: 900px;
width: 1300px;
height: 700px;
}
@media #{$mat-gt-xl} {
width: 1200px;
width: 1600px;
height: 800px;
}
@media screen and (min-width: 2320px) {
width: 2000px;
height: 800px;
}
}

View File

@ -88,11 +88,14 @@ const pageGridColumns: ScrollGridColumns = {
};
const popoverGridColumns: ScrollGridColumns = {
columns: 2,
columns: 1,
breakpoints: {
'gt-lg': 5,
'screen and (min-width: 1600px)': 4,
'gt-md': 3
'screen and (min-width: 2320px)': 8,
'gt-lg': 6,
'screen and (min-width: 1600px)': 5,
'gt-md': 4,
'gt-sm': 3,
'gt-xs': 2
}
};
@ -141,7 +144,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
dataSource: ImagesDatasource;
textSearch = this.fb.control('', {nonNullable: true});
includeSystemImages = this.fb.control(false);
includeSystemImages = false;
gridColumns: ScrollGridColumns;
@ -257,18 +260,20 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
} else {
this.gridImagesFilter = {
search: isNotEmptyStr(value) ? encodeURI(value) : null,
includeSystemImages: this.includeSystemImages.value
includeSystemImages: this.includeSystemImages
};
this.cd.markForCheck();
}
});
this.includeSystemImages.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
this.updateMode();
}
public includeSystemImagesChanged(value: boolean) {
this.includeSystemImages = value;
this.displayedColumns = this.computeDisplayedColumns();
this.gridImagesFilter = {
search: this.gridImagesFilter.search,
includeSystemImages: value
includeSystemImages: this.includeSystemImages
};
if (this.mode === 'list') {
this.paginator.pageIndex = 0;
@ -276,8 +281,6 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
} else {
this.cd.markForCheck();
}
});
this.updateMode();
}
public setMode(targetMode: 'list' | 'grid') {
@ -307,14 +310,20 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
}
private computeDisplayedColumns(): string[] {
let columns: string[];
if (this.selectionMode) {
return ['preview', 'title', 'imageSelect'];
columns = ['preview', 'title'];
if (!this.isSysAdmin && this.includeSystemImages) {
columns.push('system');
}
const columns = ['select', 'preview', 'title', 'createdTime', 'resolution', 'size'];
if (!this.isSysAdmin && this.includeSystemImages.value) {
columns.push('imageSelect');
} else {
columns = ['select', 'preview', 'title', 'createdTime', 'resolution', 'size'];
if (!this.isSysAdmin && this.includeSystemImages) {
columns.push('system');
}
columns.push('actions');
}
return columns;
}
@ -411,7 +420,7 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
} else {
this.pageLink.sortOrder = null;
}
this.dataSource.loadEntities(this.pageLink, this.includeSystemImages.value);
this.dataSource.loadEntities(this.pageLink, this.includeSystemImages);
} else {
this.gridComponent.update();
}
@ -599,6 +608,14 @@ export class ImageGalleryComponent extends PageComponent implements OnInit, OnDe
this.imageSelected.next(image);
}
rowClick($event, image: ImageResourceInfo) {
if (this.selectionMode) {
this.selectImage($event, image);
} else {
this.dataSource.selection.toggle(image);
}
}
uploadImage(): void {
this.dialog.open<UploadImageDialogComponent, UploadImageDialogData,
ImageResourceInfo>(UploadImageDialogComponent, {

View File

@ -20,15 +20,17 @@
</ng-container>
<ng-template #complexEntityReferences>
<ng-container *ngIf="contentReady; else loading">
<ul>
<ul class="tb-references">
<ng-container *ngFor="let entry of referencedEntitiesEntries">
<li *ngIf="isSystem(entry[0]); else tenantEntities">
{{ 'image.system-entities' | translate }}
<li class="tb-entities-container tb-primary-fill" *ngIf="isSystem(entry[0]); else tenantEntities">
<div class="tb-entities-title">{{ 'image.system-entities' | translate }}</div>
<ng-container *ngTemplateOutlet="referencesList; context:{entriesList: entry[1].entities, showDetailsLink: true}"></ng-container>
</li>
<ng-template #tenantEntities>
<li>
{{ 'tenant.tenant' | translate }} <a [routerLink]="entry[1].tenantDetailsUrl">{{ '"' + entry[1].tenantName + '"' }}</a> {{ 'image.entities' | translate }}
<li class="tb-entities-container tb-primary-fill">
<div class="tb-entities-title">
{{ 'tenant.tenant' | translate }} <a class="tb-reference" [routerLink]="entry[1].tenantDetailsUrl">{{ entry[1].tenantName }}</a> {{ 'image.entities' | translate }}
</div>
<ng-container *ngTemplateOutlet="referencesList; context:{entriesList: entry[1].entities, showDetailsLink: false}"></ng-container>
</li>
</ng-template>
@ -37,12 +39,15 @@
</ng-container>
</ng-template>
<ng-template #referencesList let-entriesList="entriesList" let-showDetailsLink="showDetailsLink">
<ul>
<li *ngFor="let referencedEntity of entriesList">
{{ referencedEntity.typeName }} <a *ngIf="showDetailsLink" [routerLink]="referencedEntity.detailsUrl">{{ '"' + referencedEntity.entity.name + '"' }}</a>
<b *ngIf="!showDetailsLink">{{ '"' + referencedEntity.entity.name + '"' }}</b>
</li>
</ul>
<table class="tb-entities-list-table">
<tr *ngFor="let referencedEntity of entriesList">
<td class="tb-entity-type">{{ referencedEntity.typeName }}</td>
<td class="tb-entity-name">
<a *ngIf="showDetailsLink" class="tb-reference" [routerLink]="referencedEntity.detailsUrl">{{ referencedEntity.entity.name }}</a>
<span *ngIf="!showDetailsLink">{{ referencedEntity.entity.name }}</span>
</td>
</tr>
</table>
</ng-template>
<ng-template #loading>
<mat-spinner [diameter]="32" strokeWidth="2"></mat-spinner>

View File

@ -0,0 +1,92 @@
/**
* Copyright © 2016-2023 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 {
ul.tb-references {
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 12px;
}
li.tb-entities-container {
padding: 8px 12px 12px 12px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
border-radius: 8px;
}
a.tb-reference {
color: $tb-primary-color;
font-weight: inherit;
}
.tb-entities-title {
display: flex;
gap: 8px;
color: rgba(0, 0, 0, 0.87);
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 24px;
letter-spacing: 0.25px;
}
table.tb-entities-list-table {
position: relative;
overflow: hidden;
padding: 8px 10px 8px 4px;
border-radius: 4px;
background: #fff;
align-self: stretch;
z-index: 1;
color: rgba(0, 0, 0, 0.76);
&:before {
display: block;
height: auto;
content: "";
position: absolute;
inset: 0;
border-radius: 4px;
border: 1px solid $tb-primary-color;
background: transparent;
opacity: 0.4;
pointer-events: none;
}
td.tb-entity-type {
white-space: nowrap;
padding-right: 20px;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px;
letter-spacing: 0.25px;
&:before {
content: "";
padding-left: 8px;
padding-right: 8px;
}
}
td.tb-entity-name {
width: 100%;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
letter-spacing: 0.2px;
}
}
}

View File

@ -49,7 +49,7 @@ type ReferencedEntitiesEntry = [string, TenantReferencedEntities];
@Component({
selector: 'tb-image-references',
templateUrl: './image-references.component.html',
styleUrls: []
styleUrls: ['./image-references.component.scss']
})
export class ImageReferencesComponent implements OnInit {

View File

@ -17,6 +17,7 @@
.tb-images-in-use-content {
display: flex;
flex-direction: column;
gap: 24px;
&.multiple {
gap: 16px;
}

View File

@ -523,6 +523,7 @@
right: 0;
background: $tb-primary-color;
opacity: 0.04;
pointer-events: none;
}
}