Compare commits

...

13 commits

Author SHA1 Message Date
makermelissa
be59de5d4a Github Action: Updated dist files 2023-03-15 23:53:26 +00:00
Melissa LeBlanc-Williams
8985632779 Show all boards if boardid is missing instead of erroring 2023-03-15 16:52:45 -07:00
makermelissa
d6f53df776 Github Action: Updated dist files 2023-03-15 21:57:11 +00:00
Melissa LeBlanc-Williams
8dbd20b7f1 Merge branch 'main' of https://github.com/adafruit/web-firmware-installer-js 2023-03-15 14:56:29 -07:00
Melissa LeBlanc-Williams
b1ed311059 Changed imports to use jsdelivr 2023-03-15 14:56:04 -07:00
makermelissa
01053a5f76 Github Action: Updated dist files 2023-03-15 18:26:18 +00:00
Melissa LeBlanc-Williams
3f2734c7dd Merge branch 'main' of https://github.com/adafruit/web-firmware-installer-js 2023-03-15 11:25:36 -07:00
Melissa LeBlanc-Williams
6d8fea53dc Only build dist when code is merged into Adafruit 2023-03-15 11:25:15 -07:00
Melissa LeBlanc-Williams
3728044b6c
Merge pull request #3 from makermelissa/main
Added Board Selector if multiple board IDs provided
2023-03-15 11:20:52 -07:00
Melissa LeBlanc-Williams
218480db90 Only build dist on PRs 2023-03-15 11:17:18 -07:00
makermelissa
95152c3b0d Github Action: Updated dist files 2023-03-15 18:15:21 +00:00
Melissa LeBlanc-Williams
ad2c0bd7a6 Escape the escape character 2023-03-15 11:14:39 -07:00
Melissa LeBlanc-Williams
4846ef6774 Added Board Selector 2023-03-15 11:07:04 -07:00
7 changed files with 330 additions and 187 deletions

View file

@ -4,10 +4,11 @@ on:
push:
branches:
- main
pull_request:
jobs:
build:
# Only run the build on Adafruit's repository. It makes the PRs less messy.
if: startswith(github.repository, 'adafruit/')
runs-on: ubuntu-latest
permissions:
contents: write
@ -26,6 +27,8 @@ jobs:
with:
directory: 'dist'
js_engine: 'uglify-js'
- name: Update Minified Dependencies
run: "sed -i 's/base_installer\\.js/base_installer.min.js/g' dist/*.min.js"
- name: Commit Distribution Files
uses: stefanzweifel/git-auto-commit-action@v4
with:

View file

@ -3,36 +3,9 @@
// SPDX-License-Identifier: MIT
'use strict';
import {html, render} from 'https://unpkg.com/lit-html?module';
import {asyncAppend} from 'https://unpkg.com/lit-html/directives/async-append?module';
import * as esptoolPackage from "https://unpkg.com/esp-web-flasher@5.1.2/dist/web/index.js?module"
// TODO: Figure out how to make the Web Serial from ESPTool and Web Serial to communicate with CircuitPython not conflict
// I think at the very least we'll have to reuse the same port so the user doesn't need to reselct, though it's possible it
// may change after reset. Since it's not
//
// For now, we'll use the following procedure for ESP32-S2 and ESP32-S3:
// 1. Install the bin file
// 2. Reset the board
// (if version 8.0.0-beta.6 or later)
// 3. Generate the settings.toml file
// 4. Write the settings.toml to the board via the REPL
// 5. Reset the board again
//
// For the esp32 and esp32c3, the procedure may be slightly different and going through the
// REPL may be required for the settings.toml file.
// 1. Install the bin file
// 2. Reset the board
// (if version 8.0.0-beta.6 or later)
// 3. Generate the settings.toml file
// 4. Write the settings.toml to the board via the REPL
// 5. Reset the board again
//
// To run REPL code, I may need to modularize the work I did for code.circuitpython.org
// That allows you to run code in the REPL and get the output back. I may end up creating a
// library that uses Web Serial and allows you to run code in the REPL and get the output back
// because it's very integrated into the serial recieve and send code.
//
import {html, render} from 'https://cdn.jsdelivr.net/npm/lit-html/+esm';
import {asyncAppend} from 'https://cdn.jsdelivr.net/npm/lit-html/directives/async-append/+esm';
import * as esptoolPackage from "https://cdn.jsdelivr.net/npm/esp-web-flasher@5.1.2/dist/web/index.js/+esm"
export const ESP_ROM_BAUD = 115200;
@ -141,11 +114,15 @@ export class InstallButton extends HTMLButtonElement {
if (!InstallButton.isSupported) {
await this.showNotSupported();
} else {
await this.showMenu();
await this.buttonClickHandler(e);
}
});
}
async buttonClickHandler(e) {
await this.showMenu();
}
// Parse out the url parameters from the current url
getUrlParams() {
// This should look for and validate very specific values

View file

@ -3,13 +3,12 @@
// SPDX-License-Identifier: MIT
'use strict';
import { html } from 'https://unpkg.com/lit-html?module';
import * as toml from "https://unpkg.com/iarna-toml-esm@3.0.5/toml-esm.mjs"
import { html } from 'https://cdn.jsdelivr.net/npm/lit-html/+esm';
import { map } from 'https://cdn.jsdelivr.net/npm/lit-html/directives/map/+esm';
import * as toml from "https://cdn.jsdelivr.net/npm/iarna-toml-esm@3.0.5/+esm"
import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.6.65/+esm";
import * as esptoolPackage from "https://unpkg.com/esp-web-flasher@5.1.2/dist/web/index.js?module"
//import * as esptoolPackage from "https://adafruit.github.io/Adafruit_WebSerial_ESPTool/js/modules/esptool.js"
import { REPL } from 'https://cdn.jsdelivr.net/gh/adafruit/circuitpython-repl-js@1.2.1/repl.js';
import * as esptoolPackage from "https://cdn.jsdelivr.net/npm/esp-web-flasher@5.1.2/dist/web/index.js/+esm"
import { REPL } from 'https://cdn.jsdelivr.net/gh/adafruit/circuitpython-repl-js/repl.js';
import { InstallButton, ESP_ROM_BAUD } from "./base_installer.js";
// TODO: Combine multiple steps together. For now it was easier to make them separate,
@ -45,8 +44,10 @@ export class CPInstallButton extends InstallButton {
super();
this.releaseVersion = "[version]";
this.boardName = "ESP32-based device";
this.boardId = null;
this.boardIds = null;
this.selectedBoardId = null;
this.bootloaderUrl = null;
this.boardDefs = null;
this.uf2FileUrl = null;
this.binFileUrl = null;
this.releaseVersion = 0;
@ -109,67 +110,81 @@ export class CPInstallButton extends InstallButton {
}
async connectedCallback() {
// Required
this.boardId = this.getAttribute("boardid");
// Load the Board Definitions before the button is ever clicked
const response = await fetch(BOARD_DEFS);
this.boardDefs = await response.json();
let boardIds = this.getAttribute("boardid")
if (!boardIds || boardIds.trim().length === 0) {
this.boardIds = Object.keys(this.boardDefs);
} else {
this.boardIds = boardIds.split(",");
}
// If there is only one board id, then select it by default
if (this.boardIds.length === 1) {
this.selectedBoardId = this.boardIds[0];
}
// If not provided, it will use the stable release if DEFAULT_RELEASE_LATEST is false
if (this.getAttribute("version")) {
this.releaseVersion = this.getAttribute("version");
}
// Pull in the info from the json as the default values. These can be overwritten by the attributes.
const response = await fetch(BOARD_DEFS);
const boardDefs = await response.json();
let releaseInfo = null;
if (Object.keys(boardDefs).includes(this.boardId)) {
const boardDef = boardDefs[this.boardId];
this.chipFamily = boardDef.chipfamily;
if (boardDef.name) {
this.boardName = boardDef.name;
}
if (boardDef.bootloader) {
this.bootloaderUrl = this.updateBinaryUrl(boardDef.bootloader);
}
const sortedReleases = this.sortReleases(boardDef.releases);
if (this.releaseVersion) { // User specified a release
for (let release of sortedReleases) {
if (release.version == this.releaseVersion) {
releaseInfo = release;
break;
}
}
}
if (!releaseInfo) { // Release version not found or not specified
if (DEFAULT_RELEASE_LATEST) {
releaseInfo = sortedReleases[sortedReleases.length - 1];
} else {
releaseInfo = sortedReleases[0];
}
this.releaseVersion = releaseInfo.version;
}
if (releaseInfo.uf2file) {
this.uf2FileUrl = this.updateBinaryUrl(releaseInfo.uf2file);
}
if (releaseInfo.binfile) {
this.binFileUrl = this.updateBinaryUrl(releaseInfo.binfile);
}
}
// Nice to have for now
if (this.getAttribute("chipfamily")) {
this.chipFamily = this.getAttribute("chipfamily");
}
if (this.getAttribute("boardname")) {
this.boardName = this.getAttribute("boardname");
}
this.menuTitle = `CircuitPython Installer for ${this.boardName}`;
super.connectedCallback();
}
async loadBoard(boardId) {
// Pull in the info from the json as the default values. These can be overwritten by the attributes.
let releaseInfo = null;
if (Object.keys(this.boardDefs).includes(boardId)) {
const boardDef = this.boardDefs[boardId];
this.chipFamily = boardDef.chipfamily;
if (boardDef.name) {
this.boardName = boardDef.name;
}
if (boardDef.bootloader) {
this.bootloaderUrl = this.updateBinaryUrl(boardDef.bootloader);
}
const sortedReleases = this.sortReleases(boardDef.releases);
if (this.releaseVersion) { // User specified a release
for (let release of sortedReleases) {
if (release.version == this.releaseVersion) {
releaseInfo = release;
break;
}
}
}
if (!releaseInfo) { // Release version not found or not specified
if (DEFAULT_RELEASE_LATEST) {
releaseInfo = sortedReleases[sortedReleases.length - 1];
} else {
releaseInfo = sortedReleases[0];
}
this.releaseVersion = releaseInfo.version;
}
if (releaseInfo.uf2file) {
this.uf2FileUrl = this.updateBinaryUrl(releaseInfo.uf2file);
}
if (releaseInfo.binfile) {
this.binFileUrl = this.updateBinaryUrl(releaseInfo.binfile);
}
}
// Nice to have for now
if (this.getAttribute("chipfamily")) {
this.chipFamily = this.getAttribute("chipfamily");
}
if (this.getAttribute("boardname")) {
this.boardName = this.getAttribute("boardname");
}
this.menuTitle = `CircuitPython Installer for ${this.boardName}`;
}
attributeChangedCallback(attribute, previousValue, currentValue) {
const classVar = attrMap[attribute];
this[classVar] = currentValue ? this.updateBinaryUrl(currentValue) : null;
@ -227,6 +242,25 @@ export class CPInstallButton extends InstallButton {
// This is the data for the CircuitPython specific dialogs. Some are reused.
cpDialogs = {
boardSelect: {
closeable: true,
template: (data) => html`
<p>
There are multiple boards are available. Select the board you have:
</p>
<p>
<select id="availableBoards">
<option value="0"> - boards - </option>
${map(data.boards, (board, index) => html`<option value="${board.id}" ${board.id == data.default ? "selected" : ""}>${board.name}</option>`)}
</select>
</p>
`,
buttons: [{
label: "Select Board",
onClick: this.selectBoardHandler,
isEnabled: async () => { return this.currentDialogElement.querySelector("#availableBoards").value != "0" },
}],
},
welcome: {
closeable: true,
template: (data) => html`
@ -397,6 +431,32 @@ export class CPInstallButton extends InstallButton {
},
}
getBoardName(boardId) {
if (Object.keys(this.boardDefs).includes(boardId)) {
return this.boardDefs[boardId].name;
}
return null;
}
getBoardOptions() {
let options = [];
for (let boardId of this.boardIds) {
options.push({id: boardId, name: this.getBoardName(boardId)});
}
options.sort((a, b) => {
let boardA = a.name.trim().toLowerCase();
let boardB = b.name.trim().toLowerCase();
if (boardA < boardB) {
return -1;
}
if (boardA > boardB) {
return 1;
}
return 0;
});
return options;
}
////////// STEP FUNCTIONS //////////
@ -726,6 +786,35 @@ export class CPInstallButton extends InstallButton {
}
}
async buttonClickHandler(e, skipBoardSelector = false) {
if (this.boardIds.length > 1 && (!this.selectedBoardId || !skipBoardSelector)) {
this.showDialog(this.dialogs.boardSelect, {
boards: this.getBoardOptions(),
default: this.selectedBoardId,
});
this.currentDialogElement.querySelector("#availableBoards").addEventListener(
"change", this.updateButtons.bind(this)
);
return;
}
await this.loadBoard(this.selectedBoardId);
super.buttonClickHandler(e);
}
async selectBoardHandler(e) {
const selectedValue = this.currentDialogElement.querySelector("#availableBoards").value;
if (Object.keys(this.boardDefs).includes(selectedValue)) {
this.selectedBoardId = selectedValue;
this.closeDialog();
this.buttonClickHandler(null, true);
}
}
//////////////// FILE HELPERS ////////////////
async getBootDriveName() {
@ -1259,5 +1348,4 @@ export class CPInstallButton extends InstallButton {
}
}
customElements.define('cp-install-button', CPInstallButton, {extends: "button"});

View file

@ -3,36 +3,9 @@
// SPDX-License-Identifier: MIT
'use strict';
import {html, render} from 'https://unpkg.com/lit-html?module';
import {asyncAppend} from 'https://unpkg.com/lit-html/directives/async-append?module';
import * as esptoolPackage from "https://unpkg.com/esp-web-flasher@5.1.2/dist/web/index.js?module"
// TODO: Figure out how to make the Web Serial from ESPTool and Web Serial to communicate with CircuitPython not conflict
// I think at the very least we'll have to reuse the same port so the user doesn't need to reselct, though it's possible it
// may change after reset. Since it's not
//
// For now, we'll use the following procedure for ESP32-S2 and ESP32-S3:
// 1. Install the bin file
// 2. Reset the board
// (if version 8.0.0-beta.6 or later)
// 3. Generate the settings.toml file
// 4. Write the settings.toml to the board via the REPL
// 5. Reset the board again
//
// For the esp32 and esp32c3, the procedure may be slightly different and going through the
// REPL may be required for the settings.toml file.
// 1. Install the bin file
// 2. Reset the board
// (if version 8.0.0-beta.6 or later)
// 3. Generate the settings.toml file
// 4. Write the settings.toml to the board via the REPL
// 5. Reset the board again
//
// To run REPL code, I may need to modularize the work I did for code.circuitpython.org
// That allows you to run code in the REPL and get the output back. I may end up creating a
// library that uses Web Serial and allows you to run code in the REPL and get the output back
// because it's very integrated into the serial recieve and send code.
//
import {html, render} from 'https://cdn.jsdelivr.net/npm/lit-html/+esm';
import {asyncAppend} from 'https://cdn.jsdelivr.net/npm/lit-html/directives/async-append/+esm';
import * as esptoolPackage from "https://cdn.jsdelivr.net/npm/esp-web-flasher@5.1.2/dist/web/index.js/+esm"
export const ESP_ROM_BAUD = 115200;
@ -141,11 +114,15 @@ export class InstallButton extends HTMLButtonElement {
if (!InstallButton.isSupported) {
await this.showNotSupported();
} else {
await this.showMenu();
await this.buttonClickHandler(e);
}
});
}
async buttonClickHandler(e) {
await this.showMenu();
}
// Parse out the url parameters from the current url
getUrlParams() {
// This should look for and validate very specific values

File diff suppressed because one or more lines are too long

208
dist/cpinstaller.js vendored
View file

@ -3,13 +3,12 @@
// SPDX-License-Identifier: MIT
'use strict';
import { html } from 'https://unpkg.com/lit-html?module';
import * as toml from "https://unpkg.com/iarna-toml-esm@3.0.5/toml-esm.mjs"
import { html } from 'https://cdn.jsdelivr.net/npm/lit-html/+esm';
import { map } from 'https://cdn.jsdelivr.net/npm/lit-html/directives/map/+esm';
import * as toml from "https://cdn.jsdelivr.net/npm/iarna-toml-esm@3.0.5/+esm"
import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.6.65/+esm";
import * as esptoolPackage from "https://unpkg.com/esp-web-flasher@5.1.2/dist/web/index.js?module"
//import * as esptoolPackage from "https://adafruit.github.io/Adafruit_WebSerial_ESPTool/js/modules/esptool.js"
import { REPL } from 'https://cdn.jsdelivr.net/gh/adafruit/circuitpython-repl-js@1.2.1/repl.js';
import * as esptoolPackage from "https://cdn.jsdelivr.net/npm/esp-web-flasher@5.1.2/dist/web/index.js/+esm"
import { REPL } from 'https://cdn.jsdelivr.net/gh/adafruit/circuitpython-repl-js/repl.js';
import { InstallButton, ESP_ROM_BAUD } from "./base_installer.js";
// TODO: Combine multiple steps together. For now it was easier to make them separate,
@ -45,8 +44,10 @@ export class CPInstallButton extends InstallButton {
super();
this.releaseVersion = "[version]";
this.boardName = "ESP32-based device";
this.boardId = null;
this.boardIds = null;
this.selectedBoardId = null;
this.bootloaderUrl = null;
this.boardDefs = null;
this.uf2FileUrl = null;
this.binFileUrl = null;
this.releaseVersion = 0;
@ -109,67 +110,81 @@ export class CPInstallButton extends InstallButton {
}
async connectedCallback() {
// Required
this.boardId = this.getAttribute("boardid");
// Load the Board Definitions before the button is ever clicked
const response = await fetch(BOARD_DEFS);
this.boardDefs = await response.json();
let boardIds = this.getAttribute("boardid")
if (!boardIds || boardIds.trim().length === 0) {
this.boardIds = Object.keys(this.boardDefs);
} else {
this.boardIds = boardIds.split(",");
}
// If there is only one board id, then select it by default
if (this.boardIds.length === 1) {
this.selectedBoardId = this.boardIds[0];
}
// If not provided, it will use the stable release if DEFAULT_RELEASE_LATEST is false
if (this.getAttribute("version")) {
this.releaseVersion = this.getAttribute("version");
}
// Pull in the info from the json as the default values. These can be overwritten by the attributes.
const response = await fetch(BOARD_DEFS);
const boardDefs = await response.json();
let releaseInfo = null;
if (Object.keys(boardDefs).includes(this.boardId)) {
const boardDef = boardDefs[this.boardId];
this.chipFamily = boardDef.chipfamily;
if (boardDef.name) {
this.boardName = boardDef.name;
}
if (boardDef.bootloader) {
this.bootloaderUrl = this.updateBinaryUrl(boardDef.bootloader);
}
const sortedReleases = this.sortReleases(boardDef.releases);
if (this.releaseVersion) { // User specified a release
for (let release of sortedReleases) {
if (release.version == this.releaseVersion) {
releaseInfo = release;
break;
}
}
}
if (!releaseInfo) { // Release version not found or not specified
if (DEFAULT_RELEASE_LATEST) {
releaseInfo = sortedReleases[sortedReleases.length - 1];
} else {
releaseInfo = sortedReleases[0];
}
this.releaseVersion = releaseInfo.version;
}
if (releaseInfo.uf2file) {
this.uf2FileUrl = this.updateBinaryUrl(releaseInfo.uf2file);
}
if (releaseInfo.binfile) {
this.binFileUrl = this.updateBinaryUrl(releaseInfo.binfile);
}
}
// Nice to have for now
if (this.getAttribute("chipfamily")) {
this.chipFamily = this.getAttribute("chipfamily");
}
if (this.getAttribute("boardname")) {
this.boardName = this.getAttribute("boardname");
}
this.menuTitle = `CircuitPython Installer for ${this.boardName}`;
super.connectedCallback();
}
async loadBoard(boardId) {
// Pull in the info from the json as the default values. These can be overwritten by the attributes.
let releaseInfo = null;
if (Object.keys(this.boardDefs).includes(boardId)) {
const boardDef = this.boardDefs[boardId];
this.chipFamily = boardDef.chipfamily;
if (boardDef.name) {
this.boardName = boardDef.name;
}
if (boardDef.bootloader) {
this.bootloaderUrl = this.updateBinaryUrl(boardDef.bootloader);
}
const sortedReleases = this.sortReleases(boardDef.releases);
if (this.releaseVersion) { // User specified a release
for (let release of sortedReleases) {
if (release.version == this.releaseVersion) {
releaseInfo = release;
break;
}
}
}
if (!releaseInfo) { // Release version not found or not specified
if (DEFAULT_RELEASE_LATEST) {
releaseInfo = sortedReleases[sortedReleases.length - 1];
} else {
releaseInfo = sortedReleases[0];
}
this.releaseVersion = releaseInfo.version;
}
if (releaseInfo.uf2file) {
this.uf2FileUrl = this.updateBinaryUrl(releaseInfo.uf2file);
}
if (releaseInfo.binfile) {
this.binFileUrl = this.updateBinaryUrl(releaseInfo.binfile);
}
}
// Nice to have for now
if (this.getAttribute("chipfamily")) {
this.chipFamily = this.getAttribute("chipfamily");
}
if (this.getAttribute("boardname")) {
this.boardName = this.getAttribute("boardname");
}
this.menuTitle = `CircuitPython Installer for ${this.boardName}`;
}
attributeChangedCallback(attribute, previousValue, currentValue) {
const classVar = attrMap[attribute];
this[classVar] = currentValue ? this.updateBinaryUrl(currentValue) : null;
@ -227,6 +242,25 @@ export class CPInstallButton extends InstallButton {
// This is the data for the CircuitPython specific dialogs. Some are reused.
cpDialogs = {
boardSelect: {
closeable: true,
template: (data) => html`
<p>
There are multiple boards are available. Select the board you have:
</p>
<p>
<select id="availableBoards">
<option value="0"> - boards - </option>
${map(data.boards, (board, index) => html`<option value="${board.id}" ${board.id == data.default ? "selected" : ""}>${board.name}</option>`)}
</select>
</p>
`,
buttons: [{
label: "Select Board",
onClick: this.selectBoardHandler,
isEnabled: async () => { return this.currentDialogElement.querySelector("#availableBoards").value != "0" },
}],
},
welcome: {
closeable: true,
template: (data) => html`
@ -397,6 +431,32 @@ export class CPInstallButton extends InstallButton {
},
}
getBoardName(boardId) {
if (Object.keys(this.boardDefs).includes(boardId)) {
return this.boardDefs[boardId].name;
}
return null;
}
getBoardOptions() {
let options = [];
for (let boardId of this.boardIds) {
options.push({id: boardId, name: this.getBoardName(boardId)});
}
options.sort((a, b) => {
let boardA = a.name.trim().toLowerCase();
let boardB = b.name.trim().toLowerCase();
if (boardA < boardB) {
return -1;
}
if (boardA > boardB) {
return 1;
}
return 0;
});
return options;
}
////////// STEP FUNCTIONS //////////
@ -726,6 +786,35 @@ export class CPInstallButton extends InstallButton {
}
}
async buttonClickHandler(e, skipBoardSelector = false) {
if (this.boardIds.length > 1 && (!this.selectedBoardId || !skipBoardSelector)) {
this.showDialog(this.dialogs.boardSelect, {
boards: this.getBoardOptions(),
default: this.selectedBoardId,
});
this.currentDialogElement.querySelector("#availableBoards").addEventListener(
"change", this.updateButtons.bind(this)
);
return;
}
await this.loadBoard(this.selectedBoardId);
super.buttonClickHandler(e);
}
async selectBoardHandler(e) {
const selectedValue = this.currentDialogElement.querySelector("#availableBoards").value;
if (Object.keys(this.boardDefs).includes(selectedValue)) {
this.selectedBoardId = selectedValue;
this.closeDialog();
this.buttonClickHandler(null, true);
}
}
//////////////// FILE HELPERS ////////////////
async getBootDriveName() {
@ -1259,5 +1348,4 @@ export class CPInstallButton extends InstallButton {
}
}
customElements.define('cp-install-button', CPInstallButton, {extends: "button"});

File diff suppressed because one or more lines are too long