tasmota-esp-web-tools 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/.devcontainer/Dockerfile +16 -0
  2. package/.devcontainer/devcontainer.json +44 -0
  3. package/.github/dependabot.yml +10 -0
  4. package/.github/release-drafter.yml +12 -0
  5. package/.github/workflows/ci.yml +22 -0
  6. package/.github/workflows/npmpublish.yml +22 -0
  7. package/.github/workflows/release-drafter.yml +14 -0
  8. package/.prettierignore +1 -0
  9. package/README.md +68 -0
  10. package/dist/components/ewt-button.d.ts +9 -0
  11. package/dist/components/ewt-button.js +17 -0
  12. package/dist/components/ewt-checkbox.d.ts +9 -0
  13. package/dist/components/ewt-checkbox.js +6 -0
  14. package/dist/components/ewt-circular-progress.d.ts +9 -0
  15. package/dist/components/ewt-circular-progress.js +6 -0
  16. package/dist/components/ewt-console.d.ts +20 -0
  17. package/dist/components/ewt-console.js +141 -0
  18. package/dist/components/ewt-dialog.d.ts +9 -0
  19. package/dist/components/ewt-dialog.js +14 -0
  20. package/dist/components/ewt-formfield.d.ts +9 -0
  21. package/dist/components/ewt-formfield.js +6 -0
  22. package/dist/components/ewt-icon-button.d.ts +9 -0
  23. package/dist/components/ewt-icon-button.js +6 -0
  24. package/dist/components/ewt-list-item.d.ts +9 -0
  25. package/dist/components/ewt-list-item.js +6 -0
  26. package/dist/components/ewt-select.d.ts +9 -0
  27. package/dist/components/ewt-select.js +15 -0
  28. package/dist/components/ewt-textfield.d.ts +9 -0
  29. package/dist/components/ewt-textfield.js +15 -0
  30. package/dist/components/svg.d.ts +3 -0
  31. package/dist/components/svg.js +24 -0
  32. package/dist/connect.d.ts +3 -0
  33. package/dist/connect.js +33 -0
  34. package/dist/const.d.ts +94 -0
  35. package/dist/const.js +1 -0
  36. package/dist/flash.d.ts +4 -0
  37. package/dist/flash.js +191 -0
  38. package/dist/install-button.d.ts +17 -0
  39. package/dist/install-button.js +96 -0
  40. package/dist/install-dialog.d.ts +70 -0
  41. package/dist/install-dialog.js +899 -0
  42. package/dist/no-port-picked/index.d.ts +2 -0
  43. package/dist/no-port-picked/index.js +7 -0
  44. package/dist/no-port-picked/no-port-picked-dialog.d.ts +15 -0
  45. package/dist/no-port-picked/no-port-picked-dialog.js +149 -0
  46. package/dist/pages/ewt-page-message.d.ts +14 -0
  47. package/dist/pages/ewt-page-message.js +34 -0
  48. package/dist/pages/ewt-page-progress.d.ts +14 -0
  49. package/dist/pages/ewt-page-progress.js +39 -0
  50. package/dist/styles.d.ts +1 -0
  51. package/dist/styles.js +32 -0
  52. package/dist/util/chip-family-name.d.ts +3 -0
  53. package/dist/util/chip-family-name.js +17 -0
  54. package/dist/util/console-color.d.ts +19 -0
  55. package/dist/util/console-color.js +265 -0
  56. package/dist/util/file-download.d.ts +2 -0
  57. package/dist/util/file-download.js +15 -0
  58. package/dist/util/fire-event.d.ts +5 -0
  59. package/dist/util/fire-event.js +12 -0
  60. package/dist/util/line-break-transformer.d.ts +5 -0
  61. package/dist/util/line-break-transformer.js +17 -0
  62. package/dist/util/manifest.d.ts +2 -0
  63. package/dist/util/manifest.js +12 -0
  64. package/dist/util/sleep.d.ts +1 -0
  65. package/dist/util/sleep.js +1 -0
  66. package/dist/web/connect-3012e6dd.js +886 -0
  67. package/dist/web/esp32-5f88817f.js +1 -0
  68. package/dist/web/esp32c3-596796ad.js +1 -0
  69. package/dist/web/esp32s2-f7a69530.js +1 -0
  70. package/dist/web/esp32s3-314fbacd.js +1 -0
  71. package/dist/web/esp8266-c68f89af.js +1 -0
  72. package/dist/web/index-f110c132.js +126 -0
  73. package/dist/web/install-button.js +1 -0
  74. package/package.json +36 -0
  75. package/rollup.config.js +28 -0
  76. package/script/build +8 -0
  77. package/script/develop +17 -0
  78. package/script/stubgen.py +161 -0
  79. package/src/components/ewt-button.ts +25 -0
  80. package/src/components/ewt-checkbox.ts +14 -0
  81. package/src/components/ewt-circular-progress.ts +14 -0
  82. package/src/components/ewt-console.ts +163 -0
  83. package/src/components/ewt-dialog.ts +22 -0
  84. package/src/components/ewt-formfield.ts +14 -0
  85. package/src/components/ewt-icon-button.ts +14 -0
  86. package/src/components/ewt-list-item.ts +14 -0
  87. package/src/components/ewt-select.ts +23 -0
  88. package/src/components/ewt-textfield.ts +23 -0
  89. package/src/components/svg.ts +27 -0
  90. package/src/connect.ts +42 -0
  91. package/src/const.ts +101 -0
  92. package/src/flash.ts +240 -0
  93. package/src/install-button.ts +128 -0
  94. package/src/install-dialog.ts +981 -0
  95. package/src/no-port-picked/index.ts +10 -0
  96. package/src/no-port-picked/no-port-picked-dialog.ts +158 -0
  97. package/src/pages/ewt-page-message.ts +39 -0
  98. package/src/pages/ewt-page-progress.ts +44 -0
  99. package/src/styles.ts +34 -0
  100. package/src/util/chip-family-name.ts +28 -0
  101. package/src/util/console-color.ts +283 -0
  102. package/src/util/file-download.ts +17 -0
  103. package/src/util/fire-event.ts +20 -0
  104. package/src/util/line-break-transformer.ts +20 -0
  105. package/src/util/manifest.ts +18 -0
  106. package/src/util/sleep.ts +2 -0
  107. package/static/logos/canairio.png +0 -0
  108. package/static/logos/espeasy.png +0 -0
  109. package/static/logos/esphome.svg +1 -0
  110. package/static/logos/tasmota.svg +1 -0
  111. package/static/logos/wled.png +0 -0
  112. package/static/screenshots/dashboard.png +0 -0
  113. package/static/screenshots/logs.png +0 -0
  114. package/static/social.png +0 -0
  115. package/tsconfig.json +21 -0
@@ -0,0 +1,899 @@
1
+ import { __decorate } from "tslib";
2
+ import { LitElement, html, css } from "lit";
3
+ import { state } from "lit/decorators.js";
4
+ import "./components/ewt-button";
5
+ import "./components/ewt-checkbox";
6
+ import "./components/ewt-console";
7
+ import "./components/ewt-dialog";
8
+ import "./components/ewt-formfield";
9
+ import "./components/ewt-icon-button";
10
+ import "./components/ewt-textfield";
11
+ import "./components/ewt-select";
12
+ import "./components/ewt-list-item";
13
+ import "./pages/ewt-page-progress";
14
+ import "./pages/ewt-page-message";
15
+ import { chipIcon, closeIcon, firmwareIcon } from "./components/svg";
16
+ import { ImprovSerial } from "improv-wifi-serial-sdk/dist/serial";
17
+ import { ImprovSerialCurrentState, PortNotReady, } from "improv-wifi-serial-sdk/dist/const";
18
+ import { flash } from "./flash";
19
+ import { textDownload } from "./util/file-download";
20
+ import { fireEvent } from "./util/fire-event";
21
+ import { sleep } from "./util/sleep";
22
+ import { downloadManifest } from "./util/manifest";
23
+ import { dialogStyles } from "./styles";
24
+ const ERROR_ICON = "⚠️";
25
+ const OK_ICON = "🎉";
26
+ export class EwtInstallDialog extends LitElement {
27
+ constructor() {
28
+ super(...arguments);
29
+ this.logger = console;
30
+ this._state = "DASHBOARD";
31
+ this._installErase = false;
32
+ this._installConfirmed = false;
33
+ this._provisionForce = false;
34
+ this._wasProvisioned = false;
35
+ this._busy = false;
36
+ // -1 = custom
37
+ this._selectedSsid = -1;
38
+ this._handleDisconnect = () => {
39
+ this._state = "ERROR";
40
+ this._error = "Disconnected";
41
+ };
42
+ }
43
+ render() {
44
+ if (!this.port) {
45
+ return html ``;
46
+ }
47
+ let heading;
48
+ let content;
49
+ let hideActions = false;
50
+ let allowClosing = false;
51
+ // During installation phase we temporarily remove the client
52
+ if (this._client === undefined &&
53
+ this._state !== "INSTALL" &&
54
+ this._state !== "LOGS") {
55
+ if (this._error) {
56
+ [heading, content, hideActions] = this._renderError(this._error);
57
+ }
58
+ else {
59
+ content = this._renderProgress("Connecting");
60
+ hideActions = true;
61
+ }
62
+ }
63
+ else if (this._state === "INSTALL") {
64
+ [heading, content, hideActions, allowClosing] = this._renderInstall();
65
+ }
66
+ else if (this._state === "ASK_ERASE") {
67
+ [heading, content] = this._renderAskErase();
68
+ }
69
+ else if (this._state === "ERROR") {
70
+ [heading, content, hideActions] = this._renderError(this._error);
71
+ }
72
+ else if (this._state === "DASHBOARD") {
73
+ [heading, content, hideActions, allowClosing] = this._client
74
+ ? this._renderDashboard()
75
+ : this._renderDashboardNoImprov();
76
+ }
77
+ else if (this._state === "PROVISION") {
78
+ [heading, content, hideActions] = this._renderProvision();
79
+ }
80
+ else if (this._state === "LOGS") {
81
+ [heading, content, hideActions] = this._renderLogs();
82
+ }
83
+ return html `
84
+ <ewt-dialog
85
+ open
86
+ .heading=${heading}
87
+ scrimClickAction
88
+ @closed=${this._handleClose}
89
+ .hideActions=${hideActions}
90
+ >
91
+ ${heading && allowClosing
92
+ ? html `
93
+ <ewt-icon-button dialogAction="close">
94
+ ${closeIcon}
95
+ </ewt-icon-button>
96
+ `
97
+ : ""}
98
+ ${content}
99
+ </ewt-dialog>
100
+ `;
101
+ }
102
+ _renderProgress(label, progress) {
103
+ return html `
104
+ <ewt-page-progress
105
+ .label=${label}
106
+ .progress=${progress}
107
+ ></ewt-page-progress>
108
+ `;
109
+ }
110
+ _renderError(label) {
111
+ const heading = "Error";
112
+ const content = html `
113
+ <ewt-page-message .icon=${ERROR_ICON} .label=${label}></ewt-page-message>
114
+ <ewt-button
115
+ slot="primaryAction"
116
+ dialogAction="ok"
117
+ label="Close"
118
+ ></ewt-button>
119
+ `;
120
+ const hideActions = false;
121
+ return [heading, content, hideActions];
122
+ }
123
+ _renderDashboard() {
124
+ const heading = this._info.name;
125
+ let content;
126
+ let hideActions = true;
127
+ let allowClosing = true;
128
+ content = html `
129
+ <div class="table-row">
130
+ ${firmwareIcon}
131
+ <div>${this._info.firmware}&nbsp;${this._info.version}</div>
132
+ </div>
133
+ <div class="table-row last">
134
+ ${chipIcon}
135
+ <div>${this._info.chipFamily}</div>
136
+ </div>
137
+ <div class="dashboard-buttons">
138
+ ${!this._isSameVersion
139
+ ? html `
140
+ <div>
141
+ <ewt-button
142
+ text-left
143
+ .label=${!this._isSameFirmware
144
+ ? `Install ${this._manifest.name}`
145
+ : `Update ${this._manifest.name}`}
146
+ @click=${() => {
147
+ if (this._isSameFirmware) {
148
+ this._startInstall(false);
149
+ }
150
+ else if (this._manifest.new_install_prompt_erase) {
151
+ this._state = "ASK_ERASE";
152
+ }
153
+ else {
154
+ this._startInstall(true);
155
+ }
156
+ }}
157
+ ></ewt-button>
158
+ </div>
159
+ `
160
+ : ""}
161
+ ${this._client.nextUrl === undefined
162
+ ? ""
163
+ : html `
164
+ <div>
165
+ <a
166
+ href=${this._client.nextUrl}
167
+ class="has-button"
168
+ target="_blank"
169
+ >
170
+ <ewt-button label="Visit Device"></ewt-button>
171
+ </a>
172
+ </div>
173
+ `}
174
+ ${!this._manifest.home_assistant_domain ||
175
+ this._client.state !== ImprovSerialCurrentState.PROVISIONED
176
+ ? ""
177
+ : html `
178
+ <div>
179
+ <a
180
+ href=${`https://my.home-assistant.io/redirect/config_flow_start/?domain=${this._manifest.home_assistant_domain}`}
181
+ class="has-button"
182
+ target="_blank"
183
+ >
184
+ <ewt-button label="Add to Home Assistant"></ewt-button>
185
+ </a>
186
+ </div>
187
+ `}
188
+ <div>
189
+ <ewt-button
190
+ .label=${this._client.state === ImprovSerialCurrentState.READY
191
+ ? "Connect to Wi-Fi"
192
+ : "Change Wi-Fi"}
193
+ @click=${() => {
194
+ this._state = "PROVISION";
195
+ if (this._client.state === ImprovSerialCurrentState.PROVISIONED) {
196
+ this._provisionForce = true;
197
+ }
198
+ }}
199
+ ></ewt-button>
200
+ </div>
201
+ <div>
202
+ <ewt-button
203
+ label="Logs & Console"
204
+ @click=${async () => {
205
+ const client = this._client;
206
+ if (client) {
207
+ await this._closeClientWithoutEvents(client);
208
+ await sleep(100);
209
+ }
210
+ // Also set `null` back to undefined.
211
+ this._client = undefined;
212
+ this._state = "LOGS";
213
+ }}
214
+ ></ewt-button>
215
+ </div>
216
+ ${this._isSameFirmware && this._manifest.funding_url
217
+ ? html `
218
+ <div>
219
+ <a
220
+ class="button"
221
+ href=${this._manifest.funding_url}
222
+ target="_blank"
223
+ >
224
+ <ewt-button label="Fund Development"></ewt-button>
225
+ </a>
226
+ </div>
227
+ `
228
+ : ""}
229
+ ${this._isSameVersion
230
+ ? html `
231
+ <div>
232
+ <ewt-button
233
+ class="danger"
234
+ label="Erase User Data"
235
+ @click=${() => this._startInstall(true)}
236
+ ></ewt-button>
237
+ </div>
238
+ `
239
+ : ""}
240
+ </div>
241
+ `;
242
+ return [heading, content, hideActions, allowClosing];
243
+ }
244
+ _renderDashboardNoImprov() {
245
+ const heading = "Device Dashboard";
246
+ let content;
247
+ let hideActions = true;
248
+ let allowClosing = true;
249
+ content = html `
250
+ <div class="dashboard-buttons">
251
+ <div>
252
+ <ewt-button
253
+ text-left
254
+ .label=${`Install ${this._manifest.name}`}
255
+ @click=${() => {
256
+ if (this._manifest.new_install_prompt_erase) {
257
+ this._state = "ASK_ERASE";
258
+ }
259
+ else {
260
+ // Default is to erase a device that does not support Improv Serial
261
+ this._startInstall(true);
262
+ }
263
+ }}
264
+ ></ewt-button>
265
+ </div>
266
+
267
+ <div>
268
+ <ewt-button
269
+ label="Logs & Console"
270
+ @click=${async () => {
271
+ // Also set `null` back to undefined.
272
+ this._client = undefined;
273
+ this._state = "LOGS";
274
+ }}
275
+ ></ewt-button>
276
+ </div>
277
+ </div>
278
+ `;
279
+ return [heading, content, hideActions, allowClosing];
280
+ }
281
+ _renderProvision() {
282
+ let heading = "Configure Wi-Fi";
283
+ let content;
284
+ let hideActions = false;
285
+ if (this._busy) {
286
+ return [
287
+ heading,
288
+ this._renderProgress(this._ssids === undefined
289
+ ? "Scanning for networks"
290
+ : "Trying to connect"),
291
+ true,
292
+ ];
293
+ }
294
+ if (!this._provisionForce &&
295
+ this._client.state === ImprovSerialCurrentState.PROVISIONED) {
296
+ heading = undefined;
297
+ const showSetupLinks = !this._wasProvisioned &&
298
+ (this._client.nextUrl !== undefined ||
299
+ "home_assistant_domain" in this._manifest);
300
+ hideActions = showSetupLinks;
301
+ content = html `
302
+ <ewt-page-message
303
+ .icon=${OK_ICON}
304
+ label="Device connected to the network!"
305
+ ></ewt-page-message>
306
+ ${showSetupLinks
307
+ ? html `
308
+ <div class="dashboard-buttons">
309
+ ${this._client.nextUrl === undefined
310
+ ? ""
311
+ : html `
312
+ <div>
313
+ <a
314
+ href=${this._client.nextUrl}
315
+ class="has-button"
316
+ target="_blank"
317
+ @click=${() => {
318
+ this._state = "DASHBOARD";
319
+ }}
320
+ >
321
+ <ewt-button label="Visit Device"></ewt-button>
322
+ </a>
323
+ </div>
324
+ `}
325
+ ${!this._manifest.home_assistant_domain
326
+ ? ""
327
+ : html `
328
+ <div>
329
+ <a
330
+ href=${`https://my.home-assistant.io/redirect/config_flow_start/?domain=${this._manifest.home_assistant_domain}`}
331
+ class="has-button"
332
+ target="_blank"
333
+ @click=${() => {
334
+ this._state = "DASHBOARD";
335
+ }}
336
+ >
337
+ <ewt-button
338
+ label="Add to Home Assistant"
339
+ ></ewt-button>
340
+ </a>
341
+ </div>
342
+ `}
343
+ <div>
344
+ <ewt-button
345
+ label="Skip"
346
+ @click=${() => {
347
+ this._state = "DASHBOARD";
348
+ }}
349
+ ></ewt-button>
350
+ </div>
351
+ </div>
352
+ `
353
+ : html `
354
+ <ewt-button
355
+ slot="primaryAction"
356
+ label="Continue"
357
+ @click=${() => {
358
+ this._state = "DASHBOARD";
359
+ }}
360
+ ></ewt-button>
361
+ `}
362
+ `;
363
+ }
364
+ else {
365
+ let error;
366
+ switch (this._client.error) {
367
+ case 3 /* ImprovSerialErrorState.UNABLE_TO_CONNECT */:
368
+ error = "Unable to connect";
369
+ break;
370
+ case 0 /* ImprovSerialErrorState.NO_ERROR */:
371
+ // Happens when list SSIDs not supported.
372
+ case 2 /* ImprovSerialErrorState.UNKNOWN_RPC_COMMAND */:
373
+ break;
374
+ default:
375
+ error = `Unknown error (${this._client.error})`;
376
+ }
377
+ content = html `
378
+ <div>
379
+ Enter the credentials of the Wi-Fi network that you want your device
380
+ to connect to.
381
+ </div>
382
+ ${error ? html `<p class="error">${error}</p>` : ""}
383
+ ${this._ssids !== null
384
+ ? html `
385
+ <ewt-select
386
+ fixedMenuPosition
387
+ label="Network"
388
+ @selected=${(ev) => {
389
+ const index = ev.detail.index;
390
+ // The "Join Other" item is always the last item.
391
+ this._selectedSsid =
392
+ index === this._ssids.length ? -1 : index;
393
+ }}
394
+ @closed=${(ev) => ev.stopPropagation()}
395
+ >
396
+ ${this._ssids.map((info, idx) => html `
397
+ <ewt-list-item
398
+ .selected=${this._selectedSsid === idx}
399
+ value=${idx}
400
+ >
401
+ ${info.name}
402
+ </ewt-list-item>
403
+ `)}
404
+ <ewt-list-item
405
+ .selected=${this._selectedSsid === -1}
406
+ value="-1"
407
+ >
408
+ Join other…
409
+ </ewt-list-item>
410
+ </ewt-select>
411
+ `
412
+ : ""}
413
+ ${
414
+ // Show input box if command not supported or "Join Other" selected
415
+ this._selectedSsid === -1
416
+ ? html `
417
+ <ewt-textfield label="Network Name" name="ssid"></ewt-textfield>
418
+ `
419
+ : ""}
420
+ <ewt-textfield
421
+ label="Password"
422
+ name="password"
423
+ type="password"
424
+ ></ewt-textfield>
425
+ <ewt-button
426
+ slot="primaryAction"
427
+ label="Connect"
428
+ @click=${this._doProvision}
429
+ ></ewt-button>
430
+ <ewt-button
431
+ slot="secondaryAction"
432
+ .label=${this._installState && this._installErase ? "Skip" : "Back"}
433
+ @click=${() => {
434
+ this._state = "DASHBOARD";
435
+ }}
436
+ ></ewt-button>
437
+ `;
438
+ }
439
+ return [heading, content, hideActions];
440
+ }
441
+ _renderAskErase() {
442
+ const heading = "Erase device";
443
+ const content = html `
444
+ <div>
445
+ Do you want to erase the device before installing
446
+ ${this._manifest.name}? All data on the device will be lost.
447
+ </div>
448
+ <ewt-formfield label="Erase device" class="danger">
449
+ <ewt-checkbox></ewt-checkbox>
450
+ </ewt-formfield>
451
+ <ewt-button
452
+ slot="primaryAction"
453
+ label="Next"
454
+ @click=${() => {
455
+ const checkbox = this.shadowRoot.querySelector("ewt-checkbox");
456
+ this._startInstall(checkbox.checked);
457
+ }}
458
+ ></ewt-button>
459
+ <ewt-button
460
+ slot="secondaryAction"
461
+ label="Back"
462
+ @click=${() => {
463
+ this._state = "DASHBOARD";
464
+ }}
465
+ ></ewt-button>
466
+ `;
467
+ return [heading, content];
468
+ }
469
+ _renderInstall() {
470
+ let heading;
471
+ let content;
472
+ let hideActions = false;
473
+ const allowClosing = false;
474
+ const isUpdate = !this._installErase && this._isSameFirmware;
475
+ if (!this._installConfirmed && this._isSameVersion) {
476
+ heading = "Erase User Data";
477
+ content = html `
478
+ Do you want to reset your device and erase all user data from your
479
+ device?
480
+ <ewt-button
481
+ class="danger"
482
+ slot="primaryAction"
483
+ label="Erase User Data"
484
+ @click=${this._confirmInstall}
485
+ ></ewt-button>
486
+ `;
487
+ }
488
+ else if (!this._installConfirmed) {
489
+ heading = "Confirm Installation";
490
+ const action = isUpdate ? "update to" : "install";
491
+ content = html `
492
+ ${isUpdate
493
+ ? html `Your device is running
494
+ ${this._info.firmware}&nbsp;${this._info.version}.<br /><br />`
495
+ : ""}
496
+ Do you want to ${action}
497
+ ${this._manifest.name}&nbsp;${this._manifest.version}?
498
+ ${this._installErase
499
+ ? html `<br /><br />All data on the device will be erased.`
500
+ : ""}
501
+ <ewt-button
502
+ slot="primaryAction"
503
+ label="Install"
504
+ @click=${this._confirmInstall}
505
+ ></ewt-button>
506
+ <ewt-button
507
+ slot="secondaryAction"
508
+ label="Back"
509
+ @click=${() => {
510
+ this._state = "DASHBOARD";
511
+ }}
512
+ ></ewt-button>
513
+ `;
514
+ }
515
+ else if (!this._installState ||
516
+ this._installState.state === "initializing" /* FlashStateType.INITIALIZING */ ||
517
+ this._installState.state === "manifest" /* FlashStateType.MANIFEST */ ||
518
+ this._installState.state === "preparing" /* FlashStateType.PREPARING */) {
519
+ heading = "Installing";
520
+ content = this._renderProgress("Preparing installation");
521
+ hideActions = true;
522
+ }
523
+ else if (this._installState.state === "erasing" /* FlashStateType.ERASING */) {
524
+ heading = "Installing";
525
+ content = this._renderProgress("Erasing");
526
+ hideActions = true;
527
+ }
528
+ else if (this._installState.state === "writing" /* FlashStateType.WRITING */ ||
529
+ // When we're finished, keep showing this screen with 100% written
530
+ // until Improv is initialized / not detected.
531
+ (this._installState.state === "finished" /* FlashStateType.FINISHED */ &&
532
+ this._client === undefined)) {
533
+ heading = "Installing";
534
+ let percentage;
535
+ let undeterminateLabel;
536
+ if (this._installState.state === "finished" /* FlashStateType.FINISHED */) {
537
+ // We're done writing and detecting improv, show spinner
538
+ undeterminateLabel = "Wrapping up";
539
+ }
540
+ else if (this._installState.details.percentage < 4) {
541
+ // We're writing the firmware under 4%, show spinner or else we don't show any pixels
542
+ undeterminateLabel = "Installing";
543
+ }
544
+ else {
545
+ // We're writing the firmware over 4%, show progress bar
546
+ percentage = this._installState.details.percentage;
547
+ }
548
+ content = this._renderProgress(html `
549
+ ${undeterminateLabel ? html `${undeterminateLabel}<br />` : ""}
550
+ <br />
551
+ This will take
552
+ ${this._installState.chipFamily === "ESP8266"
553
+ ? "a minute"
554
+ : "2 minutes"}.<br />
555
+ Keep this page visible to prevent slow down
556
+ `, percentage);
557
+ hideActions = true;
558
+ }
559
+ else if (this._installState.state === "finished" /* FlashStateType.FINISHED */) {
560
+ heading = undefined;
561
+ const supportsImprov = this._client !== null;
562
+ content = html `
563
+ <ewt-page-message
564
+ .icon=${OK_ICON}
565
+ label="Installation complete!"
566
+ ></ewt-page-message>
567
+ <ewt-button
568
+ slot="primaryAction"
569
+ label="Next"
570
+ @click=${() => {
571
+ this._state =
572
+ supportsImprov && this._installErase ? "PROVISION" : "DASHBOARD";
573
+ }}
574
+ ></ewt-button>
575
+ `;
576
+ }
577
+ else if (this._installState.state === "error" /* FlashStateType.ERROR */) {
578
+ heading = "Installation failed";
579
+ content = html `
580
+ <ewt-page-message
581
+ .icon=${ERROR_ICON}
582
+ .label=${this._installState.message}
583
+ ></ewt-page-message>
584
+ <ewt-button
585
+ slot="primaryAction"
586
+ label="Back"
587
+ @click=${async () => {
588
+ this._initialize();
589
+ this._state = "DASHBOARD";
590
+ }}
591
+ ></ewt-button>
592
+ `;
593
+ }
594
+ return [heading, content, hideActions, allowClosing];
595
+ }
596
+ _renderLogs() {
597
+ let heading = `Logs`;
598
+ let content;
599
+ let hideActions = false;
600
+ content = html `
601
+ <ewt-console .port=${this.port} .logger=${this.logger}></ewt-console>
602
+ <ewt-button
603
+ slot="primaryAction"
604
+ label="Back"
605
+ @click=${async () => {
606
+ await this.shadowRoot.querySelector("ewt-console").disconnect();
607
+ this._state = "DASHBOARD";
608
+ this._initialize();
609
+ }}
610
+ ></ewt-button>
611
+ <ewt-button
612
+ slot="secondaryAction"
613
+ label="Download Logs"
614
+ @click=${() => {
615
+ textDownload(this.shadowRoot.querySelector("ewt-console").logs(), `esp-web-tools-logs.txt`);
616
+ this.shadowRoot.querySelector("ewt-console").reset();
617
+ }}
618
+ ></ewt-button>
619
+ <ewt-button
620
+ slot="secondaryAction"
621
+ label="Reset Device"
622
+ @click=${async () => {
623
+ await this.shadowRoot.querySelector("ewt-console").reset();
624
+ }}
625
+ ></ewt-button>
626
+ `;
627
+ return [heading, content, hideActions];
628
+ }
629
+ willUpdate(changedProps) {
630
+ if (!changedProps.has("_state")) {
631
+ return;
632
+ }
633
+ // Clear errors when changing between pages unless we change
634
+ // to the error page.
635
+ if (this._state !== "ERROR") {
636
+ this._error = undefined;
637
+ }
638
+ // Scan for SSIDs on provision
639
+ if (this._state === "PROVISION") {
640
+ this._ssids = undefined;
641
+ this._busy = true;
642
+ this._client.scan().then((ssids) => {
643
+ this._busy = false;
644
+ this._ssids = ssids;
645
+ this._selectedSsid = ssids.length ? 0 : -1;
646
+ }, () => {
647
+ this._busy = false;
648
+ this._ssids = null;
649
+ this._selectedSsid = -1;
650
+ });
651
+ }
652
+ else {
653
+ // Reset this value if we leave provisioning.
654
+ this._provisionForce = false;
655
+ }
656
+ if (this._state === "INSTALL") {
657
+ this._installConfirmed = false;
658
+ this._installState = undefined;
659
+ }
660
+ }
661
+ firstUpdated(changedProps) {
662
+ super.firstUpdated(changedProps);
663
+ this._initialize();
664
+ }
665
+ updated(changedProps) {
666
+ super.updated(changedProps);
667
+ if (changedProps.has("_state")) {
668
+ this.setAttribute("state", this._state);
669
+ }
670
+ if (this._state !== "PROVISION") {
671
+ return;
672
+ }
673
+ if (changedProps.has("_selectedSsid") && this._selectedSsid === -1) {
674
+ // If we pick "Join other", select SSID input.
675
+ this._focusFormElement("ewt-textfield[name=ssid]");
676
+ }
677
+ else if (changedProps.has("_ssids")) {
678
+ // Form is shown when SSIDs are loaded/marked not supported
679
+ this._focusFormElement();
680
+ }
681
+ }
682
+ _focusFormElement(selector = "ewt-textfield, ewt-select") {
683
+ const formEl = this.shadowRoot.querySelector(selector);
684
+ if (formEl) {
685
+ formEl.updateComplete.then(() => setTimeout(() => formEl.focus(), 100));
686
+ }
687
+ }
688
+ async _initialize(justInstalled = false) {
689
+ if (this.port.readable === null || this.port.writable === null) {
690
+ this._state = "ERROR";
691
+ this._error =
692
+ "Serial port is not readable/writable. Close any other application using it and try again.";
693
+ return;
694
+ }
695
+ try {
696
+ this._manifest = await downloadManifest(this.manifestPath);
697
+ }
698
+ catch (err) {
699
+ this._state = "ERROR";
700
+ this._error = "Failed to download manifest";
701
+ return;
702
+ }
703
+ if (this._manifest.new_install_improv_wait_time === 0) {
704
+ this._client = null;
705
+ return;
706
+ }
707
+ const client = new ImprovSerial(this.port, this.logger);
708
+ client.addEventListener("state-changed", () => {
709
+ this.requestUpdate();
710
+ });
711
+ client.addEventListener("error-changed", () => this.requestUpdate());
712
+ try {
713
+ // If a device was just installed, give new firmware 10 seconds (overridable) to
714
+ // format the rest of the flash and do other stuff.
715
+ const timeout = !justInstalled
716
+ ? 1000
717
+ : this._manifest.new_install_improv_wait_time !== undefined
718
+ ? this._manifest.new_install_improv_wait_time * 1000
719
+ : 10000;
720
+ this._info = await client.initialize(timeout);
721
+ this._client = client;
722
+ client.addEventListener("disconnect", this._handleDisconnect);
723
+ }
724
+ catch (err) {
725
+ // Clear old value
726
+ this._info = undefined;
727
+ if (err instanceof PortNotReady) {
728
+ this._state = "ERROR";
729
+ this._error =
730
+ "Serial port is not ready. Close any other application using it and try again.";
731
+ }
732
+ else {
733
+ this._client = null; // not supported
734
+ this.logger.error("Improv initialization failed.", err);
735
+ }
736
+ }
737
+ }
738
+ _startInstall(erase) {
739
+ this._state = "INSTALL";
740
+ this._installErase = erase;
741
+ this._installConfirmed = false;
742
+ }
743
+ async _confirmInstall() {
744
+ this._installConfirmed = true;
745
+ this._installState = undefined;
746
+ if (this._client) {
747
+ await this._closeClientWithoutEvents(this._client);
748
+ }
749
+ this._client = undefined;
750
+ flash((state) => {
751
+ this._installState = state;
752
+ if (state.state === "finished" /* FlashStateType.FINISHED */) {
753
+ sleep(100)
754
+ .then(() => this._initialize(true))
755
+ .then(() => this.requestUpdate());
756
+ }
757
+ }, this.port, this.logger, this.manifestPath, this._installErase);
758
+ }
759
+ async _doProvision() {
760
+ this._busy = true;
761
+ this._wasProvisioned =
762
+ this._client.state === ImprovSerialCurrentState.PROVISIONED;
763
+ const ssid = this._selectedSsid === -1
764
+ ? this.shadowRoot.querySelector("ewt-textfield[name=ssid]").value
765
+ : this._ssids[this._selectedSsid].name;
766
+ const password = this.shadowRoot.querySelector("ewt-textfield[name=password]").value;
767
+ try {
768
+ await this._client.provision(ssid, password);
769
+ }
770
+ catch (err) {
771
+ return;
772
+ }
773
+ finally {
774
+ this._busy = false;
775
+ this._provisionForce = false;
776
+ }
777
+ }
778
+ async _handleClose() {
779
+ if (this._client) {
780
+ await this._closeClientWithoutEvents(this._client);
781
+ }
782
+ fireEvent(this, "closed");
783
+ this.parentNode.removeChild(this);
784
+ }
785
+ /**
786
+ * Return if the device runs same firmware as manifest.
787
+ */
788
+ get _isSameFirmware() {
789
+ var _a;
790
+ return !this._info
791
+ ? false
792
+ : ((_a = this.overrides) === null || _a === void 0 ? void 0 : _a.checkSameFirmware)
793
+ ? this.overrides.checkSameFirmware(this._manifest, this._info)
794
+ : this._info.firmware === this._manifest.name;
795
+ }
796
+ /**
797
+ * Return if the device runs same firmware and version as manifest.
798
+ */
799
+ get _isSameVersion() {
800
+ return (this._isSameFirmware && this._info.version === this._manifest.version);
801
+ }
802
+ async _closeClientWithoutEvents(client) {
803
+ client.removeEventListener("disconnect", this._handleDisconnect);
804
+ await client.close();
805
+ }
806
+ }
807
+ EwtInstallDialog.styles = [
808
+ dialogStyles,
809
+ css `
810
+ :host {
811
+ --mdc-dialog-max-width: 390px;
812
+ }
813
+ ewt-icon-button {
814
+ position: absolute;
815
+ right: 4px;
816
+ top: 10px;
817
+ }
818
+ .table-row {
819
+ display: flex;
820
+ }
821
+ .table-row.last {
822
+ margin-bottom: 16px;
823
+ }
824
+ .table-row svg {
825
+ width: 20px;
826
+ margin-right: 8px;
827
+ }
828
+ ewt-textfield,
829
+ ewt-select {
830
+ display: block;
831
+ margin-top: 16px;
832
+ }
833
+ .dashboard-buttons {
834
+ margin: 0 0 -16px -8px;
835
+ }
836
+ .dashboard-buttons div {
837
+ display: block;
838
+ margin: 4px 0;
839
+ }
840
+ a.has-button {
841
+ text-decoration: none;
842
+ }
843
+ .error {
844
+ color: var(--improv-danger-color);
845
+ }
846
+ .danger {
847
+ --mdc-theme-primary: var(--improv-danger-color);
848
+ --mdc-theme-secondary: var(--improv-danger-color);
849
+ }
850
+ button.link {
851
+ background: none;
852
+ color: inherit;
853
+ border: none;
854
+ padding: 0;
855
+ font: inherit;
856
+ text-align: left;
857
+ text-decoration: underline;
858
+ cursor: pointer;
859
+ }
860
+ :host([state="LOGS"]) ewt-dialog {
861
+ --mdc-dialog-max-width: 90vw;
862
+ }
863
+ ewt-console {
864
+ width: calc(80vw - 48px);
865
+ height: 80vh;
866
+ }
867
+ `,
868
+ ];
869
+ __decorate([
870
+ state()
871
+ ], EwtInstallDialog.prototype, "_client", void 0);
872
+ __decorate([
873
+ state()
874
+ ], EwtInstallDialog.prototype, "_state", void 0);
875
+ __decorate([
876
+ state()
877
+ ], EwtInstallDialog.prototype, "_installErase", void 0);
878
+ __decorate([
879
+ state()
880
+ ], EwtInstallDialog.prototype, "_installConfirmed", void 0);
881
+ __decorate([
882
+ state()
883
+ ], EwtInstallDialog.prototype, "_installState", void 0);
884
+ __decorate([
885
+ state()
886
+ ], EwtInstallDialog.prototype, "_provisionForce", void 0);
887
+ __decorate([
888
+ state()
889
+ ], EwtInstallDialog.prototype, "_error", void 0);
890
+ __decorate([
891
+ state()
892
+ ], EwtInstallDialog.prototype, "_busy", void 0);
893
+ __decorate([
894
+ state()
895
+ ], EwtInstallDialog.prototype, "_ssids", void 0);
896
+ __decorate([
897
+ state()
898
+ ], EwtInstallDialog.prototype, "_selectedSsid", void 0);
899
+ customElements.define("ewt-install-dialog", EwtInstallDialog);