UNPKG

30.5 kBJavaScriptView Raw
1import { __decorate } from "tslib";
2import { LitElement, html, css } from "lit";
3import { state } from "lit/decorators.js";
4import "./components/ewt-button";
5import "./components/ewt-checkbox";
6import "./components/ewt-console";
7import "./components/ewt-dialog";
8import "./components/ewt-formfield";
9import "./components/ewt-icon-button";
10import "./components/ewt-textfield";
11import "./components/ewt-select";
12import "./components/ewt-list-item";
13import "./pages/ewt-page-progress";
14import "./pages/ewt-page-message";
15import { chipIcon, closeIcon, firmwareIcon } from "./components/svg";
16import { ImprovSerial } from "improv-wifi-serial-sdk/dist/serial";
17import { ImprovSerialCurrentState, PortNotReady, } from "improv-wifi-serial-sdk/dist/const";
18import { flash } from "./flash";
19import { textDownload } from "./util/file-download";
20import { fireEvent } from "./util/fire-event";
21import { sleep } from "./util/sleep";
22import { downloadManifest } from "./util/manifest";
23import { dialogStyles } from "./styles";
24const ERROR_ICON = "⚠️";
25const OK_ICON = "🎉";
26export 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 === "preparing" /* FlashStateType.PREPARING */) {
518 heading = "Installing";
519 content = this._renderProgress("Preparing installation");
520 hideActions = true;
521 }
522 else if (this._installState.state === "erasing" /* FlashStateType.ERASING */) {
523 heading = "Installing";
524 content = this._renderProgress("Erasing");
525 hideActions = true;
526 }
527 else if (this._installState.state === "writing" /* FlashStateType.WRITING */ ||
528 // When we're finished, keep showing this screen with 100% written
529 // until Improv is initialized / not detected.
530 (this._installState.state === "finished" /* FlashStateType.FINISHED */ &&
531 this._client === undefined)) {
532 heading = "Installing";
533 let percentage;
534 let undeterminateLabel;
535 if (this._installState.state === "finished" /* FlashStateType.FINISHED */) {
536 // We're done writing and detecting improv, show spinner
537 undeterminateLabel = "Wrapping up";
538 }
539 else if (this._installState.details.percentage < 4) {
540 // We're writing the firmware under 4%, show spinner or else we don't show any pixels
541 undeterminateLabel = "Installing";
542 }
543 else {
544 // We're writing the firmware over 4%, show progress bar
545 percentage = this._installState.details.percentage;
546 }
547 content = this._renderProgress(html `
548 ${undeterminateLabel ? html `${undeterminateLabel}<br />` : ""}
549 <br />
550 This will take
551 ${this._installState.chipFamily === "ESP8266"
552 ? "a minute"
553 : "2 minutes"}.<br />
554 Keep this page visible to prevent slow down
555 `, percentage);
556 hideActions = true;
557 }
558 else if (this._installState.state === "finished" /* FlashStateType.FINISHED */) {
559 heading = undefined;
560 const supportsImprov = this._client !== null;
561 content = html `
562 <ewt-page-message
563 .icon=${OK_ICON}
564 label="Installation complete!"
565 ></ewt-page-message>
566 <ewt-button
567 slot="primaryAction"
568 label="Next"
569 @click=${() => {
570 this._state =
571 supportsImprov && this._installErase ? "PROVISION" : "DASHBOARD";
572 }}
573 ></ewt-button>
574 `;
575 }
576 else if (this._installState.state === "error" /* FlashStateType.ERROR */) {
577 heading = "Installation failed";
578 content = html `
579 <ewt-page-message
580 .icon=${ERROR_ICON}
581 .label=${this._installState.message}
582 ></ewt-page-message>
583 <ewt-button
584 slot="primaryAction"
585 label="Back"
586 @click=${async () => {
587 this._initialize();
588 this._state = "DASHBOARD";
589 }}
590 ></ewt-button>
591 `;
592 }
593 return [heading, content, hideActions, allowClosing];
594 }
595 _renderLogs() {
596 let heading = `Logs`;
597 let content;
598 let hideActions = false;
599 content = html `
600 <ewt-console .port=${this.port} .logger=${this.logger}></ewt-console>
601 <ewt-button
602 slot="primaryAction"
603 label="Back"
604 @click=${async () => {
605 await this.shadowRoot.querySelector("ewt-console").disconnect();
606 this._state = "DASHBOARD";
607 this._initialize();
608 }}
609 ></ewt-button>
610 <ewt-button
611 slot="secondaryAction"
612 label="Download Logs"
613 @click=${() => {
614 textDownload(this.shadowRoot.querySelector("ewt-console").logs(), `esp-web-tools-logs.txt`);
615 this.shadowRoot.querySelector("ewt-console").reset();
616 }}
617 ></ewt-button>
618 <ewt-button
619 slot="secondaryAction"
620 label="Reset Device"
621 @click=${async () => {
622 await this.shadowRoot.querySelector("ewt-console").reset();
623 }}
624 ></ewt-button>
625 `;
626 return [heading, content, hideActions];
627 }
628 willUpdate(changedProps) {
629 if (!changedProps.has("_state")) {
630 return;
631 }
632 // Clear errors when changing between pages unless we change
633 // to the error page.
634 if (this._state !== "ERROR") {
635 this._error = undefined;
636 }
637 // Scan for SSIDs on provision
638 if (this._state === "PROVISION") {
639 this._ssids = undefined;
640 this._busy = true;
641 this._client.scan().then((ssids) => {
642 this._busy = false;
643 this._ssids = ssids;
644 this._selectedSsid = ssids.length ? 0 : -1;
645 }, () => {
646 this._busy = false;
647 this._ssids = null;
648 this._selectedSsid = -1;
649 });
650 }
651 else {
652 // Reset this value if we leave provisioning.
653 this._provisionForce = false;
654 }
655 if (this._state === "INSTALL") {
656 this._installConfirmed = false;
657 this._installState = undefined;
658 }
659 }
660 firstUpdated(changedProps) {
661 super.firstUpdated(changedProps);
662 this._initialize();
663 }
664 updated(changedProps) {
665 super.updated(changedProps);
666 if (changedProps.has("_state")) {
667 this.setAttribute("state", this._state);
668 }
669 if (this._state !== "PROVISION") {
670 return;
671 }
672 if (changedProps.has("_selectedSsid") && this._selectedSsid === -1) {
673 // If we pick "Join other", select SSID input.
674 this._focusFormElement("ewt-textfield[name=ssid]");
675 }
676 else if (changedProps.has("_ssids")) {
677 // Form is shown when SSIDs are loaded/marked not supported
678 this._focusFormElement();
679 }
680 }
681 _focusFormElement(selector = "ewt-textfield, ewt-select") {
682 const formEl = this.shadowRoot.querySelector(selector);
683 if (formEl) {
684 formEl.updateComplete.then(() => setTimeout(() => formEl.focus(), 100));
685 }
686 }
687 async _initialize(justInstalled = false) {
688 if (this.port.readable === null || this.port.writable === null) {
689 this._state = "ERROR";
690 this._error =
691 "Serial port is not readable/writable. Close any other application using it and try again.";
692 return;
693 }
694 try {
695 this._manifest = await downloadManifest(this.manifestPath);
696 }
697 catch (err) {
698 this._state = "ERROR";
699 this._error = "Failed to download manifest";
700 return;
701 }
702 if (this._manifest.new_install_improv_wait_time === 0) {
703 this._client = null;
704 return;
705 }
706 const client = new ImprovSerial(this.port, this.logger);
707 client.addEventListener("state-changed", () => {
708 this.requestUpdate();
709 });
710 client.addEventListener("error-changed", () => this.requestUpdate());
711 try {
712 // If a device was just installed, give new firmware 10 seconds (overridable) to
713 // format the rest of the flash and do other stuff.
714 const timeout = !justInstalled
715 ? 1000
716 : this._manifest.new_install_improv_wait_time !== undefined
717 ? this._manifest.new_install_improv_wait_time * 1000
718 : 10000;
719 this._info = await client.initialize(timeout);
720 this._client = client;
721 client.addEventListener("disconnect", this._handleDisconnect);
722 }
723 catch (err) {
724 // Clear old value
725 this._info = undefined;
726 if (err instanceof PortNotReady) {
727 this._state = "ERROR";
728 this._error =
729 "Serial port is not ready. Close any other application using it and try again.";
730 }
731 else {
732 this._client = null; // not supported
733 this.logger.error("Improv initialization failed.", err);
734 }
735 }
736 }
737 _startInstall(erase) {
738 this._state = "INSTALL";
739 this._installErase = erase;
740 this._installConfirmed = false;
741 }
742 async _confirmInstall() {
743 this._installConfirmed = true;
744 this._installState = undefined;
745 if (this._client) {
746 await this._closeClientWithoutEvents(this._client);
747 }
748 this._client = undefined;
749 // Close port. ESPLoader likes opening it.
750 await this.port.close();
751 flash((state) => {
752 this._installState = state;
753 if (state.state === "finished" /* FlashStateType.FINISHED */) {
754 sleep(100)
755 // Flashing closes the port
756 .then(() => this.port.open({ baudRate: 115200 }))
757 .then(() => this._initialize(true))
758 .then(() => this.requestUpdate());
759 }
760 else if (state.state === "error" /* FlashStateType.ERROR */) {
761 sleep(100)
762 // Flashing closes the port
763 .then(() => this.port.open({ baudRate: 115200 }));
764 }
765 }, this.port, this.manifestPath, this._manifest, this._installErase);
766 }
767 async _doProvision() {
768 this._busy = true;
769 this._wasProvisioned =
770 this._client.state === ImprovSerialCurrentState.PROVISIONED;
771 const ssid = this._selectedSsid === -1
772 ? this.shadowRoot.querySelector("ewt-textfield[name=ssid]").value
773 : this._ssids[this._selectedSsid].name;
774 const password = this.shadowRoot.querySelector("ewt-textfield[name=password]").value;
775 try {
776 await this._client.provision(ssid, password);
777 }
778 catch (err) {
779 return;
780 }
781 finally {
782 this._busy = false;
783 this._provisionForce = false;
784 }
785 }
786 async _handleClose() {
787 if (this._client) {
788 await this._closeClientWithoutEvents(this._client);
789 }
790 fireEvent(this, "closed");
791 this.parentNode.removeChild(this);
792 }
793 /**
794 * Return if the device runs same firmware as manifest.
795 */
796 get _isSameFirmware() {
797 var _a;
798 return !this._info
799 ? false
800 : ((_a = this.overrides) === null || _a === void 0 ? void 0 : _a.checkSameFirmware)
801 ? this.overrides.checkSameFirmware(this._manifest, this._info)
802 : this._info.firmware === this._manifest.name;
803 }
804 /**
805 * Return if the device runs same firmware and version as manifest.
806 */
807 get _isSameVersion() {
808 return (this._isSameFirmware && this._info.version === this._manifest.version);
809 }
810 async _closeClientWithoutEvents(client) {
811 client.removeEventListener("disconnect", this._handleDisconnect);
812 await client.close();
813 }
814}
815EwtInstallDialog.styles = [
816 dialogStyles,
817 css `
818 :host {
819 --mdc-dialog-max-width: 390px;
820 }
821 ewt-icon-button {
822 position: absolute;
823 right: 4px;
824 top: 10px;
825 }
826 .table-row {
827 display: flex;
828 }
829 .table-row.last {
830 margin-bottom: 16px;
831 }
832 .table-row svg {
833 width: 20px;
834 margin-right: 8px;
835 }
836 ewt-textfield,
837 ewt-select {
838 display: block;
839 margin-top: 16px;
840 }
841 .dashboard-buttons {
842 margin: 0 0 -16px -8px;
843 }
844 .dashboard-buttons div {
845 display: block;
846 margin: 4px 0;
847 }
848 a.has-button {
849 text-decoration: none;
850 }
851 .error {
852 color: var(--improv-danger-color);
853 }
854 .danger {
855 --mdc-theme-primary: var(--improv-danger-color);
856 --mdc-theme-secondary: var(--improv-danger-color);
857 }
858 button.link {
859 background: none;
860 color: inherit;
861 border: none;
862 padding: 0;
863 font: inherit;
864 text-align: left;
865 text-decoration: underline;
866 cursor: pointer;
867 }
868 :host([state="LOGS"]) ewt-dialog {
869 --mdc-dialog-max-width: 90vw;
870 }
871 ewt-console {
872 width: calc(80vw - 48px);
873 height: 80vh;
874 }
875 `,
876];
877__decorate([
878 state()
879], EwtInstallDialog.prototype, "_client", void 0);
880__decorate([
881 state()
882], EwtInstallDialog.prototype, "_state", void 0);
883__decorate([
884 state()
885], EwtInstallDialog.prototype, "_installErase", void 0);
886__decorate([
887 state()
888], EwtInstallDialog.prototype, "_installConfirmed", void 0);
889__decorate([
890 state()
891], EwtInstallDialog.prototype, "_installState", void 0);
892__decorate([
893 state()
894], EwtInstallDialog.prototype, "_provisionForce", void 0);
895__decorate([
896 state()
897], EwtInstallDialog.prototype, "_error", void 0);
898__decorate([
899 state()
900], EwtInstallDialog.prototype, "_busy", void 0);
901__decorate([
902 state()
903], EwtInstallDialog.prototype, "_ssids", void 0);
904__decorate([
905 state()
906], EwtInstallDialog.prototype, "_selectedSsid", void 0);
907customElements.define("ewt-install-dialog", EwtInstallDialog);