v4.0.0 - Next Gen (#87)

This commit is contained in:
Paulus Schoutsen 2021-11-09 23:50:18 -08:00 committed by GitHub
parent 8386598933
commit 74187d9f44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 2152 additions and 526 deletions

View File

@ -13,10 +13,10 @@ Manifest definition:
```json ```json
{ {
"name": "ESPHome", "name": "ESPHome",
"version": "2021.10.3",
"builds": [ "builds": [
{ {
"chipFamily": "ESP32", "chipFamily": "ESP32",
"improv": true,
"parts": [ "parts": [
{ "path": "bootloader.bin", "offset": 4096 }, { "path": "bootloader.bin", "offset": 4096 },
{ "path": "partitions.bin", "offset": 32768 }, { "path": "partitions.bin", "offset": 32768 },
@ -46,17 +46,6 @@ Manifest definition:
} }
``` ```
Allows for optionally passing an attribute to trigger an erase before installation.
```html
<esp-web-install-button
manifest="firmware_esphome/manifest.json"
erase-first
></esp-web-install-button>
```
All attributes can also be set via properties (`manifest`, `eraseFirst`)
## Styling ## Styling
### Attributes ### Attributes
@ -67,14 +56,6 @@ The following attributes are automatically added to `<esp-web-install-button>` a
| -- | -- | | -- | -- |
| `install-supported` | Added if installing firmware is supported | `install-supported` | Added if installing firmware is supported
| `install-unsupported` | Added if installing firmware is not supported | `install-unsupported` | Added if installing firmware is not supported
| `active` | Added when flashing is active
You can add the following attributes or properties to change the UI elements:
| Attribute | Property | Description |
| -- | -- | -- |
| `show-log` | `showLog` | Show a log style view of the progress instead of a progress bar
| `hide-progress` | `hideProgress` | Hides all progress UI elements
### CSS custom properties (variables) ### CSS custom properties (variables)
@ -115,4 +96,4 @@ details | An optional extra field that is different [per state](https://github.c
## Development ## Development
Run `script/develop`. This starts a server. Open it on http://localhost:5000. Run `script/develop`. This starts a server. Open it on http://localhost:5001.

View File

@ -90,13 +90,22 @@
padding: 8px; padding: 8px;
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
} }
@media (prefers-color-scheme: dark) {
body {
background-color: #333;
color: #fff;
}
a {
color: #58a6ff;
}
}
</style> </style>
<script module> <script module>
import( import(
// In development we import locally. // In development we import locally.
window.location.hostname === "localhost" window.location.hostname === "localhost"
? "/dist/web/install-button.js" ? "/dist/web/install-button.js"
: "https://unpkg.com/esp-web-tools@3.6.0/dist/web/install-button.js?module" : "https://unpkg.com/esp-web-tools@4.0.0/dist/web/install-button.js?module"
); );
</script> </script>
</head> </head>
@ -112,8 +121,8 @@
</p> </p>
<p> <p>
To try it out and install To try it out and install
<a href="https://esphome.io">the ESPHome firmware</a>, connect an ESP to <a href="https://esphome.io">ESPHome</a> on an ESP, connect it to your
your computer and hit the button: computer and hit the button:
</p> </p>
<esp-web-install-button <esp-web-install-button
log-console log-console
@ -209,7 +218,7 @@
<pre> <pre>
&lt;script &lt;script
type="module" type="module"
src="https://unpkg.com/esp-web-tools@3.6.0/dist/web/install-button.js?module" src="https://unpkg.com/esp-web-tools@4.0.0/dist/web/install-button.js?module"
>&lt;/script> >&lt;/script>
&lt;esp-web-install-button &lt;esp-web-install-button
@ -243,17 +252,17 @@
ESP Web Tools manifest describe the firmware that you want to install. ESP Web Tools manifest describe the firmware that you want to install.
It allows specifying different builds for the different types of ESP It allows specifying different builds for the different types of ESP
devices. Current supported chip families are <code>ESP8266</code>, devices. Current supported chip families are <code>ESP8266</code>,
<code>ESP32</code>, <code>ESP32-C3</code> and <code>ESP32-S2</code>. The <code>ESP32</code>, <code>ESP32C3</code> and <code>ESP32S2</code>. The
correct build will be automatically selected based on the type of the correct build will be automatically selected based on the type of the
ESP device we detect via the serial port. ESP device we detect via the serial port.
</p> </p>
<pre> <pre>
{ {
"name": "ESPHome", "name": "ESPHome",
"version": "2021.11.0",
"builds": [ "builds": [
{ {
"chipFamily": "ESP32", "chipFamily": "ESP32",
"improv": true,
"parts": [ "parts": [
{ "path": "bootloader.bin", "offset": 4096 }, { "path": "bootloader.bin", "offset": 4096 },
{ "path": "partitions.bin", "offset": 32768 }, { "path": "partitions.bin", "offset": 32768 },
@ -276,7 +285,22 @@
where it should be installed. Part paths are resolved relative to the where it should be installed. Part paths are resolved relative to the
path of the manifest, but can also be URLs to other hosts. path of the manifest, but can also be URLs to other hosts.
</p> </p>
<h3 id="improv">Wi-Fi provisioning</h3>
<p> <p>
ESP Web Tools has support for the
<a href="https://www.improv-wifi.com/serial"
>Improv Wi-Fi serial standard</a
>. This is an open standard to allow configuring Wi-Fi via the serial
port.
</p>
<p>
If Improv is supported, a user will be guided to connect the device to
the network after installation. It also allows the user to connect
already installed devices and re-configure the wireless network
settings.
</p>
<p>TODO EXAMPLE VIDEO</p>
<!-- <p>
Each build also allows you to specify if it supports Each build also allows you to specify if it supports
<a href="https://www.improv-wifi.com">the Improv Wi-Fi standard</a>. If <a href="https://www.improv-wifi.com">the Improv Wi-Fi standard</a>. If
it does, the user will be offered to configure the Wi-Fi after it does, the user will be offered to configure the Wi-Fi after
@ -292,7 +316,7 @@
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen allowfullscreen
></iframe> ></iframe>
</div> </div> -->
<h3 id="customize">Customizing the look and feel</h3> <h3 id="customize">Customizing the look and feel</h3>
<p> <p>

742
package-lock.json generated
View File

@ -1,15 +1,23 @@
{ {
"name": "esp-web-tools", "name": "esp-web-tools",
"version": "3.6.0", "version": "4.0.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"version": "3.6.0", "name": "esp-web-tools",
"version": "4.0.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@material/mwc-button": "^0.25.3",
"@material/mwc-checkbox": "^0.25.3",
"@material/mwc-circular-progress": "^0.25.3",
"@material/mwc-dialog": "^0.25.3",
"@material/mwc-icon-button": "^0.25.3",
"@material/mwc-linear-progress": "^0.25.1", "@material/mwc-linear-progress": "^0.25.1",
"esp-web-flasher": "^3.2.0", "@material/mwc-textfield": "^0.25.3",
"esp-web-flasher": "^4.0.0",
"improv-wifi-serial-sdk": "^1.0.0",
"lit": "^2.0.0", "lit": "^2.0.0",
"tslib": "^2.3.1" "tslib": "^2.3.1"
}, },
@ -72,6 +80,69 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@material/button": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/button/-/button-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-DB0MAvdIGWKuFwlQ57hjv7ZuHIioT2mnG7RWtL7ZoCWoY45nCrsbJirmX5zZFipm9gIOJ3YnIkIrUyMVSrDX+g==",
"dependencies": {
"@material/density": "14.0.0-canary.261f2db59.0",
"@material/dom": "14.0.0-canary.261f2db59.0",
"@material/elevation": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/ripple": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/shape": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"@material/tokens": "14.0.0-canary.261f2db59.0",
"@material/touch-target": "14.0.0-canary.261f2db59.0",
"@material/typography": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/circular-progress": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-Gi6Ika8MEZQOT3Qei2NfTj+sRWxCDFjchPM7szNjIKgL2DyH03bHmodQFVcyBFiPWEcWMc/mqVYgGf/XJXs85w==",
"dependencies": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/progress-indicator": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/density": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/density/-/density-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-zOR5wISqPVr8KS/ERNC1jdRV9O832lzclyS9Ea20rDrWfuOiYsQ9bbIk12xWlxpgsn7r9fxQJyd1O2SURoHdRA==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/@material/dialog": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-NfQR0fmNS/y2iRAx5YeODLLywBAnSyZI/CL9GUq4NiNj+FeSxe+5bhG1p9NxHeGMjEVrl6fG5L9ql7lqtfQaYQ==",
"dependencies": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/button": "14.0.0-canary.261f2db59.0",
"@material/dom": "14.0.0-canary.261f2db59.0",
"@material/elevation": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/icon-button": "14.0.0-canary.261f2db59.0",
"@material/ripple": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/shape": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"@material/tokens": "14.0.0-canary.261f2db59.0",
"@material/touch-target": "14.0.0-canary.261f2db59.0",
"@material/typography": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/dom": { "node_modules/@material/dom": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/dom/-/dom-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/dom/-/dom-14.0.0-canary.261f2db59.0.tgz",
@ -81,6 +152,19 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@material/elevation": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-AqN/tsTGGyBzZ7CtoSMBY9bDYvCuUt98EUfiGjZGXcf4HgoHV3Cn/JSLrhru5Cq8Nx6HF6AmHh3dQCfNCQduew==",
"dependencies": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/feature-targeting": { "node_modules/@material/feature-targeting": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-14.0.0-canary.261f2db59.0.tgz",
@ -89,6 +173,49 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@material/floating-label": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-Cp0/LngkW6/uZWbEDTe3Ox143V4kYtxl9twiM3XLKd6a67JHCzneQWFzC0qSg90b3r5O+1zOkT3ZMF2Pbu2Vwg==",
"dependencies": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/dom": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"@material/typography": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/icon-button": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-9P6cjRqKtjE6ML+r5yz0ExU/f2KLdNabHQxmO6RpKd/FnjTyP1NcWqqj8dsvo/DZ7mOtT1MIThgkQDdiMqcYLg==",
"dependencies": {
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/density": "14.0.0-canary.261f2db59.0",
"@material/elevation": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/ripple": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"@material/touch-target": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/line-ripple": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-LlyiyxpHNlFt0PZ8Q2tvOPbjNcgm3L7tUebXsM7iGyoKXfj0HwyDI31S0KgtU3Vs5DIK4U4mnRWtoAxtBW6Jfg==",
"dependencies": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/linear-progress": { "node_modules/@material/linear-progress": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-14.0.0-canary.261f2db59.0.tgz",
@ -115,6 +242,94 @@
"tslib": "^2.0.1" "tslib": "^2.0.1"
} }
}, },
"node_modules/@material/mwc-button": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-button/-/mwc-button-0.25.3.tgz",
"integrity": "sha512-usHEKchj9hqetY7n0yebTz1Pk9Z+9W/sNZheFoSaiWQCv9XhtCdKkHH0MXTv8SpwxWuEKUf/XjtyvikGIcIn7w==",
"dependencies": {
"@material/mwc-icon": "^0.25.3",
"@material/mwc-ripple": "^0.25.3",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/mwc-checkbox": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-checkbox/-/mwc-checkbox-0.25.3.tgz",
"integrity": "sha512-PSh9IAgQK4XiDzBwgclheejkA4cbZ3K9V1JTTl/YVRDD/OLLM+Bh8tbnAg/1kGVlPWOUfDrYCcZ0gg472ca7gw==",
"dependencies": {
"@material/mwc-base": "^0.25.3",
"@material/mwc-ripple": "^0.25.3",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/mwc-circular-progress": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-circular-progress/-/mwc-circular-progress-0.25.3.tgz",
"integrity": "sha512-ajgSzfdRfq0/sZg0Z5W/ZpgZwD8Ioj59m5ScCPXXdkRoVHf7+8lsD/2Fh4095GfoYE4PWSkXYVlWsQCx+aJbcA==",
"dependencies": {
"@material/circular-progress": "=14.0.0-canary.261f2db59.0",
"@material/mwc-base": "^0.25.3",
"@material/theme": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/mwc-dialog": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-dialog/-/mwc-dialog-0.25.3.tgz",
"integrity": "sha512-UpxAYAzKXO1MW4ezpiYfEQgov08p0J8KDVKqKrMwg7lsZRkAtUMk4YJkM6qmWGqGPqd/cN++42PMPHAISJH3yA==",
"dependencies": {
"@material/dialog": "=14.0.0-canary.261f2db59.0",
"@material/dom": "=14.0.0-canary.261f2db59.0",
"@material/mwc-base": "^0.25.3",
"@material/mwc-button": "^0.25.3",
"blocking-elements": "^0.1.0",
"lit": "^2.0.0",
"tslib": "^2.0.1",
"wicg-inert": "^3.0.0"
}
},
"node_modules/@material/mwc-floating-label": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-floating-label/-/mwc-floating-label-0.25.3.tgz",
"integrity": "sha512-3uFMi8Y680P0nzP5zih4YuOZJLl/C6Ux9G810Unwo44zblG/ckgJlFiM+T+oR+OH5KM8LbfNlV0ypo7FT5zYJA==",
"dependencies": {
"@material/floating-label": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/mwc-icon": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-icon/-/mwc-icon-0.25.3.tgz",
"integrity": "sha512-36076AWZIRSr8qYOLjuDDkxej/HA0XAosrj7TS1ZeLlUBnLUtbDtvc1S7KSa0hqez7ouzOqGaWK24yoNnTa2OA==",
"dependencies": {
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/mwc-icon-button": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-icon-button/-/mwc-icon-button-0.25.3.tgz",
"integrity": "sha512-FexkMpK3ZSHh7NF+PIqvVhvAbBOgFDYPck/lqnxIDC3VGJ0rjD/1MqevDy2fY6IcHGlc8Ai7VuYbdQ6Cvw8WcQ==",
"dependencies": {
"@material/mwc-ripple": "^0.25.3",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/mwc-line-ripple": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-line-ripple/-/mwc-line-ripple-0.25.3.tgz",
"integrity": "sha512-ANJzSyumb+shBVTIhqF1+YByPU/EpFXxI9CS26qThFqlUDpYXg5xcoZpkMSmZv3Wv/loF1rs2mJfFWOcC6nFnw==",
"dependencies": {
"@material/line-ripple": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/mwc-linear-progress": { "node_modules/@material/mwc-linear-progress": {
"version": "0.25.3", "version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-linear-progress/-/mwc-linear-progress-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@material/mwc-linear-progress/-/mwc-linear-progress-0.25.3.tgz",
@ -127,6 +342,59 @@
"tslib": "^2.0.1" "tslib": "^2.0.1"
} }
}, },
"node_modules/@material/mwc-notched-outline": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-notched-outline/-/mwc-notched-outline-0.25.3.tgz",
"integrity": "sha512-8jvU8GD0Pke+pfTQ0PdXpZmkU3XIHhMVY6AHM/2IQrXHkVZmAm9kbwL7ne3Ao+6f5n+DeXDGd+SG9U6ZZjD7gw==",
"dependencies": {
"@material/mwc-base": "^0.25.3",
"@material/notched-outline": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/mwc-ripple": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-ripple/-/mwc-ripple-0.25.3.tgz",
"integrity": "sha512-G/gt/csxgME6/sAku3GiuB0O2LLvoPWsRTLq/9iABpaGLJjqaKHvNg/IVzNDdF3YZT7EORgR9cBWWl7umA4i4Q==",
"dependencies": {
"@material/dom": "=14.0.0-canary.261f2db59.0",
"@material/mwc-base": "^0.25.3",
"@material/ripple": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/mwc-textfield": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-textfield/-/mwc-textfield-0.25.3.tgz",
"integrity": "sha512-stpZ8sEyo2Mb9fG2XCoTc1Kom8oRXZiVI5rU88GtfcBU7nH0em8S4grq9X1mVfUG6Cfi1G/T+avCSIhzbYtr0w==",
"dependencies": {
"@material/floating-label": "=14.0.0-canary.261f2db59.0",
"@material/line-ripple": "=14.0.0-canary.261f2db59.0",
"@material/mwc-base": "^0.25.3",
"@material/mwc-floating-label": "^0.25.3",
"@material/mwc-line-ripple": "^0.25.3",
"@material/mwc-notched-outline": "^0.25.3",
"@material/textfield": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"node_modules/@material/notched-outline": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-gtn+IKAiX2rbfbX3a9aDlfUoKCEYrlAPOZifKXUaZ4UJYMNLzZuAqy7l5Ds30emtqUE22mySTEWqhzK6dePKsA==",
"dependencies": {
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/floating-label": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/shape": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/progress-indicator": { "node_modules/@material/progress-indicator": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-14.0.0-canary.261f2db59.0.tgz",
@ -135,6 +403,20 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@material/ripple": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-3FLCLj8X7KrFfuYBHJg1b7Odb3V/AW7fxk3m1i1zhDnygKmlQ/abVucH1s2qbX3Y+JIiq+5/C5407h9BFtOf+A==",
"dependencies": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/dom": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/rtl": { "node_modules/@material/rtl": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-14.0.0-canary.261f2db59.0.tgz",
@ -144,6 +426,38 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@material/shape": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/shape/-/shape-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-VjcQltd1uF9ugvLExMy00SMISjy/370o8lsZlb1T+xHyhXHL3UxeuWYLW5Amq6mbx65+c9Df9WmlXXOdebpEkw==",
"dependencies": {
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/textfield": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-KBPgpvvVFBfLx9nc6+wWOS2hJ40JVwh5KBjMoYbiOEFLf0O7SgCAVREHaFAXrPsC8AeTyUipx6TReONIGfMCPQ==",
"dependencies": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/density": "14.0.0-canary.261f2db59.0",
"@material/dom": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/floating-label": "14.0.0-canary.261f2db59.0",
"@material/line-ripple": "14.0.0-canary.261f2db59.0",
"@material/notched-outline": "14.0.0-canary.261f2db59.0",
"@material/ripple": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/shape": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"@material/typography": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/theme": { "node_modules/@material/theme": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/theme/-/theme-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/theme/-/theme-14.0.0-canary.261f2db59.0.tgz",
@ -153,6 +467,35 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@material/tokens": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-mgar9gsLv00HTvXIDvNR1vEEXpfKgeWhVTO8a7aWofSNyENNOVc5ImJwBgCAMb5SgLHBi6w8/c1tPzjOewBfCA==",
"dependencies": {
"@material/elevation": "14.0.0-canary.261f2db59.0"
}
},
"node_modules/@material/touch-target": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-xA6TTHN7aOTXg/+c6mQJlogzTD+Sp8WPC5TK8RBXbQxEykGXGW15p+H9pG+rX/gzD5iehnHRBrDUFmAGoskhcQ==",
"dependencies": {
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@material/typography": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/typography/-/typography-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-WOCdcNkD5KBRAwICcRqWBRG3cDkyrwK5USTNmG0oxnwnZAN7daOpPTdLppVAhadE7faj8d67ON+V9pH7+T62FQ==",
"dependencies": {
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"node_modules/@rollup/plugin-json": { "node_modules/@rollup/plugin-json": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz",
@ -337,6 +680,11 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true "dev": true
}, },
"node_modules/blocking-elements": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/blocking-elements/-/blocking-elements-0.1.1.tgz",
"integrity": "sha512-/SLWbEzMoVIMZACCyhD/4Ya2M1PWP1qMKuiymowPcI+PdWDARqeARBjhj73kbUBCxEmTZCUu5TAqxtwUO9C1Ig=="
},
"node_modules/boxen": { "node_modules/boxen": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
@ -639,9 +987,9 @@
} }
}, },
"node_modules/esp-web-flasher": { "node_modules/esp-web-flasher": {
"version": "3.2.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/esp-web-flasher/-/esp-web-flasher-3.2.0.tgz", "resolved": "https://registry.npmjs.org/esp-web-flasher/-/esp-web-flasher-4.0.0.tgz",
"integrity": "sha512-jcJtWb5QuENWzeasfGYcJP/MV+XmRQelNRoOVCAKXcBJFh9h9NnfPXJtpoG+RsIMqb7hDdutomz/bBoBUH6urw==", "integrity": "sha512-7d23iEkEjvrYkywLZtvg69GAitRJVE73dN6nmyWNmTvCe55b0UTzndLJtTHANbAiNzpgmJ7/kYnt202A7BD75A==",
"dependencies": { "dependencies": {
"pako": "^2.0.3", "pako": "^2.0.3",
"tslib": "^2.2.0" "tslib": "^2.2.0"
@ -760,6 +1108,19 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/improv-wifi-serial-sdk": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/improv-wifi-serial-sdk/-/improv-wifi-serial-sdk-1.0.0.tgz",
"integrity": "sha512-R3NM7Ry9DjTyT5B6iIIZjW5LMia64PwLEJnue5lfYlmqHyJuNMxkWrGomqG7AxQLLCul7CPN1qs52nkJglqYsg==",
"dependencies": {
"@material/mwc-button": "^0.25.3",
"@material/mwc-circular-progress": "^0.25.3",
"@material/mwc-dialog": "^0.25.3",
"@material/mwc-textfield": "^0.25.3",
"lit": "^2.0.0",
"tslib": "^2.3.1"
}
},
"node_modules/ini": { "node_modules/ini": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
@ -1469,6 +1830,11 @@
"which": "bin/which" "which": "bin/which"
} }
}, },
"node_modules/wicg-inert": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.1.tgz",
"integrity": "sha512-PhBaNh8ur9Xm4Ggy4umelwNIP6pPP1bv3EaWaKqfb/QNme2rdLjm7wIInvV4WhxVHhzA4Spgw9qNSqWtB/ca2A=="
},
"node_modules/widest-line": { "node_modules/widest-line": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
@ -1586,6 +1952,69 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"@material/button": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/button/-/button-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-DB0MAvdIGWKuFwlQ57hjv7ZuHIioT2mnG7RWtL7ZoCWoY45nCrsbJirmX5zZFipm9gIOJ3YnIkIrUyMVSrDX+g==",
"requires": {
"@material/density": "14.0.0-canary.261f2db59.0",
"@material/dom": "14.0.0-canary.261f2db59.0",
"@material/elevation": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/ripple": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/shape": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"@material/tokens": "14.0.0-canary.261f2db59.0",
"@material/touch-target": "14.0.0-canary.261f2db59.0",
"@material/typography": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/circular-progress": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-Gi6Ika8MEZQOT3Qei2NfTj+sRWxCDFjchPM7szNjIKgL2DyH03bHmodQFVcyBFiPWEcWMc/mqVYgGf/XJXs85w==",
"requires": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/progress-indicator": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/density": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/density/-/density-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-zOR5wISqPVr8KS/ERNC1jdRV9O832lzclyS9Ea20rDrWfuOiYsQ9bbIk12xWlxpgsn7r9fxQJyd1O2SURoHdRA==",
"requires": {
"tslib": "^2.1.0"
}
},
"@material/dialog": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-NfQR0fmNS/y2iRAx5YeODLLywBAnSyZI/CL9GUq4NiNj+FeSxe+5bhG1p9NxHeGMjEVrl6fG5L9ql7lqtfQaYQ==",
"requires": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/button": "14.0.0-canary.261f2db59.0",
"@material/dom": "14.0.0-canary.261f2db59.0",
"@material/elevation": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/icon-button": "14.0.0-canary.261f2db59.0",
"@material/ripple": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/shape": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"@material/tokens": "14.0.0-canary.261f2db59.0",
"@material/touch-target": "14.0.0-canary.261f2db59.0",
"@material/typography": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/dom": { "@material/dom": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/dom/-/dom-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/dom/-/dom-14.0.0-canary.261f2db59.0.tgz",
@ -1595,6 +2024,19 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"@material/elevation": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-AqN/tsTGGyBzZ7CtoSMBY9bDYvCuUt98EUfiGjZGXcf4HgoHV3Cn/JSLrhru5Cq8Nx6HF6AmHh3dQCfNCQduew==",
"requires": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/feature-targeting": { "@material/feature-targeting": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-14.0.0-canary.261f2db59.0.tgz",
@ -1603,6 +2045,49 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"@material/floating-label": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-Cp0/LngkW6/uZWbEDTe3Ox143V4kYtxl9twiM3XLKd6a67JHCzneQWFzC0qSg90b3r5O+1zOkT3ZMF2Pbu2Vwg==",
"requires": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/dom": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"@material/typography": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/icon-button": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-9P6cjRqKtjE6ML+r5yz0ExU/f2KLdNabHQxmO6RpKd/FnjTyP1NcWqqj8dsvo/DZ7mOtT1MIThgkQDdiMqcYLg==",
"requires": {
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/density": "14.0.0-canary.261f2db59.0",
"@material/elevation": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/ripple": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"@material/touch-target": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/line-ripple": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-LlyiyxpHNlFt0PZ8Q2tvOPbjNcgm3L7tUebXsM7iGyoKXfj0HwyDI31S0KgtU3Vs5DIK4U4mnRWtoAxtBW6Jfg==",
"requires": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/linear-progress": { "@material/linear-progress": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-14.0.0-canary.261f2db59.0.tgz",
@ -1629,6 +2114,94 @@
"tslib": "^2.0.1" "tslib": "^2.0.1"
} }
}, },
"@material/mwc-button": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-button/-/mwc-button-0.25.3.tgz",
"integrity": "sha512-usHEKchj9hqetY7n0yebTz1Pk9Z+9W/sNZheFoSaiWQCv9XhtCdKkHH0MXTv8SpwxWuEKUf/XjtyvikGIcIn7w==",
"requires": {
"@material/mwc-icon": "^0.25.3",
"@material/mwc-ripple": "^0.25.3",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"@material/mwc-checkbox": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-checkbox/-/mwc-checkbox-0.25.3.tgz",
"integrity": "sha512-PSh9IAgQK4XiDzBwgclheejkA4cbZ3K9V1JTTl/YVRDD/OLLM+Bh8tbnAg/1kGVlPWOUfDrYCcZ0gg472ca7gw==",
"requires": {
"@material/mwc-base": "^0.25.3",
"@material/mwc-ripple": "^0.25.3",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"@material/mwc-circular-progress": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-circular-progress/-/mwc-circular-progress-0.25.3.tgz",
"integrity": "sha512-ajgSzfdRfq0/sZg0Z5W/ZpgZwD8Ioj59m5ScCPXXdkRoVHf7+8lsD/2Fh4095GfoYE4PWSkXYVlWsQCx+aJbcA==",
"requires": {
"@material/circular-progress": "=14.0.0-canary.261f2db59.0",
"@material/mwc-base": "^0.25.3",
"@material/theme": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"@material/mwc-dialog": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-dialog/-/mwc-dialog-0.25.3.tgz",
"integrity": "sha512-UpxAYAzKXO1MW4ezpiYfEQgov08p0J8KDVKqKrMwg7lsZRkAtUMk4YJkM6qmWGqGPqd/cN++42PMPHAISJH3yA==",
"requires": {
"@material/dialog": "=14.0.0-canary.261f2db59.0",
"@material/dom": "=14.0.0-canary.261f2db59.0",
"@material/mwc-base": "^0.25.3",
"@material/mwc-button": "^0.25.3",
"blocking-elements": "^0.1.0",
"lit": "^2.0.0",
"tslib": "^2.0.1",
"wicg-inert": "^3.0.0"
}
},
"@material/mwc-floating-label": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-floating-label/-/mwc-floating-label-0.25.3.tgz",
"integrity": "sha512-3uFMi8Y680P0nzP5zih4YuOZJLl/C6Ux9G810Unwo44zblG/ckgJlFiM+T+oR+OH5KM8LbfNlV0ypo7FT5zYJA==",
"requires": {
"@material/floating-label": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"@material/mwc-icon": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-icon/-/mwc-icon-0.25.3.tgz",
"integrity": "sha512-36076AWZIRSr8qYOLjuDDkxej/HA0XAosrj7TS1ZeLlUBnLUtbDtvc1S7KSa0hqez7ouzOqGaWK24yoNnTa2OA==",
"requires": {
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"@material/mwc-icon-button": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-icon-button/-/mwc-icon-button-0.25.3.tgz",
"integrity": "sha512-FexkMpK3ZSHh7NF+PIqvVhvAbBOgFDYPck/lqnxIDC3VGJ0rjD/1MqevDy2fY6IcHGlc8Ai7VuYbdQ6Cvw8WcQ==",
"requires": {
"@material/mwc-ripple": "^0.25.3",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"@material/mwc-line-ripple": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-line-ripple/-/mwc-line-ripple-0.25.3.tgz",
"integrity": "sha512-ANJzSyumb+shBVTIhqF1+YByPU/EpFXxI9CS26qThFqlUDpYXg5xcoZpkMSmZv3Wv/loF1rs2mJfFWOcC6nFnw==",
"requires": {
"@material/line-ripple": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"@material/mwc-linear-progress": { "@material/mwc-linear-progress": {
"version": "0.25.3", "version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-linear-progress/-/mwc-linear-progress-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@material/mwc-linear-progress/-/mwc-linear-progress-0.25.3.tgz",
@ -1641,6 +2214,59 @@
"tslib": "^2.0.1" "tslib": "^2.0.1"
} }
}, },
"@material/mwc-notched-outline": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-notched-outline/-/mwc-notched-outline-0.25.3.tgz",
"integrity": "sha512-8jvU8GD0Pke+pfTQ0PdXpZmkU3XIHhMVY6AHM/2IQrXHkVZmAm9kbwL7ne3Ao+6f5n+DeXDGd+SG9U6ZZjD7gw==",
"requires": {
"@material/mwc-base": "^0.25.3",
"@material/notched-outline": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"@material/mwc-ripple": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-ripple/-/mwc-ripple-0.25.3.tgz",
"integrity": "sha512-G/gt/csxgME6/sAku3GiuB0O2LLvoPWsRTLq/9iABpaGLJjqaKHvNg/IVzNDdF3YZT7EORgR9cBWWl7umA4i4Q==",
"requires": {
"@material/dom": "=14.0.0-canary.261f2db59.0",
"@material/mwc-base": "^0.25.3",
"@material/ripple": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"@material/mwc-textfield": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@material/mwc-textfield/-/mwc-textfield-0.25.3.tgz",
"integrity": "sha512-stpZ8sEyo2Mb9fG2XCoTc1Kom8oRXZiVI5rU88GtfcBU7nH0em8S4grq9X1mVfUG6Cfi1G/T+avCSIhzbYtr0w==",
"requires": {
"@material/floating-label": "=14.0.0-canary.261f2db59.0",
"@material/line-ripple": "=14.0.0-canary.261f2db59.0",
"@material/mwc-base": "^0.25.3",
"@material/mwc-floating-label": "^0.25.3",
"@material/mwc-line-ripple": "^0.25.3",
"@material/mwc-notched-outline": "^0.25.3",
"@material/textfield": "=14.0.0-canary.261f2db59.0",
"lit": "^2.0.0",
"tslib": "^2.0.1"
}
},
"@material/notched-outline": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-gtn+IKAiX2rbfbX3a9aDlfUoKCEYrlAPOZifKXUaZ4UJYMNLzZuAqy7l5Ds30emtqUE22mySTEWqhzK6dePKsA==",
"requires": {
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/floating-label": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/shape": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/progress-indicator": { "@material/progress-indicator": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-14.0.0-canary.261f2db59.0.tgz",
@ -1649,6 +2275,20 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"@material/ripple": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-3FLCLj8X7KrFfuYBHJg1b7Odb3V/AW7fxk3m1i1zhDnygKmlQ/abVucH1s2qbX3Y+JIiq+5/C5407h9BFtOf+A==",
"requires": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/dom": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/rtl": { "@material/rtl": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-14.0.0-canary.261f2db59.0.tgz",
@ -1658,6 +2298,38 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"@material/shape": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/shape/-/shape-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-VjcQltd1uF9ugvLExMy00SMISjy/370o8lsZlb1T+xHyhXHL3UxeuWYLW5Amq6mbx65+c9Df9WmlXXOdebpEkw==",
"requires": {
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/textfield": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-KBPgpvvVFBfLx9nc6+wWOS2hJ40JVwh5KBjMoYbiOEFLf0O7SgCAVREHaFAXrPsC8AeTyUipx6TReONIGfMCPQ==",
"requires": {
"@material/animation": "14.0.0-canary.261f2db59.0",
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/density": "14.0.0-canary.261f2db59.0",
"@material/dom": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/floating-label": "14.0.0-canary.261f2db59.0",
"@material/line-ripple": "14.0.0-canary.261f2db59.0",
"@material/notched-outline": "14.0.0-canary.261f2db59.0",
"@material/ripple": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"@material/shape": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"@material/typography": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/theme": { "@material/theme": {
"version": "14.0.0-canary.261f2db59.0", "version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/theme/-/theme-14.0.0-canary.261f2db59.0.tgz", "resolved": "https://registry.npmjs.org/@material/theme/-/theme-14.0.0-canary.261f2db59.0.tgz",
@ -1667,6 +2339,35 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"@material/tokens": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-mgar9gsLv00HTvXIDvNR1vEEXpfKgeWhVTO8a7aWofSNyENNOVc5ImJwBgCAMb5SgLHBi6w8/c1tPzjOewBfCA==",
"requires": {
"@material/elevation": "14.0.0-canary.261f2db59.0"
}
},
"@material/touch-target": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-xA6TTHN7aOTXg/+c6mQJlogzTD+Sp8WPC5TK8RBXbQxEykGXGW15p+H9pG+rX/gzD5iehnHRBrDUFmAGoskhcQ==",
"requires": {
"@material/base": "14.0.0-canary.261f2db59.0",
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/rtl": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@material/typography": {
"version": "14.0.0-canary.261f2db59.0",
"resolved": "https://registry.npmjs.org/@material/typography/-/typography-14.0.0-canary.261f2db59.0.tgz",
"integrity": "sha512-WOCdcNkD5KBRAwICcRqWBRG3cDkyrwK5USTNmG0oxnwnZAN7daOpPTdLppVAhadE7faj8d67ON+V9pH7+T62FQ==",
"requires": {
"@material/feature-targeting": "14.0.0-canary.261f2db59.0",
"@material/theme": "14.0.0-canary.261f2db59.0",
"tslib": "^2.1.0"
}
},
"@rollup/plugin-json": { "@rollup/plugin-json": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz",
@ -1824,6 +2525,11 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true "dev": true
}, },
"blocking-elements": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/blocking-elements/-/blocking-elements-0.1.1.tgz",
"integrity": "sha512-/SLWbEzMoVIMZACCyhD/4Ya2M1PWP1qMKuiymowPcI+PdWDARqeARBjhj73kbUBCxEmTZCUu5TAqxtwUO9C1Ig=="
},
"boxen": { "boxen": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
@ -2061,9 +2767,9 @@
"dev": true "dev": true
}, },
"esp-web-flasher": { "esp-web-flasher": {
"version": "3.2.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/esp-web-flasher/-/esp-web-flasher-3.2.0.tgz", "resolved": "https://registry.npmjs.org/esp-web-flasher/-/esp-web-flasher-4.0.0.tgz",
"integrity": "sha512-jcJtWb5QuENWzeasfGYcJP/MV+XmRQelNRoOVCAKXcBJFh9h9NnfPXJtpoG+RsIMqb7hDdutomz/bBoBUH6urw==", "integrity": "sha512-7d23iEkEjvrYkywLZtvg69GAitRJVE73dN6nmyWNmTvCe55b0UTzndLJtTHANbAiNzpgmJ7/kYnt202A7BD75A==",
"requires": { "requires": {
"pako": "^2.0.3", "pako": "^2.0.3",
"tslib": "^2.2.0" "tslib": "^2.2.0"
@ -2163,6 +2869,19 @@
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true "dev": true
}, },
"improv-wifi-serial-sdk": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/improv-wifi-serial-sdk/-/improv-wifi-serial-sdk-1.0.0.tgz",
"integrity": "sha512-R3NM7Ry9DjTyT5B6iIIZjW5LMia64PwLEJnue5lfYlmqHyJuNMxkWrGomqG7AxQLLCul7CPN1qs52nkJglqYsg==",
"requires": {
"@material/mwc-button": "^0.25.3",
"@material/mwc-circular-progress": "^0.25.3",
"@material/mwc-dialog": "^0.25.3",
"@material/mwc-textfield": "^0.25.3",
"lit": "^2.0.0",
"tslib": "^2.3.1"
}
},
"ini": { "ini": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
@ -2747,6 +3466,11 @@
"isexe": "^2.0.0" "isexe": "^2.0.0"
} }
}, },
"wicg-inert": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.1.tgz",
"integrity": "sha512-PhBaNh8ur9Xm4Ggy4umelwNIP6pPP1bv3EaWaKqfb/QNme2rdLjm7wIInvV4WhxVHhzA4Spgw9qNSqWtB/ca2A=="
},
"widest-line": { "widest-line": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "esp-web-tools", "name": "esp-web-tools",
"version": "3.6.0", "version": "4.0.0",
"description": "Web tools for ESP devices", "description": "Web tools for ESP devices",
"main": "dist/install-button.js", "main": "dist/install-button.js",
"repository": "https://github.com/esphome/web", "repository": "https://github.com/esphome/web",
@ -21,8 +21,15 @@
"typescript": "^4.3.2" "typescript": "^4.3.2"
}, },
"dependencies": { "dependencies": {
"@material/mwc-button": "^0.25.3",
"@material/mwc-checkbox": "^0.25.3",
"@material/mwc-circular-progress": "^0.25.3",
"@material/mwc-dialog": "^0.25.3",
"@material/mwc-icon-button": "^0.25.3",
"@material/mwc-linear-progress": "^0.25.1", "@material/mwc-linear-progress": "^0.25.1",
"esp-web-flasher": "^3.2.0", "@material/mwc-textfield": "^0.25.3",
"esp-web-flasher": "^4.0.0",
"improv-wifi-serial-sdk": "^1.0.0",
"lit": "^2.0.0", "lit": "^2.0.0",
"tslib": "^2.3.1" "tslib": "^2.3.1"
} }

View File

@ -11,7 +11,7 @@ trap "kill 0" EXIT
# Run tsc once as rollup expects those files # Run tsc once as rollup expects those files
tsc || true tsc || true
npm exec -- serve & npm exec -- serve -p 5001 &
npm exec -- tsc --watch & npm exec -- tsc --watch &
npm exec -- rollup -c --watch & npm exec -- rollup -c --watch &
wait wait

View File

@ -0,0 +1,14 @@
import { ButtonBase } from "@material/mwc-button/mwc-button-base";
import { styles } from "@material/mwc-button/styles.css";
declare global {
interface HTMLElementTagNameMap {
"ewt-button": EwtButton;
}
}
export class EwtButton extends ButtonBase {
static override styles = [styles];
}
customElements.define("ewt-button", EwtButton);

View File

@ -0,0 +1,14 @@
import { CircularProgressBase } from "@material/mwc-circular-progress/mwc-circular-progress-base";
import { styles } from "@material/mwc-circular-progress/mwc-circular-progress.css";
declare global {
interface HTMLElementTagNameMap {
"ewt-circular-progress": EwtCircularProgress;
}
}
export class EwtCircularProgress extends CircularProgressBase {
static override styles = [styles];
}
customElements.define("ewt-circular-progress", EwtCircularProgress);

View File

@ -0,0 +1,227 @@
import { ColoredConsole } from "../util/console-color";
import { sleep } from "../util/sleep";
import { LineBreakTransformer } from "../util/line-break-transformer";
import { Logger } from "../const";
export class EwtConsole extends HTMLElement {
public port!: SerialPort;
public logger!: Logger;
private _console?: ColoredConsole;
private _cancelConnection?: () => Promise<void>;
public connectedCallback() {
if (this._console) {
return;
}
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `
<style>
:host, input {
background-color: #1c1c1c;
color: #ddd;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier,
monospace;
line-height: 1.45;
}
.log {
box-sizing: border-box;
height: calc(100% - 28px);
font-size: 12px;
padding: 16px;
overflow: auto;
white-space: pre-wrap;
overflow-wrap: break-word;
}
.log-bold {
font-weight: bold;
}
.log-italic {
font-style: italic;
}
.log-underline {
text-decoration: underline;
}
.log-strikethrough {
text-decoration: line-through;
}
.log-underline.log-strikethrough {
text-decoration: underline line-through;
}
.log-secret {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.log-secret-redacted {
opacity: 0;
width: 1px;
font-size: 1px;
}
.log-fg-black {
color: rgb(128, 128, 128);
}
.log-fg-red {
color: rgb(255, 0, 0);
}
.log-fg-green {
color: rgb(0, 255, 0);
}
.log-fg-yellow {
color: rgb(255, 255, 0);
}
.log-fg-blue {
color: rgb(0, 0, 255);
}
.log-fg-magenta {
color: rgb(255, 0, 255);
}
.log-fg-cyan {
color: rgb(0, 255, 255);
}
.log-fg-white {
color: rgb(187, 187, 187);
}
.log-bg-black {
background-color: rgb(0, 0, 0);
}
.log-bg-red {
background-color: rgb(255, 0, 0);
}
.log-bg-green {
background-color: rgb(0, 255, 0);
}
.log-bg-yellow {
background-color: rgb(255, 255, 0);
}
.log-bg-blue {
background-color: rgb(0, 0, 255);
}
.log-bg-magenta {
background-color: rgb(255, 0, 255);
}
.log-bg-cyan {
background-color: rgb(0, 255, 255);
}
.log-bg-white {
background-color: rgb(255, 255, 255);
}
form {
display: flex;
align-items: center;
padding: 0 8px 0 16px;
}
input {
flex: 1;
padding: 4px;
margin: 0 8px;
border: 0;
outline: none;
}
</style>
<div class="log"></div>
<form>
>
<input autofocus>
<button type="button">Send</button>
</form>
`;
this._console = new ColoredConsole(this.shadowRoot!.querySelector("div")!);
const input = this.shadowRoot!.querySelector("input")!;
this.addEventListener("click", () => input.focus());
input.addEventListener("keydown", (ev) => {
if (ev.key === "Enter") {
ev.preventDefault();
ev.stopPropagation();
this._sendCommand();
}
});
const abortController = new AbortController();
const connection = this._connect(abortController.signal);
this._cancelConnection = () => {
abortController.abort();
return connection;
};
}
private async _connect(abortSignal: AbortSignal) {
this.logger.debug("Starting console read loop");
try {
await this.port
.readable!.pipeThrough(new TextDecoderStream(), {
signal: abortSignal,
})
.pipeThrough(new TransformStream(new LineBreakTransformer()))
.pipeTo(
new WritableStream({
write: (chunk) => {
this._console!.addLine(chunk);
},
})
);
if (!abortSignal.aborted) {
this._console!.addLine("");
this._console!.addLine("");
this._console!.addLine("Terminal disconnected");
}
} catch (e) {
this._console!.addLine("");
this._console!.addLine("");
this._console!.addLine(`Terminal disconnected: ${e}`);
} finally {
await sleep(100);
this.logger.debug("Finished console read loop");
}
}
private async _sendCommand() {
const input = this.shadowRoot!.querySelector("input")!;
const command = input.value;
const encoder = new TextEncoder();
const writer = this.port.writable!.getWriter();
await writer.write(encoder.encode(command));
this._console!.addLine(`> ${command}\n`);
input.value = "";
input.focus();
try {
writer.releaseLock();
} catch (err) {
console.error("Ignoring release lock error", err);
}
}
public async disconnect() {
if (this._cancelConnection) {
await this._cancelConnection();
this._cancelConnection = undefined;
}
}
public async reset() {
this.logger.debug("Triggering reset.");
await this.port.setSignals({
dataTerminalReady: false,
requestToSend: true,
});
await this.port.setSignals({
dataTerminalReady: false,
requestToSend: false,
});
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
customElements.define("ewt-console", EwtConsole);
declare global {
interface HTMLElementTagNameMap {
"ewt-console": EwtConsole;
}
}

View File

@ -0,0 +1,22 @@
import { DialogBase } from "@material/mwc-dialog/mwc-dialog-base";
import { styles } from "@material/mwc-dialog/mwc-dialog.css";
import { css } from "lit";
declare global {
interface HTMLElementTagNameMap {
"ewt-dialog": EwtDialog;
}
}
export class EwtDialog extends DialogBase {
static override styles = [
styles,
css`
.mdc-dialog__title {
padding-right: 52px;
}
`,
];
}
customElements.define("ewt-dialog", EwtDialog);

View File

@ -0,0 +1,14 @@
import { IconButtonBase } from "@material/mwc-icon-button/mwc-icon-button-base";
import { styles } from "@material/mwc-icon-button/mwc-icon-button.css";
declare global {
interface HTMLElementTagNameMap {
"ewt-icon-button": EwtIconButton;
}
}
export class EwtIconButton extends IconButtonBase {
static override styles = [styles];
}
customElements.define("ewt-icon-button", EwtIconButton);

View File

@ -0,0 +1,14 @@
import { TextFieldBase } from "@material/mwc-textfield/mwc-textfield-base";
import { styles } from "@material/mwc-textfield/mwc-textfield.css";
declare global {
interface HTMLElementTagNameMap {
"ewt-textfield": EwtTextfield;
}
}
export class EwtTextfield extends TextFieldBase {
static override styles = [styles];
}
customElements.define("ewt-textfield", EwtTextfield);

30
src/connect.ts Normal file
View File

@ -0,0 +1,30 @@
import type { InstallButton } from "./install-button.js";
import "./install-dialog.js";
export const connect = async (button: InstallButton) => {
let port: SerialPort | undefined;
try {
port = await navigator.serial.requestPort();
} catch (err) {
console.error("User cancelled request", err);
return;
}
if (!port) {
return;
}
await port.open({ baudRate: 115200 });
const el = document.createElement("ewt-install-dialog");
el.port = port;
el.manifestPath = button.getAttribute("manifest")!;
el.addEventListener(
"closed",
() => {
port!.close();
},
{ once: true }
);
document.body.appendChild(el);
};

View File

@ -1,6 +1,11 @@
export interface Logger {
log(msg: string, ...args: any[]): void;
error(msg: string, ...args: any[]): void;
debug(msg: string, ...args: any[]): void;
}
export interface Build { export interface Build {
chipFamily: "ESP32" | "ESP8266" | "ESP32-S2" | "ESP32-C3"; chipFamily: "ESP32" | "ESP8266" | "ESP32-S2" | "ESP32-C3";
improv: boolean;
parts: { parts: {
path: string; path: string;
offset: number; offset: number;
@ -9,11 +14,12 @@ export interface Build {
export interface Manifest { export interface Manifest {
name: string; name: string;
version: string;
builds: Build[]; builds: Build[];
} }
export interface BaseFlashState { export interface BaseFlashState {
state: State; state: FlashStateType;
message: string; message: string;
manifest?: Manifest; manifest?: Manifest;
build?: Build; build?: Build;
@ -21,36 +27,36 @@ export interface BaseFlashState {
} }
export interface InitializingState extends BaseFlashState { export interface InitializingState extends BaseFlashState {
state: State.INITIALIZING; state: FlashStateType.INITIALIZING;
details: { done: boolean }; details: { done: boolean };
} }
export interface ManifestState extends BaseFlashState { export interface ManifestState extends BaseFlashState {
state: State.MANIFEST; state: FlashStateType.MANIFEST;
details: { done: boolean }; details: { done: boolean };
} }
export interface PreparingState extends BaseFlashState { export interface PreparingState extends BaseFlashState {
state: State.PREPARING; state: FlashStateType.PREPARING;
details: { done: boolean }; details: { done: boolean };
} }
export interface ErasingState extends BaseFlashState { export interface ErasingState extends BaseFlashState {
state: State.ERASING; state: FlashStateType.ERASING;
details: { done: boolean }; details: { done: boolean };
} }
export interface WritingState extends BaseFlashState { export interface WritingState extends BaseFlashState {
state: State.WRITING; state: FlashStateType.WRITING;
details: { bytesTotal: number; bytesWritten: number; percentage: number }; details: { bytesTotal: number; bytesWritten: number; percentage: number };
} }
export interface FinishedState extends BaseFlashState { export interface FinishedState extends BaseFlashState {
state: State.FINISHED; state: FlashStateType.FINISHED;
} }
export interface ErrorState extends BaseFlashState { export interface ErrorState extends BaseFlashState {
state: State.ERROR; state: FlashStateType.ERROR;
details: { error: FlashError; details: string | Error }; details: { error: FlashError; details: string | Error };
} }
@ -63,7 +69,7 @@ export type FlashState =
| FinishedState | FinishedState
| ErrorState; | ErrorState;
export const enum State { export const enum FlashStateType {
INITIALIZING = "initializing", INITIALIZING = "initializing",
MANIFEST = "manifest", MANIFEST = "manifest",
PREPARING = "preparing", PREPARING = "preparing",

View File

@ -1,138 +0,0 @@
import { css, html, HTMLTemplateResult, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { FlashState, State } from "./const";
interface Row {
state?: State;
message: HTMLTemplateResult | string;
error?: boolean;
action?: boolean;
}
@customElement("esp-web-flash-log")
export class FlashLog extends LitElement {
@state() private _rows: Row[] = [];
protected render() {
return html`${this._rows.map(
(row) =>
html`<div
class=${classMap({
error: row.error === true,
action: row.action === true,
})}
>
${row.message}
</div>`
)}`;
}
public willUpdate() {
this.toggleAttribute("hidden", !this._rows.length);
}
public clear() {
this._rows = [];
}
public processState(state: FlashState) {
if (state.state === State.ERROR) {
this.addError(state.message);
return;
}
this.addRow(state);
if (state.state === State.FINISHED) {
this.addAction(
html`<button @click=${this.clear}>Close this log</button>`
);
}
}
/**
* Add or replace a row.
*/
public addRow(row: Row) {
// If last entry has same ID, replace it.
if (
row.state &&
this._rows.length > 0 &&
this._rows[this._rows.length - 1].state === row.state
) {
const newRows = this._rows.slice(0, -1);
newRows.push(row);
this._rows = newRows;
} else {
this._rows = [...this._rows, row];
}
}
/**
* Add an error row
*/
public addError(message: Row["message"]) {
this.addRow({ message, error: true });
}
/**
* Add an action row
*/
public addAction(message: Row["message"]) {
this.addRow({ message, action: true });
}
/**
* Remove last row if state matches
*/
public removeRow(state: string) {
if (
this._rows.length > 0 &&
this._rows[this._rows.length - 1].state === state
) {
this._rows = this._rows.slice(0, -1);
}
}
static styles = css`
:host {
display: block;
margin-top: 16px;
padding: 12px 16px;
font-family: monospace;
background: var(--esp-tools-log-background, black);
color: var(--esp-tools-log-text-color, greenyellow);
font-size: 14px;
line-height: 19px;
}
:host([hidden]) {
display: none;
}
button {
background: none;
color: inherit;
border: none;
padding: 0;
font: inherit;
text-align: left;
text-decoration: underline;
cursor: pointer;
}
.error {
color: var(--esp-tools-error-color, #dc3545);
}
.error,
.action {
margin-top: 1em;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"esp-web-flash-log": FlashLog;
}
}

View File

@ -1,88 +0,0 @@
import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { FlashState, State } from "./const";
import "@material/mwc-linear-progress";
import { classMap } from "lit/directives/class-map.js";
@customElement("esp-web-flash-progress")
export class FlashProgress extends LitElement {
@state() private _state?: FlashState;
@state() private _indeterminate = true;
@state() private _progress = 0;
public processState(state: FlashState) {
this._state = state;
if (this._state.state === State.WRITING) {
this._indeterminate = false;
this._progress = this._state.details.percentage / 100;
}
if (this._state.state === State.ERROR) {
this._indeterminate = false;
}
}
public clear() {
this._state = undefined;
this._progress = 0;
this._indeterminate = true;
}
protected render() {
if (!this._state) {
return;
}
return html`<h2
class=${classMap({
error: this._state.state === State.ERROR,
done: this._state.state === State.FINISHED,
})}
>
${this._state.message}
</h2>
<p>
${this._state.manifest
? html`${this._state.manifest.name}: ${this._state.chipFamily}`
: html`&nbsp;`}
</p>
<mwc-linear-progress
class=${classMap({
error: this._state.state === State.ERROR,
done: this._state.state === State.FINISHED,
})}
.indeterminate=${this._indeterminate}
.progress=${this._progress}
></mwc-linear-progress>`;
}
static styles = css`
:host {
display: block;
--mdc-theme-primary: var(--esp-tools-progress-color, #03a9f4);
}
.error {
color: var(--esp-tools-error-color, #dc3545);
--mdc-theme-primary: var(--esp-tools-error-color, #dc3545);
}
.done {
color: var(--esp-tools-success-color, #28a745);
--mdc-theme-primary: var(--esp-tools-success-color, #28a745);
}
mwc-linear-progress {
text-align: left;
}
h2 {
margin: 16px 0 0;
}
p {
margin: 4px 0;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"esp-web-flash-progress": FlashProgress;
}
}

View File

@ -1,9 +1,17 @@
import { connect, ESPLoader, Logger } from "esp-web-flasher"; import { ESPLoader, Logger } from "esp-web-flasher";
import { Build, FlashError, FlashState, Manifest, State } from "./const"; import {
import { fireEvent, getChipFamilyName, sleep } from "./util"; Build,
FlashError,
FlashState,
Manifest,
FlashStateType,
} from "./const";
import { getChipFamilyName } from "./util/chip-family-name";
import { sleep } from "./util/sleep";
export const flash = async ( export const flash = async (
eventTarget: EventTarget, onEvent: (state: FlashState) => void,
port: SerialPort,
logger: Logger, logger: Logger,
manifestPath: string, manifestPath: string,
eraseFirst: boolean eraseFirst: boolean
@ -12,47 +20,39 @@ export const flash = async (
let build: Build | undefined; let build: Build | undefined;
let chipFamily: ReturnType<typeof getChipFamilyName>; let chipFamily: ReturnType<typeof getChipFamilyName>;
const fireStateEvent = (stateUpdate: FlashState) => { const fireStateEvent = (stateUpdate: FlashState) =>
fireEvent(eventTarget, "state-changed", { onEvent({
...stateUpdate, ...stateUpdate,
manifest, manifest,
build, build,
chipFamily, chipFamily,
}); });
};
const manifestURL = new URL(manifestPath, location.toString()).toString(); const manifestURL = new URL(manifestPath, location.toString()).toString();
const manifestProm = fetch(manifestURL).then( const manifestProm = fetch(manifestURL).then(
(resp): Promise<Manifest> => resp.json() (resp): Promise<Manifest> => resp.json()
); );
let esploader: ESPLoader | undefined; const esploader = new ESPLoader(port, logger);
try {
esploader = await connect(logger);
} catch (err) {
// User pressed cancel on web serial
return;
}
// For debugging // For debugging
(window as any).esploader = esploader; (window as any).esploader = esploader;
fireStateEvent({ fireStateEvent({
state: State.INITIALIZING, state: FlashStateType.INITIALIZING,
message: "Initializing...", message: "Initializing...",
details: { done: false }, details: { done: false },
}); });
try { try {
await esploader.initialize(); await esploader.initialize();
} catch (err) { } catch (err: any) {
logger.error(err); logger.error(err);
if (esploader.connected) { if (esploader.connected) {
fireStateEvent({ fireStateEvent({
state: State.ERROR, state: FlashStateType.ERROR,
message: message:
"Failed to initialize. Try resetting your device or holding the BOOT button while selecting your serial port.", "Failed to initialize. Try resetting your device or holding the BOOT button while clicking INSTALL.",
details: { error: FlashError.FAILED_INITIALIZING, details: err }, details: { error: FlashError.FAILED_INITIALIZING, details: err },
}); });
await esploader.disconnect(); await esploader.disconnect();
@ -63,22 +63,22 @@ export const flash = async (
chipFamily = getChipFamilyName(esploader); chipFamily = getChipFamilyName(esploader);
fireStateEvent({ fireStateEvent({
state: State.INITIALIZING, state: FlashStateType.INITIALIZING,
message: `Initialized. Found ${chipFamily}`, message: `Initialized. Found ${chipFamily}`,
details: { done: true }, details: { done: true },
}); });
fireStateEvent({ fireStateEvent({
state: State.MANIFEST, state: FlashStateType.MANIFEST,
message: "Fetching manifest...", message: "Fetching manifest...",
details: { done: false }, details: { done: false },
}); });
try { try {
manifest = await manifestProm; manifest = await manifestProm;
} catch (err) { } catch (err: any) {
fireStateEvent({ fireStateEvent({
state: State.ERROR, state: FlashStateType.ERROR,
message: `Unable to fetch manifest: ${err.message}`, message: `Unable to fetch manifest: ${err}`,
details: { error: FlashError.FAILED_MANIFEST_FETCH, details: err }, details: { error: FlashError.FAILED_MANIFEST_FETCH, details: err },
}); });
await esploader.disconnect(); await esploader.disconnect();
@ -88,14 +88,14 @@ export const flash = async (
build = manifest.builds.find((b) => b.chipFamily === chipFamily); build = manifest.builds.find((b) => b.chipFamily === chipFamily);
fireStateEvent({ fireStateEvent({
state: State.MANIFEST, state: FlashStateType.MANIFEST,
message: `Found manifest for ${manifest.name}`, message: `Found manifest for ${manifest.name}`,
details: { done: true }, details: { done: true },
}); });
if (!build) { if (!build) {
fireStateEvent({ fireStateEvent({
state: State.ERROR, state: FlashStateType.ERROR,
message: `Your ${chipFamily} board is not supported.`, message: `Your ${chipFamily} board is not supported.`,
details: { error: FlashError.NOT_SUPPORTED, details: chipFamily }, details: { error: FlashError.NOT_SUPPORTED, details: chipFamily },
}); });
@ -104,7 +104,7 @@ export const flash = async (
} }
fireStateEvent({ fireStateEvent({
state: State.PREPARING, state: FlashStateType.PREPARING,
message: "Preparing installation...", message: "Preparing installation...",
details: { done: false }, details: { done: false },
}); });
@ -131,11 +131,14 @@ export const flash = async (
const data = await prom; const data = await prom;
files.push(data); files.push(data);
totalSize += data.byteLength; totalSize += data.byteLength;
} catch (err) { } catch (err: any) {
fireStateEvent({ fireStateEvent({
state: State.ERROR, state: FlashStateType.ERROR,
message: err, message: err.message,
details: { error: FlashError.FAILED_FIRMWARE_DOWNLOAD, details: err }, details: {
error: FlashError.FAILED_FIRMWARE_DOWNLOAD,
details: err.message,
},
}); });
await esploader.disconnect(); await esploader.disconnect();
return; return;
@ -143,20 +146,20 @@ export const flash = async (
} }
fireStateEvent({ fireStateEvent({
state: State.PREPARING, state: FlashStateType.PREPARING,
message: "Installation prepared", message: "Installation prepared",
details: { done: true }, details: { done: true },
}); });
if (eraseFirst) { if (eraseFirst) {
fireStateEvent({ fireStateEvent({
state: State.ERASING, state: FlashStateType.ERASING,
message: "Erasing device...", message: "Erasing device...",
details: { done: false }, details: { done: false },
}); });
await espStub.eraseFlash(); await espStub.eraseFlash();
fireStateEvent({ fireStateEvent({
state: State.ERASING, state: FlashStateType.ERASING,
message: "Device erased", message: "Device erased",
details: { done: true }, details: { done: true },
}); });
@ -165,7 +168,7 @@ export const flash = async (
let lastPct = 0; let lastPct = 0;
fireStateEvent({ fireStateEvent({
state: State.WRITING, state: FlashStateType.WRITING,
message: `Writing progress: ${lastPct}%`, message: `Writing progress: ${lastPct}%`,
details: { details: {
bytesTotal: totalSize, bytesTotal: totalSize,
@ -190,7 +193,7 @@ export const flash = async (
} }
lastPct = newPct; lastPct = newPct;
fireStateEvent({ fireStateEvent({
state: State.WRITING, state: FlashStateType.WRITING,
message: `Writing progress: ${newPct}%`, message: `Writing progress: ${newPct}%`,
details: { details: {
bytesTotal: totalSize, bytesTotal: totalSize,
@ -202,10 +205,10 @@ export const flash = async (
part.offset, part.offset,
true true
); );
} catch (err) { } catch (err: any) {
fireStateEvent({ fireStateEvent({
state: State.ERROR, state: FlashStateType.ERROR,
message: err, message: err.message,
details: { error: FlashError.WRITE_FAILED, details: err }, details: { error: FlashError.WRITE_FAILED, details: err },
}); });
await esploader.disconnect(); await esploader.disconnect();
@ -215,7 +218,7 @@ export const flash = async (
} }
fireStateEvent({ fireStateEvent({
state: State.WRITING, state: FlashStateType.WRITING,
message: "Writing complete", message: "Writing complete",
details: { details: {
bytesTotal: totalSize, bytesTotal: totalSize,
@ -225,11 +228,13 @@ export const flash = async (
}); });
await sleep(100); await sleep(100);
await esploader.hardReset(); console.log("DISCONNECT");
await esploader.disconnect(); await esploader.disconnect();
console.log("HARD RESET");
await esploader.hardReset();
fireStateEvent({ fireStateEvent({
state: State.FINISHED, state: FlashStateType.FINISHED,
message: "All done!", message: "All done!",
}); });
}; };

View File

@ -72,7 +72,7 @@ export class InstallButton extends HTMLElement {
public renderRoot?: ShadowRoot; public renderRoot?: ShadowRoot;
public static preload() { public static preload() {
import("./start-flash"); import("./connect");
} }
public connectedCallback() { public connectedCallback() {
@ -98,8 +98,8 @@ export class InstallButton extends HTMLElement {
slot.addEventListener("click", async (ev) => { slot.addEventListener("click", async (ev) => {
ev.preventDefault(); ev.preventDefault();
const mod = await import("./start-flash"); const mod = await import("./connect");
mod.startFlash(this); mod.connect(this);
}); });
slot.name = "activate"; slot.name = "activate";

718
src/install-dialog.ts Normal file
View File

@ -0,0 +1,718 @@
import { LitElement, html, PropertyValues, css, TemplateResult } from "lit";
import { state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import "./components/ewt-dialog";
import "./components/ewt-textfield";
import "./components/ewt-button";
import "./components/ewt-icon-button";
import "./components/ewt-circular-progress";
import type { EwtTextfield } from "./components/ewt-textfield";
import { Logger, Manifest, FlashStateType, FlashState } from "./const.js";
import { ImprovSerial } from "improv-wifi-serial-sdk/dist/serial";
import {
ImprovSerialCurrentState,
ImprovSerialErrorState,
PortNotReady,
} from "improv-wifi-serial-sdk/dist/const";
import { fireEvent } from "./util/fire-event";
import { flash } from "./flash";
import "./components/ewt-console";
import { sleep } from "./util/sleep";
const ERROR_ICON = "⚠️";
const OK_ICON = "🎉";
const messageTemplate = (icon: string, label: string) => html`
<div class="center">
<div class="icon">${icon}</div>
${label}
</div>
`;
class EwtInstallDialog extends LitElement {
public port!: SerialPort;
public manifestPath!: string;
public logger: Logger = console;
private _manifest!: Manifest;
private _info?: ImprovSerial["info"];
// null = NOT_SUPPORTED
@state() private _client?: ImprovSerial | null;
@state() private _state:
| "ERROR"
| "DASHBOARD"
| "PROVISION"
| "INSTALL"
| "LOGS" = "DASHBOARD";
@state() private _installErase = false;
@state() private _installConfirmed = false;
@state() private _installState?: FlashState;
@state() private _provisionForce = false;
@state() private _error?: string;
@state() private _busy = false;
private _progressFeedback?: {
resolve: (_: unknown) => void;
reject: () => void;
};
protected render() {
if (!this.port) {
return html``;
}
let heading: string | undefined;
let content: TemplateResult;
let hideActions = false;
let allowClosing = false;
// During installation phase we temporarily remove the client
if (
this._client === undefined &&
this._state !== "INSTALL" &&
this._state !== "LOGS"
) {
if (this._error) {
content = this._renderMessage(ERROR_ICON, this._error, true);
} else {
content = this._renderProgress("Connecting");
hideActions = true;
}
} else if (this._state === "INSTALL") {
[heading, content, hideActions, allowClosing] = this._renderInstall();
} else if (this._state === "ERROR") {
heading = "Error";
content = this._renderMessage(ERROR_ICON, this._error!, true);
} else if (this._state === "DASHBOARD") {
[heading, content, hideActions, allowClosing] = this._renderDashboard();
} else if (this._state === "PROVISION") {
[heading, content, hideActions] = this._renderProvision();
} else if (this._state === "LOGS") {
[heading, content, hideActions] = this._renderLogs();
}
return html`
<ewt-dialog
open
.heading=${heading!}
scrimClickAction
@closed=${this._handleClose}
.hideActions=${hideActions}
>
${heading && allowClosing
? html`
<ewt-icon-button dialogAction="close">
<svg width="24" height="24" viewBox="0 0 24 24">
<path
d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
/>
</svg>
</ewt-icon-button>
`
: ""}
${content!}
</ewt-dialog>
`;
}
_renderProgress(label: string | TemplateResult, progress?: number) {
return html`
<div class="center">
<div>
<ewt-circular-progress
active
?indeterminate=${progress === undefined}
.progress=${progress !== undefined ? progress / 100 : undefined}
density="8"
></ewt-circular-progress>
${progress !== undefined
? html`<div class="progress-pct">${progress}%</div>`
: ""}
</div>
${label}
</div>
`;
}
_renderMessage(icon: string, label: string, showClose: boolean) {
return html`
${messageTemplate(icon, label)}
${showClose &&
html`
<ewt-button
slot="primaryAction"
dialogAction="ok"
label="Close"
></ewt-button>
`}
`;
}
_renderDashboard(): [string, TemplateResult, boolean, boolean] {
const heading = this._info!.name;
let content: TemplateResult;
let hideActions = true;
let allowClosing = true;
const isSameFirmware = this._info!.firmware === this._manifest!.name;
const isSameVersion =
isSameFirmware && this._info!.version === this._manifest!.version;
content = html`
<div class="device-info">
${this._info!.firmware}&nbsp;${this._info!.version}
</div>
<div class="dashboard-buttons">
${this._client!.nextUrl === undefined
? ""
: html`
<div>
<a
href=${this._client!.nextUrl}
class="has-button"
target="_blank"
>
<ewt-button label="Set up Device"></ewt-button>
</a>
</div>
`}
<div>
<ewt-button
.label=${this._client!.state === ImprovSerialCurrentState.READY
? "Connect to Wi-Fi"
: "Change Wi-Fi"}
@click=${() => {
this._state = "PROVISION";
if (
this._client!.state === ImprovSerialCurrentState.PROVISIONED
) {
this._provisionForce = true;
}
}}
></ewt-button>
</div>
<div>
<ewt-button
.label=${!isSameFirmware
? `Install ${this._manifest!.name}`
: isSameVersion
? "Up to date"
: "Update"}
@click=${() => this._startInstall(!isSameFirmware)}
.disabled=${isSameVersion}
></ewt-button>
</div>
<div>
<ewt-button
label="Logs"
@click=${async () => {
const client = this._client;
if (client) {
await this._closeClientWithoutEvents(client);
await sleep(100);
}
// Also set `null` back to undefined.
this._client = undefined;
this._state = "LOGS";
}}
></ewt-button>
</div>
</div>
`;
return [heading, content, hideActions, allowClosing];
}
_renderProvision(): [string | undefined, TemplateResult, boolean] {
let heading: string | undefined = "Configure Wi-Fi";
let content: TemplateResult;
let hideActions = false;
if (this._busy) {
return [heading, this._renderProgress("Trying to connect"), true];
}
if (
!this._provisionForce &&
this._client!.state === ImprovSerialCurrentState.PROVISIONED
) {
heading = undefined;
content = html`
${messageTemplate(OK_ICON, "Device connected to the network!")}
${
// If we went to provision after installing the firmware with a full erase,
// there is nothing left for the user, let them go to the device dashboard
// if available
this._installState?.state === FlashStateType.FINISHED &&
this._installErase &&
this._client!.nextUrl !== undefined
? html`
<a
slot="primaryAction"
href=${this._client!.nextUrl}
class="has-button"
target="_blank"
@click=${() => {
this._state = "DASHBOARD";
}}
>
<ewt-button label="Set up Device"></ewt-button>
</a>
<ewt-button
slot="secondaryAction"
label="Skip"
@click=${() => {
this._state = "DASHBOARD";
this._installState = undefined;
}}
></ewt-button>
`
: html`
<ewt-button
slot="primaryAction"
label="Continue"
@click=${() => {
this._state = "DASHBOARD";
}}
></ewt-button>
`
}
`;
} else {
let error: string | undefined;
switch (this._client!.error) {
case ImprovSerialErrorState.UNABLE_TO_CONNECT:
error = "Unable to connect";
break;
case ImprovSerialErrorState.NO_ERROR:
break;
default:
error = `Unknown error (${this._client!.error})`;
}
content = html`
<div>
Enter the credentials of the Wi-Fi network that you want your device
to connect to.
</div>
${error ? html`<p class="error">${error}</p>` : ""}
<ewt-textfield label="Network Name" name="ssid"></ewt-textfield>
<ewt-textfield
label="Password"
name="password"
type="password"
></ewt-textfield>
<ewt-button
slot="primaryAction"
label="Connect"
@click="${this._doProvision}"
></ewt-button>
<ewt-button
slot="secondaryAction"
.label=${this._installState && this._installErase ? "Skip" : "Back"}
@click=${() => {
this._installState = undefined;
this._state = "DASHBOARD";
}}
></ewt-button>
`;
}
return [heading, content, hideActions];
}
_renderInstall(): [string | undefined, TemplateResult, boolean, boolean] {
let heading: string | undefined = `Install ${this._manifest!.name}`;
let content: TemplateResult;
let hideActions = false;
let allowClosing = false;
const isUpdate = !this._installErase && this._isUpdate;
if (!this._installConfirmed) {
const action = isUpdate ? "update to" : "install";
content = html`
${isUpdate
? html`Your device is running
${this._info!.firmware}&nbsp;${this._info!.version}.<br /><br />`
: ""}
Do you want to ${action}
${this._manifest!.name}&nbsp;${this._manifest!.version}?
${this._installErase
? "All existing data will be erased from your device."
: ""}
<ewt-button
slot="primaryAction"
label="Install"
@click=${this._confirmInstall}
></ewt-button>
${this._client
? html`
<ewt-button
slot="secondaryAction"
label="Back"
@click=${() => {
this._state = "DASHBOARD";
}}
></ewt-button>
`
: html`
<ewt-button
slot="secondaryAction"
label="Logs"
@click=${async () => {
// In case it was null
this._client = undefined;
this._state = "LOGS";
}}
></ewt-button>
`}
`;
allowClosing = !this._client;
} else if (
!this._installState ||
this._installState.state === FlashStateType.INITIALIZING ||
this._installState.state === FlashStateType.MANIFEST ||
this._installState.state === FlashStateType.PREPARING
) {
content = this._renderProgress("Preparing installation");
hideActions = true;
} else if (this._installState.state === FlashStateType.ERASING) {
content = this._renderProgress("Erasing");
hideActions = true;
} else if (this._installState.state === FlashStateType.WRITING) {
content = this._renderProgress(
html`
${this._installState.details.percentage > 3
? ""
: html`Installing<br />`}
<br />
This will take
${this._installState.chipFamily === "ESP8266"
? "a minute"
: "2 minutes"}.<br />
Keep this page visible to prevent slow down
`,
// Show as undeterminate under 3% or else we don't show any pixels
this._installState.details.percentage > 3
? this._installState.details.percentage
: undefined
);
hideActions = true;
} else if (this._installState.state === FlashStateType.FINISHED) {
heading = undefined;
const supportsImprov = this._client !== null;
content = html`
${messageTemplate(OK_ICON, "Installation complete!")}
<ewt-button
slot="primaryAction"
.label=${supportsImprov ? "Next" : "Close"}
dialogAction=${ifDefined(supportsImprov ? undefined : "close")}
@click=${!supportsImprov
? undefined
: () => {
this._state = this._installErase ? "PROVISION" : "DASHBOARD";
}}
></ewt-button>
`;
} else if (this._installState.state === FlashStateType.ERROR) {
content = html`
${messageTemplate(ERROR_ICON, this._installState.message)}
<ewt-button
slot="primaryAction"
label="Back"
@click=${async () => {
this._initialize();
this._state = "DASHBOARD";
this._installState = undefined;
}}
></ewt-button>
`;
}
return [heading, content!, hideActions, allowClosing];
}
_renderLogs(): [string | undefined, TemplateResult, boolean] {
let heading: string | undefined = `Logs`;
let content: TemplateResult;
let hideActions = false;
content = html`
<ewt-console .port=${this.port} .logger=${this.logger}></ewt-console>
<ewt-button
slot="primaryAction"
label="Back"
@click=${async () => {
await this.shadowRoot!.querySelector("ewt-console")!.disconnect();
this._state = "DASHBOARD";
this._initialize();
}}
></ewt-button>
<ewt-button
slot="secondaryAction"
label="Reset Device"
@click=${async () => {
await this.shadowRoot!.querySelector("ewt-console")!.reset();
}}
></ewt-button>
`;
return [heading, content!, hideActions];
}
public override willUpdate(changedProps: PropertyValues) {
if (!changedProps.has("_state")) {
return;
}
// Clear errors when changing between pages unless we change
// to the error page.
if (this._state !== "ERROR") {
this._error = undefined;
}
if (this._state !== "PROVISION") {
this._provisionForce = false;
}
}
protected override firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._initialize();
}
protected override updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!changedProps.has("_state")) {
return;
}
this.setAttribute("state", this._state);
if (this._state === "PROVISION") {
const textfield = this.shadowRoot!.querySelector("ewt-textfield");
if (textfield) {
textfield.updateComplete.then(() => textfield.focus());
}
} else if (this._state === "INSTALL") {
this._installConfirmed = false;
this._installState = undefined;
}
}
private async _fetchManifest() {
if (this._manifest) {
return;
}
const manifestURL = new URL(
this.manifestPath,
location.toString()
).toString();
this._manifest = await fetch(manifestURL).then(
(resp): Promise<Manifest> => resp.json()
);
}
private async _initialize() {
if (this.port.readable === null || this.port.writable === null) {
this._state = "ERROR";
this._error =
"Serial port is not readable/writable. Close any other application using it and try again.";
}
const manifestProm = this._fetchManifest();
const client = new ImprovSerial(this.port!, this.logger);
client.addEventListener("state-changed", () => {
this.requestUpdate();
});
client.addEventListener("error-changed", () => this.requestUpdate());
try {
this._info = await client.initialize();
this._client = client;
client.addEventListener("disconnect", this._handleDisconnect);
} catch (err: any) {
// Clear old value
this._info = undefined;
if (err instanceof PortNotReady) {
this._state = "ERROR";
this._error =
"Serial port is not ready. Close any other application using it and try again.";
} else {
this._client = null; // not supported
this.logger.error("Improv initialization failed.", err);
// initialize is also called at the end of an installation
// When it can't detect improv (ie because install failed)
// We shouldn't reset settings but instead show the error
if (this._state !== "INSTALL") {
this._startInstall(true);
}
}
}
try {
await manifestProm;
} catch (err: any) {
this._state = "ERROR";
this._error = "Failed to download manifest";
}
}
private _startInstall(erase: boolean) {
this._state = "INSTALL";
this._installErase = erase;
this._installConfirmed = false;
}
private async _confirmInstall() {
this._installConfirmed = true;
this._installState = undefined;
if (this._client) {
await this._closeClientWithoutEvents(this._client);
}
this._client = undefined;
flash(
(state) => {
this._installState = state;
if (state.state === FlashStateType.FINISHED) {
this._initialize().then(() => this.requestUpdate());
}
},
this.port,
this.logger,
this.manifestPath,
this._installErase
);
}
private async _doProvision() {
this._busy = true;
const ssid = (
this.shadowRoot!.querySelector("ewt-textfield[name=ssid]") as EwtTextfield
).value;
const password = (
this.shadowRoot!.querySelector(
"ewt-textfield[name=password]"
) as EwtTextfield
).value;
try {
await this._client!.provision(ssid, password);
} catch (err: any) {
return;
} finally {
this._busy = false;
this._provisionForce = false;
}
}
private _handleDisconnect = () => {
this._state = "ERROR";
this._error = "Disconnected";
};
private async _handleClose() {
if (this._progressFeedback) {
this._progressFeedback.reject();
}
if (this._client) {
await this._closeClientWithoutEvents(this._client);
}
fireEvent(this, "closed" as any);
this.parentNode!.removeChild(this);
}
private get _isUpdate() {
return this._info?.firmware === this._manifest!.name;
}
private async _closeClientWithoutEvents(client: ImprovSerial) {
client.removeEventListener("disconnect", this._handleDisconnect);
await client.close();
}
static styles = css`
:host {
--mdc-dialog-max-width: 390px;
--mdc-theme-primary: var(--improv-primary-color, #03a9f4);
--mdc-theme-on-primary: var(--improv-on-primary-color, #fff);
}
ewt-icon-button {
position: absolute;
right: 4px;
top: 10px;
}
ewt-textfield {
display: block;
margin-top: 16px;
}
.center {
text-align: center;
}
.flash {
font-weight: bold;
margin-bottom: 1em;
background-color: var(--mdc-theme-primary);
padding: 8px 4px;
color: var(--mdc-theme-on-primary);
border-radius: 4px;
text-align: center;
}
.dashboard-buttons {
margin: 16px 0 -16px -8px;
}
.dashboard-buttons div {
display: block;
margin: 4px 0;
}
ewt-circular-progress {
margin-bottom: 16px;
}
a.has-button {
text-decoration: none;
}
.icon {
font-size: 50px;
line-height: 80px;
color: black;
}
.error {
color: #db4437;
}
button.link {
background: none;
color: inherit;
border: none;
padding: 0;
font: inherit;
text-align: left;
text-decoration: underline;
cursor: pointer;
}
:host([state="LOGS"]) ewt-dialog {
--mdc-dialog-max-width: 90vw;
}
ewt-console {
display: block;
width: calc(80vw - 48px);
height: 80vh;
}
`;
}
customElements.define("ewt-install-dialog", EwtInstallDialog);
declare global {
interface HTMLElementTagNameMap {
"ewt-install-dialog": EwtInstallDialog;
}
}

View File

@ -1,154 +0,0 @@
import { flash } from "./flash";
import "./flash-log";
import "./flash-progress";
import type { FlashLog } from "./flash-log";
import type { FlashProgress } from "./flash-progress";
import type { InstallButton } from "./install-button";
import { State } from "./const";
interface FlashData {
stateListenerAdded: boolean;
logEl: FlashLog | undefined;
progressEl: FlashProgress | undefined;
improvEl: HTMLElement | undefined;
}
const getData = (button: InstallButton): FlashData => {
if (!("_flashData" in button)) {
(button as any)._flashData = {
stateListenerAdded: false,
logEl: undefined,
progressEl: undefined,
improvEl: undefined,
} as FlashData;
}
return (button as any)._flashData as FlashData;
};
const addElement = <T extends HTMLElement>(
button: InstallButton,
element: T
): T => {
button.renderRoot!.append(element);
return element;
};
export const startFlash = async (button: InstallButton) => {
if (button.hasAttribute("active")) {
return;
}
const manifest = button.manifest || button.getAttribute("manifest");
if (!manifest) {
alert("No manifest defined!");
return;
}
const data = getData(button);
let hasImprov = false;
if (!data.stateListenerAdded) {
data.stateListenerAdded = true;
button.addEventListener("state-changed", (ev) => {
const state = (button.state = ev.detail);
if (state.state === State.INITIALIZING) {
button.toggleAttribute("active", true);
} else if (state.state === State.MANIFEST && state.build?.improv) {
hasImprov = true;
// @ts-ignore
// preload improv button
import("https://www.improv-wifi.com/sdk-js/launch-button.js");
} else if (state.state === State.FINISHED) {
button.toggleAttribute("active", false);
if (hasImprov) {
startImprov(button);
}
} else if (state.state === State.ERROR) {
button.toggleAttribute("active", false);
}
data.progressEl?.processState(ev.detail);
data.logEl?.processState(ev.detail);
});
}
const logConsole = button.logConsole || button.hasAttribute("log-console");
const showLog = button.showLog || button.hasAttribute("show-log");
const showProgress =
!showLog &&
button.hideProgress !== true &&
!button.hasAttribute("hide-progress");
if (showLog && !data.logEl) {
data.logEl = addElement<FlashLog>(
button,
document.createElement("esp-web-flash-log")
);
} else if (!showLog && data.logEl) {
data.logEl.remove();
data.logEl = undefined;
}
if (showProgress && !data.progressEl) {
data.progressEl = addElement<FlashProgress>(
button,
document.createElement("esp-web-flash-progress")
);
} else if (!showProgress && data.progressEl) {
data.progressEl.remove();
data.progressEl = undefined;
}
data.logEl?.clear();
data.progressEl?.clear();
data.improvEl?.classList.toggle("hidden", true);
flash(
button,
logConsole
? console
: {
log: () => {},
error: () => {},
debug: () => {},
},
manifest,
button.eraseFirst !== undefined
? button.eraseFirst
: button.hasAttribute("erase-first")
);
};
const startImprov = async (button: InstallButton) => {
// @ts-ignore
await import("https://www.improv-wifi.com/sdk-js/launch-button.js");
const improvButtonConstructor = customElements.get(
"improv-wifi-launch-button"
);
if (
!improvButtonConstructor.isSupported ||
!improvButtonConstructor.isAllowed
) {
return;
}
const data = getData(button);
if (!data.improvEl) {
data.improvEl = document.createElement("improv-wifi-launch-button");
data.improvEl.addEventListener("state-changed", (ev: any) => {
if (ev.detail.state === "PROVISIONED") {
data.improvEl!.classList.toggle("hidden", true);
}
});
const improvButton = document.createElement("button");
improvButton.slot = "activate";
improvButton.textContent = "CLICK HERE TO FINISH SETTING UP YOUR DEVICE";
data.improvEl.appendChild(improvButton);
addElement(button, data.improvEl);
}
data.improvEl.classList.toggle("hidden", false);
};

View File

@ -1,49 +0,0 @@
import {
CHIP_FAMILY_ESP32,
CHIP_FAMILY_ESP32S2,
CHIP_FAMILY_ESP8266,
CHIP_FAMILY_ESP32C3,
ESPLoader,
} from "esp-web-flasher";
import type { BaseFlashState } from "./const";
export const getChipFamilyName = (
esploader: ESPLoader
): NonNullable<BaseFlashState["chipFamily"]> => {
switch (esploader.chipFamily) {
case CHIP_FAMILY_ESP32:
return "ESP32";
case CHIP_FAMILY_ESP8266:
return "ESP8266";
case CHIP_FAMILY_ESP32S2:
return "ESP32-S2";
case CHIP_FAMILY_ESP32C3:
return "ESP32-C3";
default:
return "Unknown Chip";
}
};
export const sleep = (time: number) =>
new Promise((resolve) => setTimeout(resolve, time));
export const fireEvent = <Event extends keyof HTMLElementEventMap>(
eventTarget: EventTarget,
type: Event,
// @ts-ignore
detail?: HTMLElementEventMap[Event]["detail"],
options?: {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
}
): void => {
options = options || {};
const event = new CustomEvent(type, {
bubbles: options.bubbles === undefined ? true : options.bubbles,
cancelable: Boolean(options.cancelable),
composed: options.composed === undefined ? true : options.composed,
detail,
});
eventTarget.dispatchEvent(event);
};

View File

@ -0,0 +1,25 @@
import {
CHIP_FAMILY_ESP32,
CHIP_FAMILY_ESP32S2,
CHIP_FAMILY_ESP8266,
CHIP_FAMILY_ESP32C3,
ESPLoader,
} from "esp-web-flasher";
import type { BaseFlashState } from "../const";
export const getChipFamilyName = (
esploader: ESPLoader
): NonNullable<BaseFlashState["chipFamily"]> => {
switch (esploader.chipFamily) {
case CHIP_FAMILY_ESP32:
return "ESP32";
case CHIP_FAMILY_ESP8266:
return "ESP8266";
case CHIP_FAMILY_ESP32S2:
return "ESP32-S2";
case CHIP_FAMILY_ESP32C3:
return "ESP32-C3";
default:
return "Unknown Chip";
}
};

188
src/util/console-color.ts Normal file
View File

@ -0,0 +1,188 @@
interface ConsoleState {
bold: boolean;
italic: boolean;
underline: boolean;
strikethrough: boolean;
foregroundColor: string | null;
backgroundColor: string | null;
// carriageReturn: boolean;
secret: boolean;
}
export class ColoredConsole {
public state: ConsoleState = {
bold: false,
italic: false,
underline: false,
strikethrough: false,
foregroundColor: null,
backgroundColor: null,
// carriageReturn: false,
secret: false,
};
constructor(public targetElement: HTMLElement) {}
addLine(line: string) {
const re = /(?:\033|\\033)(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
let i = 0;
// This doesn't work for some reason
// if (this.state.carriageReturn) {
// if (line !== "\n") {
// // don't remove if \r\n
// this.targetElement.removeChild(this.targetElement.lastChild!);
// }
// this.state.carriageReturn = false;
// }
// if (line.includes("\r")) {
// this.state.carriageReturn = true;
// }
const lineSpan = document.createElement("span");
lineSpan.classList.add("line");
this.targetElement.appendChild(lineSpan);
const addSpan = (content: string) => {
if (content === "") return;
const span = document.createElement("span");
if (this.state.bold) span.classList.add("log-bold");
if (this.state.italic) span.classList.add("log-italic");
if (this.state.underline) span.classList.add("log-underline");
if (this.state.strikethrough) span.classList.add("log-strikethrough");
if (this.state.secret) span.classList.add("log-secret");
if (this.state.foregroundColor !== null)
span.classList.add(`log-fg-${this.state.foregroundColor}`);
if (this.state.backgroundColor !== null)
span.classList.add(`log-bg-${this.state.backgroundColor}`);
span.appendChild(document.createTextNode(content));
lineSpan.appendChild(span);
if (this.state.secret) {
const redacted = document.createElement("span");
redacted.classList.add("log-secret-redacted");
redacted.appendChild(document.createTextNode("[redacted]"));
lineSpan.appendChild(redacted);
}
};
while (true) {
const match = re.exec(line);
if (match === null) break;
const j = match.index;
addSpan(line.substring(i, j));
i = j + match[0].length;
if (match[1] === undefined) continue;
for (const colorCode of match[1].split(";")) {
switch (parseInt(colorCode)) {
case 0:
// reset
this.state.bold = false;
this.state.italic = false;
this.state.underline = false;
this.state.strikethrough = false;
this.state.foregroundColor = null;
this.state.backgroundColor = null;
this.state.secret = false;
break;
case 1:
this.state.bold = true;
break;
case 3:
this.state.italic = true;
break;
case 4:
this.state.underline = true;
break;
case 5:
this.state.secret = true;
break;
case 6:
this.state.secret = false;
break;
case 9:
this.state.strikethrough = true;
break;
case 22:
this.state.bold = false;
break;
case 23:
this.state.italic = false;
break;
case 24:
this.state.underline = false;
break;
case 29:
this.state.strikethrough = false;
break;
case 30:
this.state.foregroundColor = "black";
break;
case 31:
this.state.foregroundColor = "red";
break;
case 32:
this.state.foregroundColor = "green";
break;
case 33:
this.state.foregroundColor = "yellow";
break;
case 34:
this.state.foregroundColor = "blue";
break;
case 35:
this.state.foregroundColor = "magenta";
break;
case 36:
this.state.foregroundColor = "cyan";
break;
case 37:
this.state.foregroundColor = "white";
break;
case 39:
this.state.foregroundColor = null;
break;
case 41:
this.state.backgroundColor = "red";
break;
case 42:
this.state.backgroundColor = "green";
break;
case 43:
this.state.backgroundColor = "yellow";
break;
case 44:
this.state.backgroundColor = "blue";
break;
case 45:
this.state.backgroundColor = "magenta";
break;
case 46:
this.state.backgroundColor = "cyan";
break;
case 47:
this.state.backgroundColor = "white";
break;
case 40:
case 49:
this.state.backgroundColor = null;
break;
}
}
}
addSpan(line.substring(i));
if (
this.targetElement.scrollTop + 56 >=
this.targetElement.scrollHeight - this.targetElement.offsetHeight
) {
// at bottom
this.targetElement.scrollTop = this.targetElement.scrollHeight;
}
}
}

20
src/util/fire-event.ts Normal file
View File

@ -0,0 +1,20 @@
export const fireEvent = <Event extends keyof HTMLElementEventMap>(
eventTarget: EventTarget,
type: Event,
// @ts-ignore
detail?: HTMLElementEventMap[Event]["detail"],
options?: {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
}
): void => {
options = options || {};
const event = new CustomEvent(type, {
bubbles: options.bubbles === undefined ? true : options.bubbles,
cancelable: Boolean(options.cancelable),
composed: options.composed === undefined ? true : options.composed,
detail,
});
eventTarget.dispatchEvent(event);
};

View File

@ -0,0 +1,20 @@
export class LineBreakTransformer implements Transformer<string, string> {
private chunks = "";
transform(
chunk: string,
controller: TransformStreamDefaultController<string>
) {
// Append new chunks to existing chunks.
this.chunks += chunk;
// For each line breaks in chunks, send the parsed lines out.
const lines = this.chunks.split("\r\n");
this.chunks = lines.pop()!;
lines.forEach((line) => controller.enqueue(line + "\r\n"));
}
flush(controller: TransformStreamDefaultController<string>) {
// When the stream is closed, flush any remaining chunks out.
controller.enqueue(this.chunks);
}
}

2
src/util/sleep.ts Normal file
View File

@ -0,0 +1,2 @@
export const sleep = (time: number) =>
new Promise((resolve) => setTimeout(resolve, time));

Binary file not shown.

Binary file not shown.

View File

@ -1,9 +1,9 @@
{ {
"name": "ESP Web Tools demo powered by ESPHome", "name": "ESPHome",
"version": "2021.11.0-dev",
"builds": [ "builds": [
{ {
"chipFamily": "ESP32", "chipFamily": "ESP32",
"improv": true,
"parts": [ "parts": [
{ "path": "bootloader.bin", "offset": 4096 }, { "path": "bootloader.bin", "offset": 4096 },
{ "path": "partitions.bin", "offset": 32768 }, { "path": "partitions.bin", "offset": 32768 },

Binary file not shown.