Commit 195ed51afa9ba44d2fc93ca2a680303aa96a9efe

Authored by Vladyslav_Prykhodko
1 parent 6b63d336

UI: Add copy button checksum

... ... @@ -39,7 +39,7 @@ export class OtaPackageService {
39 39 }
40 40
41 41 public getOtaPackagesInfoByDeviceProfileId(pageLink: PageLink, deviceProfileId: string, type: OtaUpdateType,
42   - hasData = true, config?: RequestConfig): Observable<PageData<OtaPackageInfo>> {
  42 + config?: RequestConfig): Observable<PageData<OtaPackageInfo>> {
43 43 const url = `/api/otaPackages/${deviceProfileId}/${type}${pageLink.toQuery()}`;
44 44 return this.http.get<PageData<OtaPackageInfo>>(url, defaultHttpOptionsFromConfig(config));
45 45 }
... ...
... ... @@ -163,8 +163,34 @@
163 163 *matCellDef="let entity; let row = index"
164 164 [matTooltip]="cellTooltip(entity, column, row)"
165 165 matTooltipPosition="above"
166   - [innerHTML]="cellContent(entity, column, row)"
167 166 [ngStyle]="cellStyle(entity, column, row)">
  167 + <span [innerHTML]="cellContent(entity, column, row)"></span>
  168 + <ng-template [ngIf]="column.actionCell.name">
  169 + <ng-container [ngSwitch]="column.actionCell.type">
  170 + <ng-template [ngSwitchCase]="cellActionType.COPY_BUTTON">
  171 + <tb-copy-button
  172 + [disabled]="isLoading$ | async"
  173 + [fxShow]="column.actionCell.isEnabled(entity)"
  174 + [copyText]="column.actionCell.onAction(null, entity)"
  175 + tooltipText="{{ column.actionCell.nameFunction ? column.actionCell.nameFunction(entity) : column.actionCell.name }}"
  176 + tooltipPosition="above"
  177 + [icon]="column.actionCell.icon"
  178 + [mdiIcon]="column.actionCell.mdiIcon" [style]="column.actionCell.style">
  179 + </tb-copy-button>
  180 + </ng-template>
  181 + <ng-template ngSwitchDefault>
  182 + <button mat-icon-button [disabled]="isLoading$ | async"
  183 + [fxShow]="column.actionCell.isEnabled(entity)"
  184 + matTooltip="{{ column.actionCell.nameFunction ? column.actionCell.nameFunction(entity) : column.actionCell.name }}"
  185 + matTooltipPosition="above"
  186 + (click)="column.actionCell.onAction($event, entity)">
  187 + <mat-icon [svgIcon]="column.actionCell.mdiIcon" [ngStyle]="column.actionCell.style">
  188 + {{column.actionCell.icon}}
  189 + </mat-icon>
  190 + </button>
  191 + </ng-template>
  192 + </ng-container>
  193 + </ng-template>
168 194 </mat-cell>
169 195 </ng-container>
170 196 <ng-container [matColumnDef]="column.key" *ngFor="let column of actionColumns; trackBy: trackByColumnKey;">
... ...
... ... @@ -43,7 +43,7 @@ import { TranslateService } from '@ngx-translate/core';
43 43 import { BaseData, HasId } from '@shared/models/base-data';
44 44 import { ActivatedRoute } from '@angular/router';
45 45 import {
46   - CellActionDescriptor,
  46 + CellActionDescriptor, CellActionDescriptorType,
47 47 EntityActionTableColumn,
48 48 EntityColumn,
49 49 EntityTableColumn,
... ... @@ -104,6 +104,8 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
104 104 timewindow: Timewindow;
105 105 dataSource: EntitiesDataSource<BaseData<HasId>>;
106 106
  107 + cellActionType = CellActionDescriptorType;
  108 +
107 109 isDetailsOpen = false;
108 110 detailsPanelOpened = new EventEmitter<boolean>();
109 111
... ...
... ... @@ -48,6 +48,9 @@ export type CellContentFunction<T extends BaseData<HasId>> = (entity: T, key: st
48 48 export type CellTooltipFunction<T extends BaseData<HasId>> = (entity: T, key: string) => string | undefined;
49 49 export type HeaderCellStyleFunction<T extends BaseData<HasId>> = (key: string) => object;
50 50 export type CellStyleFunction<T extends BaseData<HasId>> = (entity: T, key: string) => object;
  51 +export type CopyCellContent<T extends BaseData<HasId>> = (entity: T, key: string, length: number) => object;
  52 +
  53 +export enum CellActionDescriptorType { 'DEFAULT', 'COPY_BUTTON'}
51 54
52 55 export interface CellActionDescriptor<T extends BaseData<HasId>> {
53 56 name: string;
... ... @@ -56,7 +59,8 @@ export interface CellActionDescriptor<T extends BaseData<HasId>> {
56 59 mdiIcon?: string;
57 60 style?: any;
58 61 isEnabled: (entity: T) => boolean;
59   - onAction: ($event: MouseEvent, entity: T) => void;
  62 + onAction: ($event: MouseEvent, entity: T) => any;
  63 + type?: CellActionDescriptorType;
60 64 }
61 65
62 66 export interface GroupActionDescriptor<T extends BaseData<HasId>> {
... ... @@ -95,7 +99,8 @@ export class EntityTableColumn<T extends BaseData<HasId>> extends BaseEntityTabl
95 99 public sortable: boolean = true,
96 100 public headerCellStyleFunction: HeaderCellStyleFunction<T> = () => ({}),
97 101 public cellTooltipFunction: CellTooltipFunction<T> = () => undefined,
98   - public isNumberColumn: boolean = false) {
  102 + public isNumberColumn: boolean = false,
  103 + public actionCell: CellActionDescriptor<T> = {isEnabled: () => false, name: null, onAction: () => ({})}) {
99 104 super('content', key, title, width, sortable);
100 105 }
101 106 }
... ... @@ -187,3 +192,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
187 192 export function checkBoxCell(value: boolean): string {
188 193 return `<mat-icon class="material-icons mat-icon">${value ? 'check_box' : 'check_box_outline_blank'}</mat-icon>`;
189 194 }
  195 +
  196 +export function copyCellContent(value: string): string {
  197 + return `<mat-icon class="material-icons mat-icon">${value ? 'check_box' : 'check_box_outline_blank'}</mat-icon>`;
  198 +}
... ...
... ... @@ -17,6 +17,7 @@
17 17 import { Injectable } from '@angular/core';
18 18 import { Resolve } from '@angular/router';
19 19 import {
  20 + CellActionDescriptorType,
20 21 DateEntityTableColumn,
21 22 EntityTableColumn,
22 23 EntityTableConfig
... ... @@ -61,27 +62,46 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot
61 62
62 63 this.config.columns.push(
63 64 new DateEntityTableColumn<OtaPackageInfo>('createdTime', 'common.created-time', this.datePipe, '150px'),
64   - new EntityTableColumn<OtaPackageInfo>('title', 'ota-update.title', '25%'),
65   - new EntityTableColumn<OtaPackageInfo>('version', 'ota-update.version', '25%'),
66   - new EntityTableColumn<OtaPackageInfo>('type', 'ota-update.package-type', '25%', entity => {
  65 + new EntityTableColumn<OtaPackageInfo>('title', 'ota-update.title', '20%'),
  66 + new EntityTableColumn<OtaPackageInfo>('version', 'ota-update.version', '20%'),
  67 + new EntityTableColumn<OtaPackageInfo>('type', 'ota-update.package-type', '20%', entity => {
67 68 return this.translate.instant(OtaUpdateTypeTranslationMap.get(entity.type));
68 69 }),
69   - new EntityTableColumn<OtaPackageInfo>('fileName', 'ota-update.file-name', '25%'),
  70 + new EntityTableColumn<OtaPackageInfo>('url', 'ota-update.url', '20%', entity => {
  71 + return entity.url && entity.url.length > 20 ? `${entity.url.slice(0, 20)}…` : '';
  72 + }, () => ({}), true, () => ({}), () => undefined, false,
  73 + {
  74 + name: this.translate.instant('ota-update.copy-checksum'),
  75 + icon: 'content_paste',
  76 + style: {
  77 + 'font-size': '16px',
  78 + color: 'rgba(0,0,0,.87)'
  79 + },
  80 + isEnabled: (otaPackage) => !!otaPackage.url,
  81 + onAction: ($event, entity) => entity.url,
  82 + type: CellActionDescriptorType.COPY_BUTTON
  83 + }),
  84 + new EntityTableColumn<OtaPackageInfo>('fileName', 'ota-update.file-name', '20%'),
70 85 new EntityTableColumn<OtaPackageInfo>('dataSize', 'ota-update.file-size', '70px', entity => {
71 86 return entity.dataSize ? this.fileSize.transform(entity.dataSize) : '';
72 87 }),
73   - new EntityTableColumn<OtaPackageInfo>('checksum', 'ota-update.checksum', '540px', entity => {
74   - return entity.checksum ? `${ChecksumAlgorithmTranslationMap.get(entity.checksumAlgorithm)}: ${entity.checksum}` : '';
75   - }, () => ({}), false)
76   - );
77   -
78   - this.config.cellActionDescriptors.push(
  88 + new EntityTableColumn<OtaPackageInfo>('checksum', 'ota-update.checksum', '220px', entity => {
  89 + return entity.checksum ? this.checksumText(entity) : '';
  90 + }, () => ({}), true, () => ({}), () => undefined, false,
79 91 {
80 92 name: this.translate.instant('ota-update.copy-checksum'),
81   - icon: 'content_copy',
  93 + icon: 'content_paste',
  94 + style: {
  95 + 'font-size': '16px',
  96 + color: 'rgba(0,0,0,.87)'
  97 + },
82 98 isEnabled: (otaPackage) => !!otaPackage.checksum,
83   - onAction: ($event, entity) => this.copyPackageChecksum($event, entity)
84   - },
  99 + onAction: ($event, entity) => entity.checksum,
  100 + type: CellActionDescriptorType.COPY_BUTTON
  101 + })
  102 + );
  103 +
  104 + this.config.cellActionDescriptors.push(
85 105 {
86 106 name: this.translate.instant('ota-update.download'),
87 107 icon: 'file_download',
... ... @@ -120,19 +140,12 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot
120 140 }
121 141 }
122 142
123   - copyPackageChecksum($event: Event, otaPackageInfo: OtaPackageInfo) {
124   - if ($event) {
125   - $event.stopPropagation();
  143 + checksumText(entity): string {
  144 + let text = `${ChecksumAlgorithmTranslationMap.get(entity.checksumAlgorithm)}: ${entity.checksum}`;
  145 + if (text.length > 20) {
  146 + text = `${text.slice(0, 20)}…`;
126 147 }
127   - this.clipboardService.copy(otaPackageInfo?.checksum);
128   - this.store.dispatch(new ActionNotificationShow(
129   - {
130   - message: this.translate.instant('ota-update.checksum-copied-message'),
131   - type: 'success',
132   - duration: 750,
133   - verticalPosition: 'bottom',
134   - horizontalPosition: 'right'
135   - }));
  148 + return text;
136 149 }
137 150
138 151 onPackageAction(action: EntityAction<OtaPackageInfo>): boolean {
... ... @@ -140,9 +153,6 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot
140 153 case 'uploadPackage':
141 154 this.exportPackage(action.event, action.entity);
142 155 return true;
143   - case 'copyChecksum':
144   - this.copyPackageChecksum(action.event, action.entity);
145   - return true;
146 156 }
147 157 return false;
148 158 }
... ...
... ... @@ -31,7 +31,6 @@
31 31 <div fxLayout="row" fxLayout.xs="column">
32 32 <button mat-raised-button
33 33 ngxClipboard
34   - [disabled]="(isLoading$ | async)"
35 34 (cbOnSuccess)="onPackageIdCopied()"
36 35 [cbContent]="entity?.id?.id"
37 36 [fxShow]="!isEdit">
... ... @@ -39,9 +38,10 @@
39 38 <span translate>ota-update.copyId</span>
40 39 </button>
41 40 <button mat-raised-button
42   - [disabled]="(isLoading$ | async)"
43   - (click)="onEntityAction($event, 'copyChecksum')"
44   - [fxShow]="!isEdit && entity?.checksum">
  41 + ngxClipboard
  42 + (cbOnSuccess)="onPackageChecksumCopied()"
  43 + [cbContent]="entity?.checksum"
  44 + [fxShow]="!isEdit">
45 45 <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
46 46 <span translate>ota-update.copy-checksum</span>
47 47 </button>
... ...
... ... @@ -149,6 +149,17 @@ export class OtaUpdateComponent extends EntityComponent<OtaPackage> implements O
149 149 }));
150 150 }
151 151
  152 + onPackageChecksumCopied() {
  153 + this.store.dispatch(new ActionNotificationShow(
  154 + {
  155 + message: this.translate.instant('ota-update.checksum-copied-message'),
  156 + type: 'success',
  157 + duration: 750,
  158 + verticalPosition: 'bottom',
  159 + horizontalPosition: 'right'
  160 + }));
  161 + }
  162 +
152 163 prepareFormValue(formValue: any): any {
153 164 delete formValue.resource;
154 165 delete formValue.generateChecksum;
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2021 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<button mat-icon-button
  19 + #tooltip
  20 + [disabled]="disabled"
  21 + [matTooltip]="matTooltipText"
  22 + [matTooltipPosition]="matTooltipPosition"
  23 + (mouseenter)="copied ? $event.stopImmediatePropagation():''"
  24 + (mouseleave)="copied ? $event.stopImmediatePropagation():''"
  25 + (click)="copy($event)">
  26 + <mat-icon [svgIcon]="mdiIconSymbol" [ngStyle]="style" [ngClass]="{'copied': copied}">
  27 + {{ iconSymbol }}
  28 + </mat-icon>
  29 +</button>
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + .mat-icon-button{
  18 + height: 32px;
  19 + width: 32px;
  20 + line-height: 32px;
  21 + .mat-icon.copied{
  22 + color: #00C851!important;
  23 + }
  24 + }
  25 + &:hover{
  26 + .mat-icon{
  27 + color: #28567E !important;
  28 + }
  29 + }
  30 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2021 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { ChangeDetectorRef, Component, Input, ViewChild } from '@angular/core';
  18 +import { ClipboardService } from 'ngx-clipboard';
  19 +import { MatTooltip, TooltipPosition } from '@angular/material/tooltip/tooltip';
  20 +import { TranslateService } from '@ngx-translate/core';
  21 +
  22 +@Component({
  23 + selector: 'tb-copy-button',
  24 + styleUrls: ['copy-button.component.scss'],
  25 + templateUrl: './copy-button.component.html'
  26 +})
  27 +export class CopyButtonComponent {
  28 +
  29 + private copedIcon = '';
  30 + private timer;
  31 +
  32 + copied = false;
  33 +
  34 + @Input()
  35 + copyText: string;
  36 +
  37 + @Input()
  38 + disabled = false;
  39 +
  40 + @Input()
  41 + mdiIcon: string;
  42 +
  43 + @Input()
  44 + icon: string;
  45 +
  46 + @Input()
  47 + tooltipText: string;
  48 +
  49 + @Input()
  50 + tooltipPosition: TooltipPosition;
  51 +
  52 + @Input()
  53 + style: {[key: string]: any} = {};
  54 +
  55 + constructor(private clipboardService: ClipboardService,
  56 + private translate: TranslateService,
  57 + private cd: ChangeDetectorRef) {
  58 + }
  59 +
  60 + @ViewChild('tooltip', {static: true}) tooltip: MatTooltip;
  61 +
  62 + copy($event: Event): void {
  63 + $event.stopPropagation();
  64 + $event.preventDefault();
  65 + if (this.timer) {
  66 + clearTimeout(this.timer);
  67 + }
  68 + this.clipboardService.copy(this.copyText);
  69 + this.copedIcon = 'done';
  70 + this.copied = true;
  71 + this.tooltip.show();
  72 + this.timer = setTimeout(() => {
  73 + this.copedIcon = null;
  74 + this.copied = false;
  75 + this.tooltip.hide();
  76 + this.cd.detectChanges();
  77 + }, 1500);
  78 + }
  79 +
  80 + get iconSymbol(): string {
  81 + return this.copedIcon || this.icon;
  82 + }
  83 +
  84 + get mdiIconSymbol(): string {
  85 + return this.copedIcon ? '' : this.mdiIcon;
  86 + }
  87 +
  88 + get matTooltipText(): string {
  89 + return this.copied ? this.translate.instant('ota-update.copied') : this.tooltipText;
  90 + }
  91 +
  92 + get matTooltipPosition(): TooltipPosition {
  93 + return this.copied ? 'below' : this.tooltipPosition;
  94 + }
  95 +}
... ...
... ... @@ -216,7 +216,7 @@ export class OtaPackageAutocompleteComponent implements ControlValueAccessor, On
216 216 direction: Direction.ASC
217 217 });
218 218 return this.otaPackageService.getOtaPackagesInfoByDeviceProfileId(pageLink, this.deviceProfileId, this.type,
219   - true, {ignoreLoading: true}).pipe(
  219 + {ignoreLoading: true}).pipe(
220 220 map((data) => data && data.data.length ? data.data : null)
221 221 );
222 222 }
... ...
... ... @@ -143,6 +143,7 @@ import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe';
143 143 import { QuickTimeIntervalComponent } from '@shared/components/time/quick-time-interval.component';
144 144 import { OtaPackageAutocompleteComponent } from '@shared/components/ota-package/ota-package-autocomplete.component';
145 145 import { MAT_DATE_LOCALE } from '@angular/material/core';
  146 +import { CopyButtonComponent } from '@shared/components/button/copy-button.component';
146 147
147 148 @NgModule({
148 149 providers: [
... ... @@ -240,7 +241,8 @@ import { MAT_DATE_LOCALE } from '@angular/material/core';
240 241 EntityGatewaySelectComponent,
241 242 ContactComponent,
242 243 OtaPackageAutocompleteComponent,
243   - WidgetsBundleSearchComponent
  244 + WidgetsBundleSearchComponent,
  245 + CopyButtonComponent
244 246 ],
245 247 imports: [
246 248 CommonModule,
... ... @@ -412,7 +414,8 @@ import { MAT_DATE_LOCALE } from '@angular/material/core';
412 414 EntityGatewaySelectComponent,
413 415 ContactComponent,
414 416 OtaPackageAutocompleteComponent,
415   - WidgetsBundleSearchComponent
  417 + WidgetsBundleSearchComponent,
  418 + CopyButtonComponent
416 419 ]
417 420 })
418 421 export class SharedModule { }
... ...
... ... @@ -2166,6 +2166,7 @@
2166 2166 "content-type": "Content type",
2167 2167 "copy-checksum": "Copy checksum",
2168 2168 "copyId": "Copy package Id",
  2169 + "copied": "Copied!",
2169 2170 "delete": "Delete package",
2170 2171 "delete-ota-update-text": "Be careful, after the confirmation the OTA update will become unrecoverable.",
2171 2172 "delete-ota-update-title": "Are you sure you want to delete the OTA update '{{title}}'?",
... ...