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 | 47 | "flot": "git://github.com/thingsboard/flot.git#0.9-work", |
48 | 48 | "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", |
49 | 49 | "font-awesome": "^4.7.0", |
50 | + "html2canvas": "^1.0.0-rc.7", | |
50 | 51 | "jquery": "^3.5.1", |
51 | 52 | "jquery.terminal": "^2.18.3", |
52 | 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 | 81 | (click)="isFullscreen = !isFullscreen"> |
82 | 82 | <mat-icon>{{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
83 | 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 | 90 | <button [fxShow]="currentDashboardId && (isEdit || displayExport())" mat-icon-button |
85 | 91 | matTooltip="{{'dashboard.export' | translate}}" |
86 | 92 | matTooltipPosition="below" |
... | ... | @@ -133,6 +139,7 @@ |
133 | 139 | </tb-dashboard-toolbar> |
134 | 140 | </section> |
135 | 141 | <section class="tb-dashboard-container tb-absolute-fill" |
142 | + #dashboardContainer | |
136 | 143 | [ngClass]="{ 'is-fullscreen': forceFullscreen, |
137 | 144 | 'tb-dashboard-toolbar-opened': toolbarOpened, |
138 | 145 | 'tb-dashboard-toolbar-animated': isToolbarOpenedAnimate, |
... | ... | @@ -187,7 +194,7 @@ |
187 | 194 | </tb-dashboard-layout> |
188 | 195 | </mat-drawer-content> |
189 | 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 | 198 | <tb-footer-fab-buttons *ngIf="!embedded && !isMobileApp" |
192 | 199 | [fxShow]="!isAddingWidget && isEdit && !widgetEditMode" |
193 | 200 | relative |
... | ... | @@ -212,7 +219,7 @@ |
212 | 219 | <mat-icon>{{ isEdit ? 'close' : 'edit' }}</mat-icon> |
213 | 220 | </button> |
214 | 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 | 223 | <span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard v.{{ thingsboardVersion }}</a></span> |
217 | 224 | </section> |
218 | 225 | </mat-drawer-content> | ... | ... |
... | ... | @@ -16,7 +16,7 @@ |
16 | 16 | |
17 | 17 | import { |
18 | 18 | ChangeDetectorRef, |
19 | - Component, | |
19 | + Component, ElementRef, | |
20 | 20 | Inject, |
21 | 21 | Injector, |
22 | 22 | Input, |
... | ... | @@ -123,6 +123,11 @@ import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/ |
123 | 123 | import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; |
124 | 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 | 131 | // @dynamic |
127 | 132 | @Component({ |
128 | 133 | selector: 'tb-dashboard-page', |
... | ... | @@ -153,6 +158,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC |
153 | 158 | dashboard: Dashboard; |
154 | 159 | dashboardConfiguration: DashboardConfiguration; |
155 | 160 | |
161 | + @ViewChild('dashboardContainer') dashboardContainer: ElementRef<HTMLElement>; | |
162 | + | |
156 | 163 | prevDashboard: Dashboard; |
157 | 164 | |
158 | 165 | iframeMode = this.utils.iframeMode; |
... | ... | @@ -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 | 487 | public displayDashboardTimewindow(): boolean { |
472 | 488 | if (this.dashboard.configuration.settings && |
473 | 489 | isDefined(this.dashboard.configuration.settings.showDashboardTimewindow)) { |
... | ... | @@ -1248,4 +1264,24 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC |
1248 | 1264 | onCloseSearchBundle() { |
1249 | 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 | 69 | <mat-checkbox fxFlex formControlName="showDashboardExport"> |
70 | 70 | {{ 'dashboard.display-dashboard-export' | translate }} |
71 | 71 | </mat-checkbox> |
72 | + <mat-checkbox fxFlex formControlName="showUpdateDashboardImage"> | |
73 | + {{ 'dashboard.display-update-dashboard-image' | translate }} | |
74 | + </mat-checkbox> | |
72 | 75 | </div> |
73 | 76 | <mat-checkbox formControlName="showDashboardLogo"> |
74 | 77 | {{ 'dashboard.display-dashboard-logo' | translate }} | ... | ... |
... | ... | @@ -82,7 +82,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS |
82 | 82 | showDashboardLogo: [isUndefined(this.settings.showDashboardLogo) ? false : this.settings.showDashboardLogo, []], |
83 | 83 | dashboardLogoUrl: [isUndefined(this.settings.dashboardLogoUrl) ? null : this.settings.dashboardLogoUrl, []], |
84 | 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 | 88 | this.settingsFormGroup.get('stateControllerId').valueChanges.subscribe( |
88 | 89 | (stateControllerId: StateControllerId) => { | ... | ... |
... | ... | @@ -141,6 +141,7 @@ import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-dow |
141 | 141 | import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component'; |
142 | 142 | import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component'; |
143 | 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 | 146 | @NgModule({ |
146 | 147 | declarations: |
... | ... | @@ -260,6 +261,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu |
260 | 261 | DashboardSettingsDialogComponent, |
261 | 262 | ManageDashboardStatesDialogComponent, |
262 | 263 | DashboardStateDialogComponent, |
264 | + DashboardImageDialogComponent, | |
263 | 265 | EmbedDashboardDialogComponent, |
264 | 266 | DisplayWidgetTypesPanelComponent |
265 | 267 | ], |
... | ... | @@ -370,6 +372,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu |
370 | 372 | DashboardSettingsDialogComponent, |
371 | 373 | ManageDashboardStatesDialogComponent, |
372 | 374 | DashboardStateDialogComponent, |
375 | + DashboardImageDialogComponent, | |
373 | 376 | EmbedDashboardDialogComponent, |
374 | 377 | DisplayWidgetTypesPanelComponent |
375 | 378 | ], | ... | ... |
... | ... | @@ -667,6 +667,8 @@ |
667 | 667 | "add-widget": "Add new widget", |
668 | 668 | "title": "Title", |
669 | 669 | "image": "Dashboard image", |
670 | + "update-image": "Update dashboard image", | |
671 | + "take-screenshot": "Take screenshot", | |
670 | 672 | "select-widget-title": "Select widget", |
671 | 673 | "select-widget-value": "{{title}}: select widget", |
672 | 674 | "select-widget-subtitle": "List of available widget types", |
... | ... | @@ -748,6 +750,7 @@ |
748 | 750 | "display-filters": "Display filters", |
749 | 751 | "display-dashboard-timewindow": "Display timewindow", |
750 | 752 | "display-dashboard-export": "Display export", |
753 | + "display-update-dashboard-image": "Display update dashboard image", | |
751 | 754 | "display-dashboard-logo": "Display logo in dashboard fullscreen mode", |
752 | 755 | "dashboard-logo-image": "Dashboard logo image", |
753 | 756 | "import": "Import dashboard", | ... | ... |
... | ... | @@ -2222,6 +2222,11 @@ base64-arraybuffer@0.1.5: |
2222 | 2222 | resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" |
2223 | 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 | 2230 | base64-js@^1.0.2: |
2226 | 2231 | version "1.3.1" |
2227 | 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 | 3211 | postcss "^7.0.1" |
3207 | 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 | 3221 | css-loader@4.2.2: |
3210 | 3222 | version "4.2.2" |
3211 | 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 | 4794 | resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" |
4783 | 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 | 4804 | http-cache-semantics@^3.8.1: |
4786 | 4805 | version "3.8.1" |
4787 | 4806 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" | ... | ... |