Adds build step using vite and breaks code into JS6 modules (#14)

This is a large update. I've selected Vite as the build system for this project. I tried other tools like golang minify but this had the most useful features and serves as a dev server too. I think a lot can be done to shore up the management of global state in the application. For now this works.

closes #13

#### Changes
- adds vite for dev/build
- Breaks out JS into JS6 modules
- Updates docker file to have two stage build

Co-authored-by: Drew Bednar <drew@androiddrew.com>
Reviewed-on: #14
pull/16/head
Drew Bednar 8 months ago
parent 8f070a4e45
commit f381147f16

@ -1,3 +1,3 @@
Dockerfile
Makefile
README.md
dist/
node_modules/

3
.gitignore vendored

@ -1,2 +1,3 @@
node_modules/
dist/*.{html,js,css,ico}
dist/
examples/

@ -1,2 +1,9 @@
FROM nginx
COPY ./src/index.html ./src/script.js ./src/main.css /usr/share/nginx/html
FROM node:20.16.0 AS build-stage
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN make build
FROM nginx AS runtime-stage
COPY --from=build-stage /build/dist/* /usr/share/nginx/html/

@ -1,5 +1,5 @@
serve:
python3 -m http.server 8080
npx vite
.PHONY: serve
watch:
@ -7,7 +7,7 @@ watch:
.PHONY: watch
build:
echo "Not implemented yet..."
npx vite build --emptyOutDir
.PHONY: build
image:

426
package-lock.json generated

@ -8,6 +8,167 @@
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true
},
"@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
"dev": true,
"optional": true
},
"@esbuild/android-arm": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
"dev": true,
"optional": true
},
"@esbuild/android-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
"dev": true,
"optional": true
},
"@esbuild/android-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
"dev": true,
"optional": true
},
"@esbuild/darwin-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
"dev": true,
"optional": true
},
"@esbuild/darwin-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
"dev": true,
"optional": true
},
"@esbuild/linux-ia32": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
"dev": true,
"optional": true
},
"@esbuild/linux-mips64el": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
"dev": true,
"optional": true
},
"@esbuild/linux-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
"dev": true,
"optional": true
},
"@esbuild/linux-riscv64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
"dev": true,
"optional": true
},
"@esbuild/linux-s390x": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
"dev": true,
"optional": true
},
"@esbuild/linux-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
"dev": true,
"optional": true
},
"@esbuild/netbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
"dev": true,
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
"dev": true,
"optional": true
},
"@esbuild/sunos-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
"dev": true,
"optional": true
},
"@esbuild/win32-arm64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
"dev": true,
"optional": true
},
"@esbuild/win32-ia32": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
"dev": true,
"optional": true
},
"@esbuild/win32-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
"dev": true,
"optional": true
},
"@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -94,6 +255,124 @@
"dev": true,
"optional": true
},
"@rollup/rollup-android-arm-eabi": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz",
"integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==",
"dev": true,
"optional": true
},
"@rollup/rollup-android-arm64": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz",
"integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==",
"dev": true,
"optional": true
},
"@rollup/rollup-darwin-arm64": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz",
"integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==",
"dev": true,
"optional": true
},
"@rollup/rollup-darwin-x64": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz",
"integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==",
"dev": true,
"optional": true
},
"@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz",
"integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==",
"dev": true,
"optional": true
},
"@rollup/rollup-linux-arm-musleabihf": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz",
"integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==",
"dev": true,
"optional": true
},
"@rollup/rollup-linux-arm64-gnu": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz",
"integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==",
"dev": true,
"optional": true
},
"@rollup/rollup-linux-arm64-musl": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz",
"integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==",
"dev": true,
"optional": true
},
"@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz",
"integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==",
"dev": true,
"optional": true
},
"@rollup/rollup-linux-riscv64-gnu": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz",
"integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==",
"dev": true,
"optional": true
},
"@rollup/rollup-linux-s390x-gnu": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz",
"integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==",
"dev": true,
"optional": true
},
"@rollup/rollup-linux-x64-gnu": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz",
"integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==",
"dev": true,
"optional": true
},
"@rollup/rollup-linux-x64-musl": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz",
"integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==",
"dev": true,
"optional": true
},
"@rollup/rollup-win32-arm64-msvc": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz",
"integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==",
"dev": true,
"optional": true
},
"@rollup/rollup-win32-ia32-msvc": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz",
"integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==",
"dev": true,
"optional": true
},
"@rollup/rollup-win32-x64-msvc": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz",
"integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==",
"dev": true,
"optional": true
},
"@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
@ -128,6 +407,20 @@
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true
},
"autoprefixer": {
"version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
"dev": true,
"requires": {
"browserslist": "^4.23.3",
"caniuse-lite": "^1.0.30001646",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.1",
"postcss-value-parser": "^4.2.0"
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -158,12 +451,30 @@
"fill-range": "^7.1.1"
}
},
"browserslist": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001646",
"electron-to-chromium": "^1.5.4",
"node-releases": "^2.0.18",
"update-browserslist-db": "^1.1.0"
}
},
"camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001651",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
"integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
"dev": true
},
"chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@ -247,12 +558,55 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"electron-to-chromium": {
"version": "1.5.6",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz",
"integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==",
"dev": true
},
"emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"requires": {
"@esbuild/aix-ppc64": "0.21.5",
"@esbuild/android-arm": "0.21.5",
"@esbuild/android-arm64": "0.21.5",
"@esbuild/android-x64": "0.21.5",
"@esbuild/darwin-arm64": "0.21.5",
"@esbuild/darwin-x64": "0.21.5",
"@esbuild/freebsd-arm64": "0.21.5",
"@esbuild/freebsd-x64": "0.21.5",
"@esbuild/linux-arm": "0.21.5",
"@esbuild/linux-arm64": "0.21.5",
"@esbuild/linux-ia32": "0.21.5",
"@esbuild/linux-loong64": "0.21.5",
"@esbuild/linux-mips64el": "0.21.5",
"@esbuild/linux-ppc64": "0.21.5",
"@esbuild/linux-riscv64": "0.21.5",
"@esbuild/linux-s390x": "0.21.5",
"@esbuild/linux-x64": "0.21.5",
"@esbuild/netbsd-x64": "0.21.5",
"@esbuild/openbsd-x64": "0.21.5",
"@esbuild/sunos-x64": "0.21.5",
"@esbuild/win32-arm64": "0.21.5",
"@esbuild/win32-ia32": "0.21.5",
"@esbuild/win32-x64": "0.21.5"
}
},
"escalade": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"dev": true
},
"fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@ -305,6 +659,12 @@
"signal-exit": "^4.0.1"
}
},
"fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"dev": true
},
"fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -483,12 +843,24 @@
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true
},
"node-releases": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
"dev": true
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -668,6 +1040,32 @@
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true
},
"rollup": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz",
"integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==",
"dev": true,
"requires": {
"@rollup/rollup-android-arm-eabi": "4.20.0",
"@rollup/rollup-android-arm64": "4.20.0",
"@rollup/rollup-darwin-arm64": "4.20.0",
"@rollup/rollup-darwin-x64": "4.20.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.20.0",
"@rollup/rollup-linux-arm-musleabihf": "4.20.0",
"@rollup/rollup-linux-arm64-gnu": "4.20.0",
"@rollup/rollup-linux-arm64-musl": "4.20.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.20.0",
"@rollup/rollup-linux-riscv64-gnu": "4.20.0",
"@rollup/rollup-linux-s390x-gnu": "4.20.0",
"@rollup/rollup-linux-x64-gnu": "4.20.0",
"@rollup/rollup-linux-x64-musl": "4.20.0",
"@rollup/rollup-win32-arm64-msvc": "4.20.0",
"@rollup/rollup-win32-ia32-msvc": "4.20.0",
"@rollup/rollup-win32-x64-msvc": "4.20.0",
"@types/estree": "1.0.5",
"fsevents": "~2.3.2"
}
},
"run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@ -797,9 +1195,9 @@
"dev": true
},
"tailwindcss": {
"version": "3.4.9",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz",
"integrity": "sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==",
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
"integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==",
"dev": true,
"requires": {
"@alloc/quick-lru": "^5.2.0",
@ -859,12 +1257,34 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true
},
"update-browserslist-db": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
"integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
"dev": true,
"requires": {
"escalade": "^3.1.2",
"picocolors": "^1.0.1"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"vite": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz",
"integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==",
"dev": true,
"requires": {
"esbuild": "^0.21.3",
"fsevents": "~2.3.3",
"postcss": "^8.4.40",
"rollup": "^4.13.0"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

@ -1,5 +1,8 @@
{
"devDependencies": {
"tailwindcss": "^3.4.9"
"autoprefixer": "^10.4.20",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.10",
"vite": "^5.4.0"
}
}
}

@ -0,0 +1,7 @@
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

@ -0,0 +1,10 @@
import * as globals from './globals.js';
import { setupEventListeners } from './eventListeners.js';
import { updateSerialSelect } from './uiHelpers.js';
document.addEventListener('DOMContentLoaded', async () => {
const ports = await navigator.serial.getPorts();
globals.setSerialPorts(ports);
updateSerialSelect(ports);
setupEventListeners();
});

@ -0,0 +1,67 @@
import * as globals from './globals.js';
import { connectToSerialPort, transmitContents } from './serialCommunication.js';
import { scrollToBottom, updateSerialSelect, onPortDisconnect } from './uiHelpers.js';
const uPyKeyboardInterrupt = new Uint8Array([13, 3, 3]);
export function setupEventListeners() {
globals.addPort.addEventListener('click', async () => {
const port = await navigator.serial.requestPort();
globals.serialPorts.push(port);
updateSerialSelect(globals.serialPorts);
});
globals.autoscrollCheckbox.addEventListener('change', (e) => {
globals.setAutoscroll(e.target.checked);
if (globals.autoscroll) {
scrollToBottom();
}
});
globals.clearButton.addEventListener('click', () => {
while (globals.scrollableElement.firstChild) {
globals.scrollableElement.removeChild(globals.scrollableElement.firstChild);
}
});
globals.connectButton.addEventListener('click', async () => {
const selectedPort = globals.serialPorts[globals.select.selectedIndex];
const baudRate = Math.round(globals.baud.value);
if (!globals.isPortConnected) {
console.log("selected port: ", selectedPort)
console.log("Baud Rate: ", baudRate)
await connectToSerialPort(selectedPort, baudRate);
} else {
console.log("Disconnecting from port...")
globals.setPortConnected(false);
onPortDisconnect();
}
});
globals.refreshPorts.addEventListener('click', async () => {
const ports = await navigator.serial.getPorts();
console.log(ports)
globals.setSerialPorts(ports)
updateSerialSelect(globals.serialPorts)
});
globals.transmitButton.addEventListener('click', async (event) => {
transmitContents(globals.transmitInput.innerText)
globals.transmitInput.innerHTML = ''
});
globals.transmitInput.addEventListener('keydown', async (event) => {
if (event.key === 'c' && event.ctrlKey) {
event.preventDefault();
console.log("Sending interrupt ", uPyKeyboardInterrupt)
await globals.writer.write(uPyKeyboardInterrupt);
}
});
globals.transmitInput.addEventListener('keyup', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
transmitContents(globals.transmitInput.innerText);
globals.transmitInput.innerHTML = ''
}
});
}

@ -0,0 +1,71 @@
export const addDeviceMessage = `Add a device...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`;
export const addPort = document.getElementById('add-port');
export const autoscrollCheckbox = document.getElementById('autoscroll-checkbox');
export const baud = document.getElementById('baud');
export const clearButton = document.getElementById('clear-button');
export const connectButton = document.getElementById('connect-button');
export const refreshPorts = document.getElementById('refresh-ports');
export const scrollableElement = document.getElementById('scrollable-element');
export const select = document.getElementById('serial-select');
export const transmitInput = document.querySelector('div[contenteditable="true"]');
export const transmitButton = document.getElementById('transmit-button');
export const uPyKeyboardInterrupt = new Uint8Array([13, 3, 3]);
export let controller;
export let isPortConnected = false;
export let autoscroll = true;
export let serialPorts = [];
export let encoder;
export let reader;
export let writer;
export function setController(newController) {
controller = newController
}
export function setPortConnected(isConnected) {
isPortConnected = isConnected;
}
export function setAutoscroll(isSet) {
autoscroll = isSet;
console.log('Set autoscroll: ', autoscroll)
}
export function setSerialPorts(ports) {
serialPorts = ports
console.log('Set ports: ', serialPorts)
}
export function getEncoder() {
return encoder;
}
export function setEncoder(newEncoder) {
encoder = newEncoder;
}
export function getReader() {
return reader;
}
export function setReader(newReader) {
reader = newReader;
}
export function getWriter() {
return writer;
}
export function setWriter(newWriter) {
writer = newWriter;
}
// expose some globals for debuggin in webconsole
if (typeof window !== 'undefined') {
window.wsc = {
isPortConnected,
autoscroll,
serialPorts,
};
}

@ -55,17 +55,6 @@
</div>
</div>
<div class="font-mono rounded-md bg-white shadow-xl h-[calc(75vh-70px)] overflow-y-auto border border-gray-300 p-2.5 bg-gray-200 mx-5 mb-6 " id="scrollable-element"></div>
<!-- Transmit OLD-->
<!-- <div class="flex items-center space-x-2 px-5 mb-5">
<input type="text" id="transmit-input" class="font-mono flex-grow font-medium bg-white rounded-md py-1.5 px-3 border-0 border-gray-300 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-emerald-500 sm:text-sm sm:leading-6" placeholder="Enter text to send...">
<button id="transmit-button" class="relative inline-flex items-center gap-x-1.5 px-4 py-1.5 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">
<svg class="-ml-0.5 h-5 w-5 text-white" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M2 3.75A.75.75 0 012.75 3h11.5a.75.75 0 010 1.5H2.75A.75.75 0 012 3.75zM2 7.5a.75.75 0 01.75-.75h6.365a.75.75 0 010 1.5H2.75A.75.75 0 012 7.5zM14 7a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02l-1.95-2.1v6.59a.75.75 0 01-1.5 0V9.66l-1.95 2.1a.75.75 0 11-1.1-1.02l3.25-3.5A.75.75 0 0114 7zM2 11.25a.75.75 0 01.75-.75H7A.75.75 0 017 12H2.75a.75.75 0 01-.75-.75z" clip-rule="evenodd" />
</svg>
Send</button>
</div> -->
<!-- End Transmit OLD-->
<!-- Experiment -->
<div class="flex items-center space-x-2 px-5 mb-5">
<div contenteditable="true" class="shadow-xl flex-grow rounded-md font-mono py-1.5 px-3 ring-1 ring-inset ring-gray-300 text-gray-900 outline-none focus:ring-2 focus:ring-emerald-500">
<p data-placeholder="Enter text to send..." class="empty:before:content-[attr(data-placeholder)] empty:before:text-gray-500"></p>
@ -77,29 +66,6 @@
Send
</button>
</div>
<!-- <div class="flex items-center space-x-2 px-5 mb-5">
<div aria-label="Enter text to send..." class="flex-grow mt-1 max-h-96 w-full overflow-y-auto break-words">
<div contenteditable="true" translate="no" enterkeyhint="enter" tabindex="0"
class="font-mono flex-grow font-medium bg-white rounded-md py-1.5 px-3 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-emerald-500 sm:text-sm sm:leading-6 break-words max-w-[60ch]">
<p data-placeholder="Enter text to send..."
class="m-0 text-gray-900 focus:outline-none empty:before:content-[attr(data-placeholder)] empty:before:text-gray-500">
</p>
</div>
</div>
<button id="transmit-button" class="relative inline-flex items-center gap-x-1.5 px-4 py-1.5 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">
<svg class="-ml-0.5 h-5 w-5 text-white" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M2 3.75A.75.75 0 012.75 3h11.5a.75.75 0 010 1.5H2.75A.75.75 0 012 3.75zM2 7.5a.75.75 0 01.75-.75h6.365a.75.75 0 010 1.5H2.75A.75.75 0 012 7.5zM14 7a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02l-1.95-2.1v6.59a.75.75 0 01-1.5 0V9.66l-1.95 2.1a.75.75 0 11-1.1-1.02l3.25-3.5A.75.75 0 0114 7zM2 11.25a.75.75 0 01.75-.75H7A.75.75 0 017 12H2.75a.75.75 0 01-.75-.75z" clip-rule="evenodd" />
</svg>
Send
</button>
</div> -->
<!-- End experiment -->
<!-- <div aria-label="Enter text to send..." class="mt-1 max-h-96 w-full overflow-y-auto break-words">
<div contenteditable="true" translate="no" enterkeyhint="enter" tabindex="0" class="break-words max-w-[60ch]">
<p data-placeholder="Enter text to send..." class="text-gray-900 ring-1 ring-gray-300 font-mono ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-emerald-500 is-empty is-editor-empty before:!text-text-500 before:whitespace-nowrap"><br class="ProseMirror-trailingBreak"></p>
</div>
</div> -->
</div>
<footer>
<div class="mx-auto max-w-7xl overflow-hidden py-12 px-4 sm:px-6 lg:px-8">
@ -110,6 +76,6 @@
</p>
</div>
</footer>
<script src="script.js"></script>
<script type="module" src="./app.js"></script>
</body>
</html>

@ -1,230 +0,0 @@
// Globals
const addDeviceMessage = `Add a device...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`
const addPort = document.getElementById('add-port')
const autoscrollCheckbox = document.getElementById('autoscroll-checkbox');
const baud = document.getElementById('baud');
const clearButton = document.getElementById('clear-button');
const connectButton = document.getElementById('connect-button')
const refreshPorts = document.getElementById('refresh-ports');
const scrollableElement = document.getElementById('scrollable-element');
const select = document.getElementById('serial-select');
const transmitInput = document.querySelector('div[contenteditable="true"]');
const transmitButton = document.getElementById('transmit-button');
const uPyKeyboardInterrupt = new Uint8Array([13, 3, 3])
let isPortConnected = false;
let controller;
let autoscroll = true;
let serialPorts = [];
let encoder;
let reader;
let writer;
// Event Listeners
addPort.addEventListener('click', async () => {
const port = await navigator.serial.requestPort();
serialPorts.push(port);
updateSerialSelect(serialPorts);
});
autoscrollCheckbox.addEventListener('change', (e) => {
autoscroll = e.target.checked;
if (autoscroll) {
scrollToBottom();
}
});
clearButton.addEventListener('click', () => {
while (scrollableElement.firstChild) {
scrollableElement.removeChild(scrollableElement.firstChild);
}
});
connectButton.addEventListener('click', async () => {
const selectedPort = serialPorts[select.selectedIndex]
const baudRate = Math.round(baud.value)
if (!isPortConnected) {
await connectToSerialPort(selectedPort, baudRate);
} else {
isPortConnected = false;
onPortDisconnect();
}
});
refreshPorts.addEventListener('click', async () => {
ports = await navigator.serial.getPorts();
serialPorts = ports
updateSerialSelect(ports)
});
transmitButton.addEventListener('click', async (event) => {
transmitContents(transmitInput.innerText)
transmitInput.innerHTML = ''
});
transmitInput.addEventListener('keydown', (event) => {
if (event.key === 'c' && event.ctrlKey) {
event.preventDefault();
console.log("Sending interrupt ", uPyKeyboardInterrupt)
writer.write(uPyKeyboardInterrupt);
}
});
transmitInput.addEventListener('keyup', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
transmitContents(transmitInput.innerText);
transmitInput.innerHTML = ''
}
});
// Functions
function transmitContents(input) {
encoded_string = encoder.encode(input + '\r')
console.log("Binary Contents: ", encoded_string)
writer.write(encoded_string)
}
function onPortConnect() {
connectButton.classList.remove('bg-emerald-500', 'hover:bg-emerald-600', 'focus:ring-emerald-500');
connectButton.classList.add('bg-red-500', 'hover:bg-red-600', 'focus:ring-red-500');
connectButton.textContent = 'Disconnect';
}
function onPortDisconnect() {
connectButton.classList.remove('bg-red-500', 'hover:bg-red-600', 'focus:ring-red-500');
connectButton.classList.add('bg-emerald-500', 'hover:bg-emerald-600', 'focus:ring-emerald-500');
connectButton.textContent = 'Connect';
}
function buildPortOption(port) {
const option = document.createElement('option');
option.value = port;
try {
const info = port.getInfo();
if (info && 'usbVendorId' in info && 'usbProductId' in info) {
const { usbVendorId, usbProductId } = info;
option.text = `Device ${usbVendorId}:${usbProductId}`;
} else {
console.error('getInfo() did not return expected properties:', info);
option.text = 'Unknown Device';
}
} catch (error) {
console.error('Error retrieving port information:', error);
option.text = 'Unknown Device';
}
return option;
}
function addText(text) {
const newText = document.createElement('p');
newText.textContent = `${new Date().toLocaleTimeString()} ${text}`;
scrollableElement.appendChild(newText);
if (autoscroll) {
scrollToBottom();
}
}
function scrollToBottom() {
scrollableElement.scrollTop = scrollableElement.scrollHeight;
}
// Async Functions
async function connectToSerialPort(port, baud) {
await port.open({ baudRate: baud });
onPortConnect()
let buffer = ''
// The TextDecoderStream interface of the Encoding API converts a stream of text in a binary encoding,
// such as UTF-8 etc., to a stream of strings
// const textEncoder = new TextEncoderStream();
// const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
// writer = textEncoder.writable.getWriter();
encoder = new TextEncoder()
writer = port.writable.getWriter();
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
reader = textDecoder.readable.getReader();
controller = new AbortController();
const signal = controller.signal;
try {
isPortConnected = true;
while (isPortConnected) {
const { value, done } = await reader.read({ signal });
if (done || !port.readable) {
break;
}
buffer += value;
while (buffer.includes('\n')) {
const newlineIndex = buffer.indexOf('\n');
const line = buffer.slice(0, newlineIndex);
buffer = buffer.slice(newlineIndex + 1);
addText(line);
}
}
} catch (error) {
console.error('Error reading data from serial port:', error);
} finally {
// Port cleanup
controller.abort();
writer.releaseLock();
reader.releaseLock();
// try {
// writer.close();
// await writableStreamClosed;
// } catch (error) {
// console.error("Error encountered in writeableSteamClosed:", error)
// }
try {
reader.cancel();
} catch (error) {
console.error("Error encountered in readableStreamClosed:", error);
}
try {
await readableStreamClosed;
} catch (error) {
}
try {
await readableStreamClosed;
} catch (error) {
}
try {
await port.close();
} catch (error) {
console.error("Error encountered closing port:", error);
}
onPortDisconnect()
}
}
async function updateSerialSelect(ports) {
if (ports.length < 1) {
const option = document.createElement('option');
option.text = addDeviceMessage;
select.innerHTML = ''
select.appendChild(option)
return;
}
select.innerHTML = ''
ports.forEach(port => {
const option = buildPortOption(port)
select.appendChild(option);
});
}

@ -0,0 +1,73 @@
import * as globals from './globals.js';
import { onPortConnect, onPortDisconnect, addText } from './uiHelpers.js';
export async function connectToSerialPort(port, baud) {
console.log("Connecting to port: ", port);
await port.open({ baudRate: baud });
onPortConnect()
let buffer = ''
globals.setEncoder(new TextEncoder());
globals.setWriter(port.writable.getWriter());
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
globals.setReader(textDecoder.readable.getReader());
globals.setController(new AbortController());
const signal = globals.controller.signal;
try {
globals.setPortConnected(true);
while (globals.isPortConnected) {
const { value, done } = await globals.reader.read({ signal });
if (done || !port.readable) {
break;
}
buffer += value;
while (buffer.includes('\n')) {
const newlineIndex = buffer.indexOf('\n');
const line = buffer.slice(0, newlineIndex);
buffer = buffer.slice(newlineIndex + 1);
addText(line);
}
}
console.log("Exiting serial print loop")
} catch (error) {
console.error('Error reading data from serial port:', error);
} finally {
// Port cleanup
console.log("Cleaning up port.")
globals.controller.abort();
console.log("Releasing writer lock")
globals.writer.releaseLock();
// Since we are using a readableSteam we should call cancel instead of globals.reader.releaseLock();
globals.reader.cancel().then(() => {
console.log('Stream canceled');
}).catch(error => {
console.error('Error canceling the stream:', error);
});
try {
await readableStreamClosed;
} catch (error) {
}
try {
await port.close();
} catch (error) {
console.error("Error encountered closing port:", error);
}
console.log("Port closed.")
onPortDisconnect()
}
}
export function transmitContents(input) {
const encoded_string = globals.encoder.encode(input + '\r')
console.log("Binary Contents: ", encoded_string);
globals.writer.write(encoded_string);
}

@ -0,0 +1,63 @@
import * as globals from './globals.js';
export function onPortConnect() {
globals.connectButton.classList.remove('bg-emerald-500', 'hover:bg-emerald-600', 'focus:ring-emerald-500');
globals.connectButton.classList.add('bg-red-500', 'hover:bg-red-600', 'focus:ring-red-500');
globals.connectButton.textContent = 'Disconnect';
}
export function onPortDisconnect() {
globals.connectButton.classList.remove('bg-red-500', 'hover:bg-red-600', 'focus:ring-red-500');
globals.connectButton.classList.add('bg-emerald-500', 'hover:bg-emerald-600', 'focus:ring-emerald-500');
globals.connectButton.textContent = 'Connect';
}
export function buildPortOption(port) {
const option = document.createElement('option');
option.value = port;
try {
const info = port.getInfo();
if (info && 'usbVendorId' in info && 'usbProductId' in info) {
const { usbVendorId, usbProductId } = info;
option.text = `Device ${usbVendorId}:${usbProductId}`;
} else {
console.error('getInfo() did not return expected properties:', info);
option.text = 'Unknown Device';
}
} catch (error) {
console.error('Error retrieving port information:', error);
option.text = 'Unknown Device';
}
return option;
}
export function addText(text) {
const newText = document.createElement('p');
newText.textContent = `${new Date().toLocaleTimeString()} ${text}`;
globals.scrollableElement.appendChild(newText);
if (globals.autoscroll) {
scrollToBottom();
}
}
export function scrollToBottom() {
globals.scrollableElement.scrollTop = globals.scrollableElement.scrollHeight;
}
export async function updateSerialSelect(ports) {
if (ports.length < 1) {
const option = document.createElement('option');
option.text = globals.addDeviceMessage;
globals.select.innerHTML = ''
globals.select.appendChild(option)
return;
}
globals.select.innerHTML = ''
ports.forEach(port => {
const option = buildPortOption(port)
globals.select.appendChild(option);
});
}

@ -0,0 +1,29 @@
import { defineConfig } from 'vite';
import { resolve } from 'path';
export default defineConfig({
root: resolve(__dirname, 'src'),
base: './',
build: {
outDir: resolve(__dirname, 'dist'),
rollupOptions: {
input: {
main: resolve(__dirname, 'src/index.html'),
},
output: {
entryFileNames: 'script.[hash].js',
chunkFileNames: 'chunk.[hash].js',
assetFileNames: '[name].[hash].[ext]',
},
},
minify: 'esbuild',
},
css: {
postcss: {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
],
},
},
});