Commit 064778951a76cf718ad8478d17413ccba166db41
1 parent
c39fb8c9
UI: Ability to update dashboard image from screenshot
Showing
12 changed files
with
330 additions
and
4 deletions
@@ -47,6 +47,7 @@ | @@ -47,6 +47,7 @@ | ||
47 | "flot": "git://github.com/thingsboard/flot.git#0.9-work", | 47 | "flot": "git://github.com/thingsboard/flot.git#0.9-work", |
48 | "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", | 48 | "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", |
49 | "font-awesome": "^4.7.0", | 49 | "font-awesome": "^4.7.0", |
50 | + "html2canvas": "^1.0.0-rc.7", | ||
50 | "jquery": "^3.5.1", | 51 | "jquery": "^3.5.1", |
51 | "jquery.terminal": "^2.18.3", | 52 | "jquery.terminal": "^2.18.3", |
52 | "js-beautify": "^1.13.0", | 53 | "js-beautify": "^1.13.0", |
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html
0 → 100644
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 | +<form (ngSubmit)="save()"> | ||
19 | + <mat-toolbar color="primary"> | ||
20 | + <h2 translate>dashboard.update-image</h2> | ||
21 | + <span fxFlex></span> | ||
22 | + <button mat-button mat-icon-button | ||
23 | + (click)="cancel()" | ||
24 | + type="button"> | ||
25 | + <mat-icon class="material-icons">close</mat-icon> | ||
26 | + </button> | ||
27 | + </mat-toolbar> | ||
28 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | ||
29 | + </mat-progress-bar> | ||
30 | + <div mat-dialog-content style="position: relative;"> | ||
31 | + <fieldset [disabled]="(isLoading$ | async) || (takingScreenshot$ | async)" fxLayout="column" fxLayoutAlign="center center" fxLayoutGap="16px"> | ||
32 | + <div class="tb-image-preview-container"> | ||
33 | + <div *ngIf="!safeImageUrl; else elseBlock">{{ 'dashboard.no-image' | translate }}</div> | ||
34 | + <ng-template #elseBlock><img class="tb-image-preview" [src]="safeImageUrl" /></ng-template> | ||
35 | + </div> | ||
36 | + <button mat-raised-button color="accent" | ||
37 | + type="button" | ||
38 | + [disabled]="(isLoading$ | async) || (takingScreenshot$ | async)" | ||
39 | + (click)="takeScreenShot()"> | ||
40 | + {{ 'dashboard.take-screenshot' | translate }} | ||
41 | + </button> | ||
42 | + <div [formGroup]="dashboardImageFormGroup"> | ||
43 | + <tb-image-input [showPreview]="false" label="{{'dashboard.image' | translate}}" | ||
44 | + formControlName="dashboardImage"> | ||
45 | + </tb-image-input> | ||
46 | + </div> | ||
47 | + </fieldset> | ||
48 | + <div *ngIf="takingScreenshot$ | async" class="taking-screenshot-progress tb-absolute-fill" fxLayout="column" | ||
49 | + fxLayoutAlign="center center"> | ||
50 | + <mat-progress-spinner color="accent" mode="indeterminate"></mat-progress-spinner> | ||
51 | + </div> | ||
52 | + </div> | ||
53 | + <div mat-dialog-actions fxLayoutAlign="end center"> | ||
54 | + <button mat-button color="primary" | ||
55 | + type="button" | ||
56 | + [disabled]="(isLoading$ | async) || (takingScreenshot$ | async)" | ||
57 | + (click)="cancel()" cdkFocusInitial> | ||
58 | + {{ 'action.cancel' | translate }} | ||
59 | + </button> | ||
60 | + <button mat-raised-button color="primary" | ||
61 | + type="submit" | ||
62 | + [disabled]="(isLoading$ | async) || (takingScreenshot$ | async) || !dashboardImageFormGroup.dirty"> | ||
63 | + {{ 'action.update' | translate }} | ||
64 | + </button> | ||
65 | + </div> | ||
66 | +</form> |
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.scss
0 → 100644
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 | +$previewSize: 300px !default; | ||
18 | + | ||
19 | +:host { | ||
20 | + .tb-image-preview { | ||
21 | + width: auto; | ||
22 | + max-width: $previewSize - 2; | ||
23 | + height: auto; | ||
24 | + max-height: $previewSize - 2; | ||
25 | + } | ||
26 | + | ||
27 | + .tb-image-preview-container { | ||
28 | + position: relative; | ||
29 | + float: left; | ||
30 | + width: $previewSize; | ||
31 | + height: $previewSize; | ||
32 | + margin-right: 12px; | ||
33 | + vertical-align: top; | ||
34 | + border: solid 1px; | ||
35 | + | ||
36 | + div { | ||
37 | + width: 100%; | ||
38 | + font-size: 18px; | ||
39 | + text-align: center; | ||
40 | + } | ||
41 | + | ||
42 | + div, | ||
43 | + .tb-image-preview { | ||
44 | + position: absolute; | ||
45 | + top: 50%; | ||
46 | + left: 50%; | ||
47 | + transform: translate(-50%, -50%); | ||
48 | + } | ||
49 | + } | ||
50 | + | ||
51 | + .taking-screenshot-progress { | ||
52 | + background-color: rgba(255, 255, 255, 0.65); | ||
53 | + } | ||
54 | +} |
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.ts
0 → 100644
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 { Component, Inject } from '@angular/core'; | ||
18 | +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; | ||
19 | +import { Store } from '@ngrx/store'; | ||
20 | +import { AppState } from '@core/core.state'; | ||
21 | +import { FormBuilder, FormGroup } from '@angular/forms'; | ||
22 | +import { Router } from '@angular/router'; | ||
23 | +import { DialogComponent } from '@app/shared/components/dialog.component'; | ||
24 | +import { DashboardId } from '@shared/models/id/dashboard-id'; | ||
25 | +import { DashboardService } from '@core/http/dashboard.service'; | ||
26 | +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; | ||
27 | +import html2canvas from 'html2canvas'; | ||
28 | +import { map, share } from 'rxjs/operators'; | ||
29 | +import { BehaviorSubject, from } from 'rxjs'; | ||
30 | + | ||
31 | +export interface DashboardImageDialogData { | ||
32 | + dashboardId: DashboardId; | ||
33 | + currentImage?: string; | ||
34 | + dashboardElement: HTMLElement; | ||
35 | +} | ||
36 | + | ||
37 | +export interface DashboardImageDialogResult { | ||
38 | + image?: string; | ||
39 | +} | ||
40 | + | ||
41 | +@Component({ | ||
42 | + selector: 'tb-dashboard-image-dialog', | ||
43 | + templateUrl: './dashboard-image-dialog.component.html', | ||
44 | + styleUrls: ['./dashboard-image-dialog.component.scss'] | ||
45 | +}) | ||
46 | +export class DashboardImageDialogComponent extends DialogComponent<DashboardImageDialogComponent, DashboardImageDialogResult> { | ||
47 | + | ||
48 | + takingScreenshotSubject = new BehaviorSubject(false); | ||
49 | + | ||
50 | + takingScreenshot$ = this.takingScreenshotSubject.asObservable().pipe( | ||
51 | + share() | ||
52 | + ); | ||
53 | + | ||
54 | + dashboardId: DashboardId; | ||
55 | + safeImageUrl?: SafeUrl; | ||
56 | + dashboardElement: HTMLElement; | ||
57 | + | ||
58 | + dashboardImageFormGroup: FormGroup; | ||
59 | + | ||
60 | + constructor(protected store: Store<AppState>, | ||
61 | + protected router: Router, | ||
62 | + @Inject(MAT_DIALOG_DATA) public data: DashboardImageDialogData, | ||
63 | + public dialogRef: MatDialogRef<DashboardImageDialogComponent, DashboardImageDialogResult>, | ||
64 | + private dashboardService: DashboardService, | ||
65 | + private sanitizer: DomSanitizer, | ||
66 | + private fb: FormBuilder) { | ||
67 | + super(store, router, dialogRef); | ||
68 | + | ||
69 | + this.dashboardId = this.data.dashboardId; | ||
70 | + this.updateImage(this.data.currentImage); | ||
71 | + this.dashboardElement = this.data.dashboardElement; | ||
72 | + | ||
73 | + this.dashboardImageFormGroup = this.fb.group({ | ||
74 | + dashboardImage: [this.data.currentImage] | ||
75 | + }); | ||
76 | + | ||
77 | + this.dashboardImageFormGroup.get('dashboardImage').valueChanges.subscribe( | ||
78 | + (newImage) => { | ||
79 | + this.updateImage(newImage); | ||
80 | + } | ||
81 | + ); | ||
82 | + } | ||
83 | + | ||
84 | + takeScreenShot() { | ||
85 | + this.takingScreenshotSubject.next(true); | ||
86 | + from(html2canvas(this.dashboardElement, { | ||
87 | + logging: false, | ||
88 | + useCORS: true, | ||
89 | + foreignObjectRendering: false, | ||
90 | + scale: 512 / this.dashboardElement.clientWidth | ||
91 | + })).pipe( | ||
92 | + map(canvas => canvas.toDataURL())).subscribe( | ||
93 | + (image) => { | ||
94 | + this.updateImage(image); | ||
95 | + this.dashboardImageFormGroup.patchValue({dashboardImage: image}, {emitEvent: false}); | ||
96 | + this.dashboardImageFormGroup.markAsDirty(); | ||
97 | + this.takingScreenshotSubject.next(false); | ||
98 | + }, | ||
99 | + (e) => { | ||
100 | + this.takingScreenshotSubject.next(false); | ||
101 | + } | ||
102 | + ); | ||
103 | + } | ||
104 | + | ||
105 | + cancel(): void { | ||
106 | + this.dialogRef.close(null); | ||
107 | + } | ||
108 | + | ||
109 | + save(): void { | ||
110 | + this.dashboardService.getDashboard(this.dashboardId.id).subscribe( | ||
111 | + (dashboard) => { | ||
112 | + const newImage: string = this.dashboardImageFormGroup.get('dashboardImage').value; | ||
113 | + dashboard.image = newImage; | ||
114 | + this.dashboardService.saveDashboard(dashboard).subscribe( | ||
115 | + () => { | ||
116 | + this.dialogRef.close({ | ||
117 | + image: newImage | ||
118 | + }); | ||
119 | + } | ||
120 | + ); | ||
121 | + } | ||
122 | + ); | ||
123 | + } | ||
124 | + | ||
125 | + private updateImage(imageUrl: string) { | ||
126 | + if (imageUrl) { | ||
127 | + this.safeImageUrl = this.sanitizer.bypassSecurityTrustUrl(imageUrl); | ||
128 | + } else { | ||
129 | + this.safeImageUrl = null; | ||
130 | + } | ||
131 | + } | ||
132 | +} |
@@ -81,6 +81,12 @@ | @@ -81,6 +81,12 @@ | ||
81 | (click)="isFullscreen = !isFullscreen"> | 81 | (click)="isFullscreen = !isFullscreen"> |
82 | <mat-icon>{{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> | 82 | <mat-icon>{{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
83 | </button> | 83 | </button> |
84 | + <button [fxShow]="currentDashboardId && !isEdit && isTenantAdmin() && displayUpdateDashboardImage()" mat-icon-button | ||
85 | + matTooltip="{{'dashboard.update-image' | translate}}" | ||
86 | + matTooltipPosition="below" | ||
87 | + (click)="updateDashboardImage($event)"> | ||
88 | + <mat-icon>wallpaper</mat-icon> | ||
89 | + </button> | ||
84 | <button [fxShow]="currentDashboardId && (isEdit || displayExport())" mat-icon-button | 90 | <button [fxShow]="currentDashboardId && (isEdit || displayExport())" mat-icon-button |
85 | matTooltip="{{'dashboard.export' | translate}}" | 91 | matTooltip="{{'dashboard.export' | translate}}" |
86 | matTooltipPosition="below" | 92 | matTooltipPosition="below" |
@@ -133,6 +139,7 @@ | @@ -133,6 +139,7 @@ | ||
133 | </tb-dashboard-toolbar> | 139 | </tb-dashboard-toolbar> |
134 | </section> | 140 | </section> |
135 | <section class="tb-dashboard-container tb-absolute-fill" | 141 | <section class="tb-dashboard-container tb-absolute-fill" |
142 | + #dashboardContainer | ||
136 | [ngClass]="{ 'is-fullscreen': forceFullscreen, | 143 | [ngClass]="{ 'is-fullscreen': forceFullscreen, |
137 | 'tb-dashboard-toolbar-opened': toolbarOpened, | 144 | 'tb-dashboard-toolbar-opened': toolbarOpened, |
138 | 'tb-dashboard-toolbar-animated': isToolbarOpenedAnimate, | 145 | 'tb-dashboard-toolbar-animated': isToolbarOpenedAnimate, |
@@ -187,7 +194,7 @@ | @@ -187,7 +194,7 @@ | ||
187 | </tb-dashboard-layout> | 194 | </tb-dashboard-layout> |
188 | </mat-drawer-content> | 195 | </mat-drawer-content> |
189 | </mat-drawer-container> | 196 | </mat-drawer-container> |
190 | - <section fxLayout="row" class="layout-wrap tb-footer-buttons" fxLayoutAlign="start end"> | 197 | + <section data-html2canvas-ignore fxLayout="row" class="layout-wrap tb-footer-buttons" fxLayoutAlign="start end"> |
191 | <tb-footer-fab-buttons *ngIf="!embedded && !isMobileApp" | 198 | <tb-footer-fab-buttons *ngIf="!embedded && !isMobileApp" |
192 | [fxShow]="!isAddingWidget && isEdit && !widgetEditMode" | 199 | [fxShow]="!isAddingWidget && isEdit && !widgetEditMode" |
193 | relative | 200 | relative |
@@ -212,7 +219,7 @@ | @@ -212,7 +219,7 @@ | ||
212 | <mat-icon>{{ isEdit ? 'close' : 'edit' }}</mat-icon> | 219 | <mat-icon>{{ isEdit ? 'close' : 'edit' }}</mat-icon> |
213 | </button> | 220 | </button> |
214 | </section> | 221 | </section> |
215 | - <section class="tb-powered-by-footer" [ngStyle]="{'color': dashboard.configuration.settings.titleColor}"> | 222 | + <section data-html2canvas-ignore class="tb-powered-by-footer" [ngStyle]="{'color': dashboard.configuration.settings.titleColor}"> |
216 | <span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard v.{{ thingsboardVersion }}</a></span> | 223 | <span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard v.{{ thingsboardVersion }}</a></span> |
217 | </section> | 224 | </section> |
218 | </mat-drawer-content> | 225 | </mat-drawer-content> |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | import { | 17 | import { |
18 | ChangeDetectorRef, | 18 | ChangeDetectorRef, |
19 | - Component, | 19 | + Component, ElementRef, |
20 | Inject, | 20 | Inject, |
21 | Injector, | 21 | Injector, |
22 | Input, | 22 | Input, |
@@ -123,6 +123,11 @@ import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/ | @@ -123,6 +123,11 @@ import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/ | ||
123 | import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; | 123 | import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; |
124 | import { MobileService } from '@core/services/mobile.service'; | 124 | import { MobileService } from '@core/services/mobile.service'; |
125 | 125 | ||
126 | +import { | ||
127 | + DashboardImageDialogComponent, | ||
128 | + DashboardImageDialogData, DashboardImageDialogResult | ||
129 | +} from '@home/components/dashboard-page/dashboard-image-dialog.component'; | ||
130 | + | ||
126 | // @dynamic | 131 | // @dynamic |
127 | @Component({ | 132 | @Component({ |
128 | selector: 'tb-dashboard-page', | 133 | selector: 'tb-dashboard-page', |
@@ -153,6 +158,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | @@ -153,6 +158,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | ||
153 | dashboard: Dashboard; | 158 | dashboard: Dashboard; |
154 | dashboardConfiguration: DashboardConfiguration; | 159 | dashboardConfiguration: DashboardConfiguration; |
155 | 160 | ||
161 | + @ViewChild('dashboardContainer') dashboardContainer: ElementRef<HTMLElement>; | ||
162 | + | ||
156 | prevDashboard: Dashboard; | 163 | prevDashboard: Dashboard; |
157 | 164 | ||
158 | iframeMode = this.utils.iframeMode; | 165 | iframeMode = this.utils.iframeMode; |
@@ -468,6 +475,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | @@ -468,6 +475,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | ||
468 | } | 475 | } |
469 | } | 476 | } |
470 | 477 | ||
478 | + public displayUpdateDashboardImage(): boolean { | ||
479 | + if (this.dashboard.configuration.settings && | ||
480 | + isDefined(this.dashboard.configuration.settings.showUpdateDashboardImage)) { | ||
481 | + return this.dashboard.configuration.settings.showUpdateDashboardImage; | ||
482 | + } else { | ||
483 | + return true; | ||
484 | + } | ||
485 | + } | ||
486 | + | ||
471 | public displayDashboardTimewindow(): boolean { | 487 | public displayDashboardTimewindow(): boolean { |
472 | if (this.dashboard.configuration.settings && | 488 | if (this.dashboard.configuration.settings && |
473 | isDefined(this.dashboard.configuration.settings.showDashboardTimewindow)) { | 489 | isDefined(this.dashboard.configuration.settings.showDashboardTimewindow)) { |
@@ -1248,4 +1264,24 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | @@ -1248,4 +1264,24 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC | ||
1248 | onCloseSearchBundle() { | 1264 | onCloseSearchBundle() { |
1249 | this.searchBundle = ''; | 1265 | this.searchBundle = ''; |
1250 | } | 1266 | } |
1267 | + | ||
1268 | + public updateDashboardImage($event: Event) { | ||
1269 | + if ($event) { | ||
1270 | + $event.stopPropagation(); | ||
1271 | + } | ||
1272 | + this.dialog.open<DashboardImageDialogComponent, DashboardImageDialogData, | ||
1273 | + DashboardImageDialogResult>(DashboardImageDialogComponent, { | ||
1274 | + disableClose: true, | ||
1275 | + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], | ||
1276 | + data: { | ||
1277 | + dashboardId: this.dashboard.id, | ||
1278 | + currentImage: this.dashboard.image, | ||
1279 | + dashboardElement: this.dashboardContainer.nativeElement | ||
1280 | + } | ||
1281 | + }).afterClosed().subscribe((result) => { | ||
1282 | + if (result) { | ||
1283 | + this.dashboard.image = result.image; | ||
1284 | + } | ||
1285 | + }); | ||
1286 | + } | ||
1251 | } | 1287 | } |
@@ -69,6 +69,9 @@ | @@ -69,6 +69,9 @@ | ||
69 | <mat-checkbox fxFlex formControlName="showDashboardExport"> | 69 | <mat-checkbox fxFlex formControlName="showDashboardExport"> |
70 | {{ 'dashboard.display-dashboard-export' | translate }} | 70 | {{ 'dashboard.display-dashboard-export' | translate }} |
71 | </mat-checkbox> | 71 | </mat-checkbox> |
72 | + <mat-checkbox fxFlex formControlName="showUpdateDashboardImage"> | ||
73 | + {{ 'dashboard.display-update-dashboard-image' | translate }} | ||
74 | + </mat-checkbox> | ||
72 | </div> | 75 | </div> |
73 | <mat-checkbox formControlName="showDashboardLogo"> | 76 | <mat-checkbox formControlName="showDashboardLogo"> |
74 | {{ 'dashboard.display-dashboard-logo' | translate }} | 77 | {{ 'dashboard.display-dashboard-logo' | translate }} |
@@ -82,7 +82,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS | @@ -82,7 +82,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS | ||
82 | showDashboardLogo: [isUndefined(this.settings.showDashboardLogo) ? false : this.settings.showDashboardLogo, []], | 82 | showDashboardLogo: [isUndefined(this.settings.showDashboardLogo) ? false : this.settings.showDashboardLogo, []], |
83 | dashboardLogoUrl: [isUndefined(this.settings.dashboardLogoUrl) ? null : this.settings.dashboardLogoUrl, []], | 83 | dashboardLogoUrl: [isUndefined(this.settings.dashboardLogoUrl) ? null : this.settings.dashboardLogoUrl, []], |
84 | showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []], | 84 | showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []], |
85 | - showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []] | 85 | + showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []], |
86 | + showUpdateDashboardImage: [isUndefined(this.settings.showUpdateDashboardImage) ? true : this.settings.showUpdateDashboardImage, []] | ||
86 | }); | 87 | }); |
87 | this.settingsFormGroup.get('stateControllerId').valueChanges.subscribe( | 88 | this.settingsFormGroup.get('stateControllerId').valueChanges.subscribe( |
88 | (stateControllerId: StateControllerId) => { | 89 | (stateControllerId: StateControllerId) => { |
@@ -141,6 +141,7 @@ import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-dow | @@ -141,6 +141,7 @@ import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-dow | ||
141 | import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component'; | 141 | import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component'; |
142 | import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component'; | 142 | import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component'; |
143 | import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component'; | 143 | import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component'; |
144 | +import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component'; | ||
144 | 145 | ||
145 | @NgModule({ | 146 | @NgModule({ |
146 | declarations: | 147 | declarations: |
@@ -260,6 +261,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu | @@ -260,6 +261,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu | ||
260 | DashboardSettingsDialogComponent, | 261 | DashboardSettingsDialogComponent, |
261 | ManageDashboardStatesDialogComponent, | 262 | ManageDashboardStatesDialogComponent, |
262 | DashboardStateDialogComponent, | 263 | DashboardStateDialogComponent, |
264 | + DashboardImageDialogComponent, | ||
263 | EmbedDashboardDialogComponent, | 265 | EmbedDashboardDialogComponent, |
264 | DisplayWidgetTypesPanelComponent | 266 | DisplayWidgetTypesPanelComponent |
265 | ], | 267 | ], |
@@ -370,6 +372,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu | @@ -370,6 +372,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu | ||
370 | DashboardSettingsDialogComponent, | 372 | DashboardSettingsDialogComponent, |
371 | ManageDashboardStatesDialogComponent, | 373 | ManageDashboardStatesDialogComponent, |
372 | DashboardStateDialogComponent, | 374 | DashboardStateDialogComponent, |
375 | + DashboardImageDialogComponent, | ||
373 | EmbedDashboardDialogComponent, | 376 | EmbedDashboardDialogComponent, |
374 | DisplayWidgetTypesPanelComponent | 377 | DisplayWidgetTypesPanelComponent |
375 | ], | 378 | ], |
@@ -91,6 +91,7 @@ export interface DashboardSettings { | @@ -91,6 +91,7 @@ export interface DashboardSettings { | ||
91 | dashboardLogoUrl?: string; | 91 | dashboardLogoUrl?: string; |
92 | showDashboardTimewindow?: boolean; | 92 | showDashboardTimewindow?: boolean; |
93 | showDashboardExport?: boolean; | 93 | showDashboardExport?: boolean; |
94 | + showUpdateDashboardImage?: boolean; | ||
94 | toolbarAlwaysOpen?: boolean; | 95 | toolbarAlwaysOpen?: boolean; |
95 | titleColor?: string; | 96 | titleColor?: string; |
96 | } | 97 | } |
@@ -667,6 +667,8 @@ | @@ -667,6 +667,8 @@ | ||
667 | "add-widget": "Add new widget", | 667 | "add-widget": "Add new widget", |
668 | "title": "Title", | 668 | "title": "Title", |
669 | "image": "Dashboard image", | 669 | "image": "Dashboard image", |
670 | + "update-image": "Update dashboard image", | ||
671 | + "take-screenshot": "Take screenshot", | ||
670 | "select-widget-title": "Select widget", | 672 | "select-widget-title": "Select widget", |
671 | "select-widget-value": "{{title}}: select widget", | 673 | "select-widget-value": "{{title}}: select widget", |
672 | "select-widget-subtitle": "List of available widget types", | 674 | "select-widget-subtitle": "List of available widget types", |
@@ -748,6 +750,7 @@ | @@ -748,6 +750,7 @@ | ||
748 | "display-filters": "Display filters", | 750 | "display-filters": "Display filters", |
749 | "display-dashboard-timewindow": "Display timewindow", | 751 | "display-dashboard-timewindow": "Display timewindow", |
750 | "display-dashboard-export": "Display export", | 752 | "display-dashboard-export": "Display export", |
753 | + "display-update-dashboard-image": "Display update dashboard image", | ||
751 | "display-dashboard-logo": "Display logo in dashboard fullscreen mode", | 754 | "display-dashboard-logo": "Display logo in dashboard fullscreen mode", |
752 | "dashboard-logo-image": "Dashboard logo image", | 755 | "dashboard-logo-image": "Dashboard logo image", |
753 | "import": "Import dashboard", | 756 | "import": "Import dashboard", |
@@ -2222,6 +2222,11 @@ base64-arraybuffer@0.1.5: | @@ -2222,6 +2222,11 @@ base64-arraybuffer@0.1.5: | ||
2222 | resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" | 2222 | resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" |
2223 | integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= | 2223 | integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= |
2224 | 2224 | ||
2225 | +base64-arraybuffer@^0.2.0: | ||
2226 | + version "0.2.0" | ||
2227 | + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45" | ||
2228 | + integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ== | ||
2229 | + | ||
2225 | base64-js@^1.0.2: | 2230 | base64-js@^1.0.2: |
2226 | version "1.3.1" | 2231 | version "1.3.1" |
2227 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" | 2232 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" |
@@ -3206,6 +3211,13 @@ css-declaration-sorter@^4.0.1: | @@ -3206,6 +3211,13 @@ css-declaration-sorter@^4.0.1: | ||
3206 | postcss "^7.0.1" | 3211 | postcss "^7.0.1" |
3207 | timsort "^0.3.0" | 3212 | timsort "^0.3.0" |
3208 | 3213 | ||
3214 | +css-line-break@1.1.1: | ||
3215 | + version "1.1.1" | ||
3216 | + resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.1.1.tgz#d5e9bdd297840099eb0503c7310fd34927a026ef" | ||
3217 | + integrity sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA== | ||
3218 | + dependencies: | ||
3219 | + base64-arraybuffer "^0.2.0" | ||
3220 | + | ||
3209 | css-loader@4.2.2: | 3221 | css-loader@4.2.2: |
3210 | version "4.2.2" | 3222 | version "4.2.2" |
3211 | resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.2.tgz#b668b3488d566dc22ebcf9425c5f254a05808c89" | 3223 | resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.2.tgz#b668b3488d566dc22ebcf9425c5f254a05808c89" |
@@ -4782,6 +4794,13 @@ html-escaper@^2.0.0: | @@ -4782,6 +4794,13 @@ html-escaper@^2.0.0: | ||
4782 | resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" | 4794 | resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" |
4783 | integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== | 4795 | integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== |
4784 | 4796 | ||
4797 | +html2canvas@^1.0.0-rc.7: | ||
4798 | + version "1.0.0-rc.7" | ||
4799 | + resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.0.0-rc.7.tgz#70c159ce0e63954a91169531894d08ad5627ac98" | ||
4800 | + integrity sha512-yvPNZGejB2KOyKleZspjK/NruXVQuowu8NnV2HYG7gW7ytzl+umffbtUI62v2dCHQLDdsK6HIDtyJZ0W3neerA== | ||
4801 | + dependencies: | ||
4802 | + css-line-break "1.1.1" | ||
4803 | + | ||
4785 | http-cache-semantics@^3.8.1: | 4804 | http-cache-semantics@^3.8.1: |
4786 | version "3.8.1" | 4805 | version "3.8.1" |
4787 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" | 4806 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" |