15 changed files with 468 additions and 40 deletions
@ -0,0 +1,94 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<form [formGroup]="imageFormGroup" (ngSubmit)="save()" style="width: 560px;"> |
|||
<mat-toolbar color="primary"> |
|||
<h2>{{ (readonly ? 'image.image-details' : 'image.edit-image') | translate }}</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="cancel()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<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 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> |
|||
<button *ngIf="!readonly" |
|||
type="button" |
|||
mat-stroked-button |
|||
color="primary" |
|||
[disabled]="isLoading$ | async" |
|||
(click)="updateImage($event)"> |
|||
{{ 'image.update-image' | translate }} |
|||
</button> |
|||
</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> |
|||
@ -0,0 +1,83 @@ |
|||
/** |
|||
* 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. |
|||
*/ |
|||
|
|||
:host { |
|||
.tb-image-container { |
|||
width: fit-content; |
|||
border-radius: 4px; |
|||
border: 1px solid rgba(0, 0, 0, 0.05); |
|||
padding: 12px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: flex-start; |
|||
gap: 12px; |
|||
|
|||
.tb-image-content { |
|||
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%; |
|||
height: 100%; |
|||
border: 1px solid rgba(0, 0, 0, 0.12); |
|||
border-radius: 4px; |
|||
background: #fff; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
z-index: 1; |
|||
.tb-image-preview-spacer { |
|||
margin-top: 100%; |
|||
} |
|||
.tb-image-preview { |
|||
position: absolute; |
|||
inset: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
object-fit: contain; |
|||
} |
|||
} |
|||
.tb-image-preview-details { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8px; |
|||
color: rgba(0, 0, 0, 0.38); |
|||
font-size: 14px; |
|||
font-style: normal; |
|||
font-weight: 400; |
|||
line-height: 20px; |
|||
.mat-divider.mat-divider-vertical { |
|||
height: 20px; |
|||
} |
|||
} |
|||
.tb-image-actions { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8px; |
|||
color: rgba(0,0,0,0.54); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,147 @@ |
|||
///
|
|||
/// 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 { ChangeDetectorRef, 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 { 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 '@home/components/image/upload-image-dialog.component'; |
|||
import { UrlHolder } from '@shared/pipe/image.pipe'; |
|||
|
|||
export interface ImageDialogData { |
|||
readonly: boolean; |
|||
image: ImageResourceInfo; |
|||
} |
|||
|
|||
@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 { |
|||
|
|||
image: ImageResourceInfo; |
|||
|
|||
readonly: boolean; |
|||
|
|||
imageFormGroup: UntypedFormGroup; |
|||
|
|||
submitted = false; |
|||
|
|||
imageChanged = false; |
|||
|
|||
imagePreviewData: UrlHolder; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected router: Router, |
|||
private imageService: ImageService, |
|||
private dialog: MatDialog, |
|||
@Inject(MAT_DIALOG_DATA) private data: ImageDialogData, |
|||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
|||
public dialogRef: MatDialogRef<ImageDialogComponent, boolean>, |
|||
public fb: UntypedFormBuilder) { |
|||
super(store, router, dialogRef); |
|||
this.image = data.image; |
|||
this.readonly = data.readonly; |
|||
this.imagePreviewData = { |
|||
url: this.image.link |
|||
}; |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.imageFormGroup = this.fb.group({ |
|||
title: [this.image.title, [Validators.required]] |
|||
}); |
|||
if (this.data.readonly) { |
|||
this.imageFormGroup.disable(); |
|||
} |
|||
} |
|||
|
|||
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); |
|||
} |
|||
|
|||
downloadImage($event) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
this.imageService.downloadImage(imageResourceType(this.image), this.image.resourceKey).subscribe(); |
|||
} |
|||
|
|||
exportImage($event) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
// TODO:
|
|||
} |
|||
|
|||
updateImage($event): void { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
this.dialog.open<UploadImageDialogComponent, UploadImageDialogData, |
|||
ImageResourceInfo>(UploadImageDialogComponent, { |
|||
disableClose: true, |
|||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
|||
data: { |
|||
image: this.image |
|||
} |
|||
}).afterClosed().subscribe((result) => { |
|||
if (result) { |
|||
this.imageChanged = true; |
|||
this.image = result; |
|||
this.imagePreviewData = { |
|||
url: this.image.link |
|||
}; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
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); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue