mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-03 00:22:49 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
f69be14b5e
|
|||
|
dce554fc66
|
|||
|
f152dbdcf5
|
|||
|
6a29e6a2fc
|
|||
|
9bf3801fef
|
|||
|
d2accfb838
|
|||
|
b8a376b93b
|
|||
|
588719df91
|
|||
|
6a0dad9dad
|
|||
|
f3704e4051
|
|||
|
3e6298717e
|
|||
|
aced0bbbb7
|
|||
|
|
36874c59e3 | ||
|
9dc61a3482
|
|||
|
d9183f952a
|
|||
|
913a833824
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 9
|
||||||
- name: 🐉 Use Node.js 22.4.x
|
- name: 🐉 Use Node.js 22.4.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ const config = {
|
|||||||
"experiment",
|
"experiment",
|
||||||
"code",
|
"code",
|
||||||
"dictionary",
|
"dictionary",
|
||||||
|
"developer_board",
|
||||||
|
"developer_board_off",
|
||||||
|
"memory",
|
||||||
],
|
],
|
||||||
codePoints: {
|
codePoints: {
|
||||||
speed: "e9e4",
|
speed: "e9e4",
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "charachorder-device-manager",
|
"name": "charachorder-device-manager",
|
||||||
"version": "2.0.1",
|
"version": "2.2.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.16",
|
"node": ">=22.4",
|
||||||
"pnpm": ">=8.6"
|
"pnpm": ">=9.4"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -62,6 +62,7 @@
|
|||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"cypress": "^13.13.2",
|
"cypress": "^13.13.2",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
|
"esptool-js": "^0.4.7",
|
||||||
"flexsearch": "^0.7.43",
|
"flexsearch": "^0.7.43",
|
||||||
"fontkit": "^2.0.4",
|
"fontkit": "^2.0.4",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
@@ -89,6 +90,7 @@
|
|||||||
"vite-plugin-mkcert": "^1.17.6",
|
"vite-plugin-mkcert": "^1.17.6",
|
||||||
"vite-plugin-pwa": "^0.20.5",
|
"vite-plugin-pwa": "^0.20.5",
|
||||||
"vitest": "^2.1.4",
|
"vitest": "^2.1.4",
|
||||||
|
"web-serial-polyfill": "^1.0.15",
|
||||||
"workbox-window": "^7.3.0"
|
"workbox-window": "^7.3.0"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module"
|
||||||
|
|||||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -92,6 +92,9 @@ importers:
|
|||||||
d3:
|
d3:
|
||||||
specifier: ^7.9.0
|
specifier: ^7.9.0
|
||||||
version: 7.9.0
|
version: 7.9.0
|
||||||
|
esptool-js:
|
||||||
|
specifier: ^0.4.7
|
||||||
|
version: 0.4.7
|
||||||
flexsearch:
|
flexsearch:
|
||||||
specifier: ^0.7.43
|
specifier: ^0.7.43
|
||||||
version: 0.7.43
|
version: 0.7.43
|
||||||
@@ -173,6 +176,9 @@ importers:
|
|||||||
vitest:
|
vitest:
|
||||||
specifier: ^2.1.4
|
specifier: ^2.1.4
|
||||||
version: 2.1.4(@types/node@20.14.10)(jsdom@25.0.1)(sass@1.80.6)(terser@5.31.1)
|
version: 2.1.4(@types/node@20.14.10)(jsdom@25.0.1)(sass@1.80.6)(terser@5.31.1)
|
||||||
|
web-serial-polyfill:
|
||||||
|
specifier: ^1.0.15
|
||||||
|
version: 1.0.15
|
||||||
workbox-window:
|
workbox-window:
|
||||||
specifier: ^7.3.0
|
specifier: ^7.3.0
|
||||||
version: 7.3.0
|
version: 7.3.0
|
||||||
@@ -1645,6 +1651,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
|
||||||
|
atob-lite@2.0.0:
|
||||||
|
resolution: {integrity: sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==}
|
||||||
|
|
||||||
autoprefixer@10.4.20:
|
autoprefixer@10.4.20:
|
||||||
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
|
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@@ -2250,6 +2259,9 @@ packages:
|
|||||||
esm-env@1.0.0:
|
esm-env@1.0.0:
|
||||||
resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==}
|
resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==}
|
||||||
|
|
||||||
|
esptool-js@0.4.7:
|
||||||
|
resolution: {integrity: sha512-xVwtSVDRsvjXSEvNFrorgJfB71RFFkZkL+hs7O7gW5hgPrKGywZxo2U5LJddzkJ6eE31QinNVyywc0OaSntZCw==}
|
||||||
|
|
||||||
esrap@1.2.2:
|
esrap@1.2.2:
|
||||||
resolution: {integrity: sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==}
|
resolution: {integrity: sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==}
|
||||||
|
|
||||||
@@ -3084,6 +3096,9 @@ packages:
|
|||||||
pako@0.2.9:
|
pako@0.2.9:
|
||||||
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
|
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
|
||||||
|
|
||||||
|
pako@2.1.0:
|
||||||
|
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
|
||||||
|
|
||||||
parent-module@1.0.1:
|
parent-module@1.0.1:
|
||||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -4060,6 +4075,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
web-serial-polyfill@1.0.15:
|
||||||
|
resolution: {integrity: sha512-usZN7kGRkEWr8DzRWxW+og55L1fHo4hNIwxCSCfWKpM+i0L+2AwzupMvkDFxnJNqUFOhLaD3PlgAOJxUOUrAoA==}
|
||||||
|
|
||||||
webidl-conversions@4.0.2:
|
webidl-conversions@4.0.2:
|
||||||
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
||||||
|
|
||||||
@@ -5796,6 +5814,8 @@ snapshots:
|
|||||||
|
|
||||||
at-least-node@1.0.0: {}
|
at-least-node@1.0.0: {}
|
||||||
|
|
||||||
|
atob-lite@2.0.0: {}
|
||||||
|
|
||||||
autoprefixer@10.4.20(postcss@8.4.39):
|
autoprefixer@10.4.20(postcss@8.4.39):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.24.2
|
browserslist: 4.24.2
|
||||||
@@ -6519,6 +6539,12 @@ snapshots:
|
|||||||
|
|
||||||
esm-env@1.0.0: {}
|
esm-env@1.0.0: {}
|
||||||
|
|
||||||
|
esptool-js@0.4.7:
|
||||||
|
dependencies:
|
||||||
|
atob-lite: 2.0.0
|
||||||
|
pako: 2.1.0
|
||||||
|
tslib: 2.6.3
|
||||||
|
|
||||||
esrap@1.2.2:
|
esrap@1.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
@@ -7336,6 +7362,8 @@ snapshots:
|
|||||||
|
|
||||||
pako@0.2.9: {}
|
pako@0.2.9: {}
|
||||||
|
|
||||||
|
pako@2.1.0: {}
|
||||||
|
|
||||||
parent-module@1.0.1:
|
parent-module@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
callsites: 3.1.0
|
callsites: 3.1.0
|
||||||
@@ -8336,6 +8364,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
xml-name-validator: 5.0.0
|
xml-name-validator: 5.0.0
|
||||||
|
|
||||||
|
web-serial-polyfill@1.0.15: {}
|
||||||
|
|
||||||
webidl-conversions@4.0.2: {}
|
webidl-conversions@4.0.2: {}
|
||||||
|
|
||||||
webidl-conversions@7.0.0: {}
|
webidl-conversions@7.0.0: {}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "app"
|
name = "app"
|
||||||
version = "2.0.1"
|
version = "2.2.1"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["Thea Schöbl <dev@theaninova.de>"]
|
authors = ["Thea Schöbl <dev@theaninova.de>"]
|
||||||
license = "AGPL-3"
|
license = "AGPL-3"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"devPath": "http://localhost:5173",
|
"devPath": "http://localhost:5173",
|
||||||
"distDir": "../build"
|
"distDir": "../build"
|
||||||
},
|
},
|
||||||
"package": { "productName": "amacc1ng", "version": "2.0.1" },
|
"package": { "productName": "amacc1ng", "version": "2.2.1" },
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": { "all": false },
|
"allowlist": { "all": false },
|
||||||
"bundle": {
|
"bundle": {
|
||||||
|
|||||||
@@ -3,35 +3,35 @@ col:
|
|||||||
# Ring / Middle
|
# Ring / Middle
|
||||||
- offset: [2, 0]
|
- offset: [2, 0]
|
||||||
row:
|
row:
|
||||||
- switch: { d: 25, e: 26, n: 27, w: 28, s: 29 }
|
- switch: { e: 26, n: 27, w: 28, s: 29 }
|
||||||
- switch: { d: 20, e: 21, n: 22, w: 23, s: 24 }
|
- switch: { e: 21, n: 22, w: 23, s: 24 }
|
||||||
- offset: [4, 0]
|
- offset: [4, 0]
|
||||||
switch: { d: 65, w: 66, n: 67, e: 68, s: 69 }
|
switch: { w: 66, n: 67, e: 68, s: 69 }
|
||||||
- switch: { d: 70, w: 71, n: 72, e: 73, s: 74 }
|
- switch: { w: 71, n: 72, e: 73, s: 74 }
|
||||||
- offset: [2, 0]
|
- offset: [2, 0]
|
||||||
row:
|
row:
|
||||||
- switch: { d: 40, e: 41, n: 42, w: 43, s: 44 }
|
- switch: { e: 41, n: 42, w: 43, s: 44 }
|
||||||
- switch: { d: 35, e: 36, n: 37, w: 38, s: 39 }
|
- switch: { e: 36, n: 37, w: 38, s: 39 }
|
||||||
- offset: [4, 0]
|
- offset: [4, 0]
|
||||||
switch: { d: 80, w: 81, n: 82, e: 83, s: 84 }
|
switch: { w: 81, n: 82, e: 83, s: 84 }
|
||||||
- switch: { d: 85, w: 86, n: 87, e: 88, s: 89 }
|
- switch: { w: 86, n: 87, e: 88, s: 89 }
|
||||||
# Pinkie / Index
|
# Pinkie / Index
|
||||||
- offset: [0, -3]
|
- offset: [0, -3]
|
||||||
row:
|
row:
|
||||||
- switch: { d: 30, e: 31, n: 32, w: 33, s: 34 }
|
- switch: { e: 31, n: 32, w: 33, s: 34 }
|
||||||
- offset: [4, 0]
|
- offset: [4, 0]
|
||||||
switch: { d: 15, e: 16, n: 17, w: 18, s: 19 }
|
switch: { e: 16, n: 17, w: 18, s: 19 }
|
||||||
- switch: { d: 60, w: 61, n: 62, e: 63, s: 64 }
|
- switch: { w: 61, n: 62, e: 63, s: 64 }
|
||||||
- offset: [4, 0]
|
- offset: [4, 0]
|
||||||
switch: { d: 75, w: 76, n: 77, e: 78, s: 79 }
|
switch: { w: 76, n: 77, e: 78, s: 79 }
|
||||||
# Thumbs
|
# Thumbs
|
||||||
- row:
|
- row:
|
||||||
- offset: [5.5, 0.5]
|
- offset: [5.5, 0.5]
|
||||||
switch: { d: 10, e: 11, n: 12, w: 13, s: 14 }
|
switch: { e: 11, n: 12, w: 13, s: 14 }
|
||||||
- offset: [1, 0.5]
|
- offset: [1, 0.5]
|
||||||
switch: { d: 55, w: 56, n: 57, e: 58, s: 59 }
|
switch: { w: 56, n: 57, e: 58, s: 59 }
|
||||||
- row:
|
- row:
|
||||||
- offset: [4.5, -0.25]
|
- offset: [4.5, -0.25]
|
||||||
switch: { d: 5, e: 6, n: 7, w: 8, s: 9 }
|
switch: { e: 6, n: 7, w: 8, s: 9 }
|
||||||
- offset: [3, -0.25]
|
- offset: [3, -0.25]
|
||||||
switch: { d: 50, w: 51, n: 52, e: 53, s: 54 }
|
switch: { w: 51, n: 52, e: 53, s: 54 }
|
||||||
|
|||||||
37
src/lib/assets/layouts/m4gr.yml
Normal file
37
src/lib/assets/layouts/m4gr.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: M4G
|
||||||
|
col:
|
||||||
|
# Ring / Middle
|
||||||
|
- offset: [2, 0]
|
||||||
|
row:
|
||||||
|
- switch: { e: 26, n: 27, w: 28, s: 29 }
|
||||||
|
- switch: { e: 21, n: 22, w: 23, s: 24 }
|
||||||
|
- offset: [4, 0]
|
||||||
|
switch: { w: 66, n: 67, e: 68, s: 69 }
|
||||||
|
- switch: { w: 71, n: 72, e: 73, s: 74 }
|
||||||
|
- offset: [2, 0]
|
||||||
|
row:
|
||||||
|
- switch: { e: 41, n: 42, w: 43, s: 44 }
|
||||||
|
- switch: { e: 36, n: 37, w: 38, s: 39 }
|
||||||
|
- offset: [4, 0]
|
||||||
|
switch: { w: 81, n: 82, e: 83, s: 84 }
|
||||||
|
- switch: { w: 86, n: 87, e: 88, s: 89 }
|
||||||
|
# Pinkie / Index
|
||||||
|
- offset: [0, -3]
|
||||||
|
row:
|
||||||
|
- switch: { e: 31, n: 32, w: 33, s: 34 }
|
||||||
|
- offset: [4, 0]
|
||||||
|
switch: { e: 16, n: 17, w: 18, s: 19 }
|
||||||
|
- switch: { w: 61, n: 62, e: 63, s: 64 }
|
||||||
|
- offset: [4, 0]
|
||||||
|
switch: { w: 76, n: 77, e: 78, s: 79 }
|
||||||
|
# Thumbs
|
||||||
|
- row:
|
||||||
|
- offset: [5.5, 0.5]
|
||||||
|
switch: { e: 11, n: 12, w: 13, s: 14 }
|
||||||
|
- offset: [1, 0.5]
|
||||||
|
switch: { w: 56, n: 57, e: 58, s: 59 }
|
||||||
|
- row:
|
||||||
|
- offset: [4.5, -0.25]
|
||||||
|
switch: { e: 6, n: 7, w: 8, s: 9 }
|
||||||
|
- offset: [3, -0.25]
|
||||||
|
switch: { w: 51, n: 52, e: 53, s: 54 }
|
||||||
@@ -37,6 +37,10 @@
|
|||||||
import("$lib/assets/layouts/m4g.yml").then(
|
import("$lib/assets/layouts/m4g.yml").then(
|
||||||
(it) => it.default as VisualLayout,
|
(it) => it.default as VisualLayout,
|
||||||
),
|
),
|
||||||
|
M4GR: () =>
|
||||||
|
import("$lib/assets/layouts/m4gr.yml").then(
|
||||||
|
(it) => it.default as VisualLayout,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -70,6 +74,7 @@
|
|||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
max-height: 20cm;
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const KEY_COUNTS = {
|
|||||||
LITE: 67,
|
LITE: 67,
|
||||||
X: 256,
|
X: 256,
|
||||||
M4G: 90,
|
M4G: 90,
|
||||||
|
M4GR: 90,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -36,6 +37,13 @@ if (
|
|||||||
await import("./tauri-serial");
|
await import("./tauri-serial");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (browser && navigator.serial === undefined && navigator.usb !== undefined) {
|
||||||
|
// @ts-expect-error polyfill
|
||||||
|
navigator.serial = await import("web-serial-polyfill").then(
|
||||||
|
({ serial }) => serial,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getViablePorts(): Promise<SerialPort[]> {
|
export async function getViablePorts(): Promise<SerialPort[]> {
|
||||||
return navigator.serial.getPorts().then((ports) =>
|
return navigator.serial.getPorts().then((ports) =>
|
||||||
ports.filter((it) => {
|
ports.filter((it) => {
|
||||||
|
|||||||
@@ -27,9 +27,9 @@
|
|||||||
external: true,
|
external: true,
|
||||||
},
|
},
|
||||||
{ href: "/editor", icon: "edit_document", title: "Editor", wip: true },
|
{ href: "/editor", icon: "edit_document", title: "Editor", wip: true },
|
||||||
|
{ href: "https://chat.dev.charachorder.io", icon: "chat", title: "Chat", wip: true },
|
||||||
],
|
],
|
||||||
/*[
|
/*[
|
||||||
{ href: "/chat", icon: "chat", title: "Chat", wip: true },
|
|
||||||
{ href: "/plugin", icon: "code", title: "Plugin", wip: true },
|
{ href: "/plugin", icon: "code", title: "Plugin", wip: true },
|
||||||
],*/
|
],*/
|
||||||
] satisfies {
|
] satisfies {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1><a href="/firmware">Firmware Updates</a></h1>
|
<h1><a href="/ccos">Firmware Updates</a></h1>
|
||||||
|
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { downloadBackup } from "$lib/backup/backup";
|
import { downloadBackup } from "$lib/backup/backup";
|
||||||
import { initSerial, serialPort } from "$lib/serial/connection";
|
import { initSerial, serialPort } from "$lib/serial/connection";
|
||||||
import { fade, slide } from "svelte/transition";
|
import { fade, slide } from "svelte/transition";
|
||||||
|
import type { LoaderOptions, ESPLoader } from "esptool-js";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
@@ -9,7 +10,12 @@
|
|||||||
let success = $state(false);
|
let success = $state(false);
|
||||||
let error = $state<Error | undefined>(undefined);
|
let error = $state<Error | undefined>(undefined);
|
||||||
|
|
||||||
|
let terminalOutput = $state("");
|
||||||
|
|
||||||
let step = $state(0);
|
let step = $state(0);
|
||||||
|
let eraseAll = $state(false);
|
||||||
|
|
||||||
|
let espLoader;
|
||||||
|
|
||||||
async function update() {
|
async function update() {
|
||||||
working = true;
|
working = true;
|
||||||
@@ -18,7 +24,9 @@
|
|||||||
const port = $serialPort!;
|
const port = $serialPort!;
|
||||||
$serialPort = undefined;
|
$serialPort = undefined;
|
||||||
try {
|
try {
|
||||||
const file = await fetch(otaUrl!).then((it) => it.blob());
|
const file = await fetch(
|
||||||
|
`${data.meta.path}/${data.meta.update.ota!}`,
|
||||||
|
).then((it) => it.blob());
|
||||||
|
|
||||||
await port.updateFirmware(file);
|
await port.updateFirmware(file);
|
||||||
|
|
||||||
@@ -36,18 +44,7 @@
|
|||||||
: undefined,
|
: undefined,
|
||||||
);
|
);
|
||||||
let isCorrectDevice = $derived(
|
let isCorrectDevice = $derived(
|
||||||
currentDevice ? currentDevice === data.device : undefined,
|
currentDevice ? currentDevice === data.meta.target : undefined,
|
||||||
);
|
|
||||||
|
|
||||||
let uf2Url = $derived(
|
|
||||||
data.uf2
|
|
||||||
? `${import.meta.env.VITE_FIRMWARE_URL}${data.device}/${data.version}/${data.uf2.name}`
|
|
||||||
: undefined,
|
|
||||||
);
|
|
||||||
let otaUrl = $derived(
|
|
||||||
data.ota
|
|
||||||
? `${import.meta.env.VITE_FIRMWARE_URL}${data.device}/${data.version}/${data.ota.name}`
|
|
||||||
: undefined,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,10 +81,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getFileSystem() {
|
async function getFileSystem() {
|
||||||
if (!uf2Url) return;
|
if (!data.meta.update.uf2) return;
|
||||||
const uf2Promise = fetch(uf2Url).then((it) => it.blob());
|
const uf2Promise = fetch(
|
||||||
|
`${data.meta.path}/${data.meta.update.uf2.name}`,
|
||||||
|
).then((it) => it.blob());
|
||||||
const handle = await window.showSaveFilePicker({
|
const handle = await window.showSaveFilePicker({
|
||||||
id: `${data.device}-update`,
|
id: `${data.meta.target}-update`,
|
||||||
suggestedName: "CURRENT.UF2",
|
suggestedName: "CURRENT.UF2",
|
||||||
excludeAcceptAllOption: true,
|
excludeAcceptAllOption: true,
|
||||||
types: [
|
types: [
|
||||||
@@ -102,21 +101,104 @@
|
|||||||
await uf2.stream().pipeTo(writable);
|
await uf2.stream().pipeTo(writable);
|
||||||
step = 4;
|
step = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function espBootloader() {
|
||||||
|
$serialPort?.forget();
|
||||||
|
const port = await navigator.serial.requestPort();
|
||||||
|
port.open({ baudRate: 1200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function connectEsp(port: SerialPort): Promise<ESPLoader> {
|
||||||
|
const esptool = data.meta.update.esptool!;
|
||||||
|
const { Transport, ESPLoader } = await import("esptool-js");
|
||||||
|
const espLoader = new ESPLoader({
|
||||||
|
transport: new Transport(port),
|
||||||
|
baudrate: 9600, // Number(esptool.baud),
|
||||||
|
romBaudrate: 9600, // Number(esptool.baud),
|
||||||
|
debugLogging: true,
|
||||||
|
terminal: {
|
||||||
|
clean: () => {
|
||||||
|
terminalOutput = "";
|
||||||
|
},
|
||||||
|
writeLine: (data) => {
|
||||||
|
terminalOutput += data + "\n";
|
||||||
|
},
|
||||||
|
write: (data) => {
|
||||||
|
terminalOutput += data;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies LoaderOptions);
|
||||||
|
await espLoader.detectChip(esptool.before);
|
||||||
|
if (!espLoader.IS_STUB) {
|
||||||
|
await espLoader.runStub();
|
||||||
|
}
|
||||||
|
|
||||||
|
return espLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flashImages() {
|
||||||
|
const port = await navigator.serial.requestPort();
|
||||||
|
try {
|
||||||
|
const esptool = data.meta.update.esptool!;
|
||||||
|
espLoader = await connectEsp(port);
|
||||||
|
const fileArray = await Promise.all(
|
||||||
|
Object.entries(esptool.files).map(([offset, name]) =>
|
||||||
|
fetch(`${data.meta.path}/${name}`)
|
||||||
|
.then((it) => it.blob())
|
||||||
|
.then((it) => it.text())
|
||||||
|
.then((it) => ({
|
||||||
|
address: Number(offset),
|
||||||
|
data: it,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await espLoader.writeFlash({
|
||||||
|
flashSize: esptool.flash_size,
|
||||||
|
flashMode: esptool.flash_mode,
|
||||||
|
flashFreq: esptool.flash_freq,
|
||||||
|
compress: true,
|
||||||
|
eraseAll,
|
||||||
|
fileArray,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
port.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function eraseSPI() {
|
||||||
|
const port = await navigator.serial.requestPort();
|
||||||
|
try {
|
||||||
|
console.log(data.meta);
|
||||||
|
const spiFlash = data.meta.spi_flash!;
|
||||||
|
espLoader = await connectEsp(port);
|
||||||
|
|
||||||
|
/*espLoader.flashSpiAttach(
|
||||||
|
(spiFlash.connection.clk << 0) |
|
||||||
|
(spiFlash.connection.q << 8) |
|
||||||
|
(spiFlash.connection.d << 16) |
|
||||||
|
(spiFlash.connection.cs << 24),
|
||||||
|
);
|
||||||
|
espLoader.flashId();*/
|
||||||
|
} finally {
|
||||||
|
port.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>
|
<h2>
|
||||||
<a class="inline-link" href="/ccos">CCOS</a> /
|
<a class="inline-link" href="/ccos">CCOS</a> /
|
||||||
<a
|
<a
|
||||||
href="/ccos/{data.device}"
|
href="/ccos/{data.meta.target}"
|
||||||
class="device inline-link"
|
class="device inline-link"
|
||||||
class:correct-device={isCorrectDevice === true}
|
class:correct-device={isCorrectDevice === true}
|
||||||
class:incorrect-device={isCorrectDevice === false}>{data.device}</a
|
class:incorrect-device={isCorrectDevice === false}>{data.meta.target}</a
|
||||||
>
|
>
|
||||||
/ <em class="version">{data.version}</em>
|
/ <em class="version">{data.meta.version}</em>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{#if data.ota && !data.device.endsWith("m0")}
|
{#if data.meta.update.ota && !data.meta.target.endsWith("m0")}
|
||||||
{@const buttonError = error || (!success && isCorrectDevice === false)}
|
{@const buttonError = error || (!success && isCorrectDevice === false)}
|
||||||
<section>
|
<section>
|
||||||
<button
|
<button
|
||||||
@@ -136,7 +218,7 @@
|
|||||||
{$serialPort.chipset}</b
|
{$serialPort.chipset}</b
|
||||||
>
|
>
|
||||||
will be updated from <b class="version">{$serialPort.version}</b> to
|
will be updated from <b class="version">{$serialPort.version}</b> to
|
||||||
<b class="version">{data.version}</b>
|
<b class="version">{data.meta.version}</b>
|
||||||
</div>
|
</div>
|
||||||
{:else if $serialPort && isCorrectDevice === false}
|
{:else if $serialPort && isCorrectDevice === false}
|
||||||
<div class="error" transition:slide>
|
<div class="error" transition:slide>
|
||||||
@@ -158,27 +240,6 @@
|
|||||||
<h3>Manual Update</h3>
|
<h3>Manual Update</h3>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<ul class="files">
|
|
||||||
{#if data.uf2}
|
|
||||||
<li>
|
|
||||||
<a target="_blank" download href={uf2Url}
|
|
||||||
>{data.uf2.name} <span class="icon">download</span><span class="size"
|
|
||||||
>{toByteUnit(data.uf2.size)}</span
|
|
||||||
></a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{#if data.ota}
|
|
||||||
<li>
|
|
||||||
<a target="_blank" download href={otaUrl}
|
|
||||||
>{data.ota.name} <span class="icon">download</span><span class="size"
|
|
||||||
>{toByteUnit(data.uf2.size)}</span
|
|
||||||
></a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{#if isCorrectDevice === false}
|
{#if isCorrectDevice === false}
|
||||||
<div transition:slide class="incorrect-device">
|
<div transition:slide class="incorrect-device">
|
||||||
These files are incompatible with your device
|
These files are incompatible with your device
|
||||||
@@ -186,7 +247,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h4>UF2 Instructions</h4>
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>
|
<li>
|
||||||
<button class="inline-button" onclick={connect}
|
<button class="inline-button" onclick={connect}
|
||||||
@@ -227,6 +287,37 @@
|
|||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{#if data.meta.update.esptool}
|
||||||
|
<section>
|
||||||
|
<h3>Factory Flash (WIP)</h3>
|
||||||
|
<p>
|
||||||
|
If everything else fails, you can go through the same process that is
|
||||||
|
being used in the factory.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This will temporarily brick your device if the process is not done
|
||||||
|
completely or incorrectly.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="esp-buttons">
|
||||||
|
<button onclick={espBootloader}
|
||||||
|
><span class="icon">memory</span>ESP Bootloader</button
|
||||||
|
>
|
||||||
|
<button onclick={flashImages}
|
||||||
|
><span class="icon">developer_board</span>Flash Images</button
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
><input type="checkbox" id="erase" bind:checked={eraseAll} />Erase All</label
|
||||||
|
>
|
||||||
|
<button onclick={eraseSPI}
|
||||||
|
><span class="icon">developer_board</span>Erase SPI Flash</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre>{terminalOutput}</pre>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -239,6 +330,10 @@
|
|||||||
margin-block-start: 4em;
|
margin-block-start: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.primary {
|
.primary {
|
||||||
color: var(--md-sys-color-primary);
|
color: var(--md-sys-color-primary);
|
||||||
}
|
}
|
||||||
@@ -249,6 +344,7 @@
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: calc(min(100%, 16cm));
|
width: calc(min(100%, 16cm));
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes rotate {
|
@keyframes rotate {
|
||||||
@@ -402,4 +498,8 @@
|
|||||||
.incorrect-device {
|
.incorrect-device {
|
||||||
color: var(--md-sys-color-error);
|
color: var(--md-sys-color-error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.esp-buttons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,20 +1,50 @@
|
|||||||
import type { PageLoad } from "./$types";
|
import type { PageLoad } from "./$types";
|
||||||
import type { FileListing, Listing } from "../../listing";
|
import type { FileListing, Listing } from "../../listing";
|
||||||
|
import type { VersionMeta } from "./meta";
|
||||||
|
|
||||||
export const load = (async ({ fetch, params }) => {
|
export const load = (async ({ fetch, params }) => {
|
||||||
const result = await fetch(
|
const result = await fetch(
|
||||||
`${import.meta.env.VITE_FIRMWARE_URL}/${params.device}/${params.version}/`,
|
`${import.meta.env.VITE_FIRMWARE_URL}/${params.device}/${params.version}/`,
|
||||||
);
|
);
|
||||||
const data: Listing[] = await result.json();
|
const data: Listing[] = await result.json();
|
||||||
|
const meta: VersionMeta | undefined = data.some(
|
||||||
|
(entry) => entry.type === "file" && entry.name === "meta.json",
|
||||||
|
)
|
||||||
|
? await fetch(
|
||||||
|
`${import.meta.env.VITE_FIRMWARE_URL}/${params.device}/${params.version}/meta.json`,
|
||||||
|
).then((res) => res.json())
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uf2: data.find(
|
meta: {
|
||||||
(entry) => entry.type === "file" && entry.name === "CURRENT.UF2",
|
version: meta?.version ?? params.version,
|
||||||
) as FileListing,
|
target: meta?.target ?? params.device,
|
||||||
ota: data.find(
|
path: `${import.meta.env.VITE_FIRMWARE_URL}${params.device}/${params.version}`,
|
||||||
(entry) => entry.type === "file" && entry.name === "firmware.bin",
|
git_commit: meta?.git_commit ?? "",
|
||||||
),
|
git_is_dirty: meta?.git_is_dirty ?? false,
|
||||||
version: params.version,
|
git_date: meta?.git_date ?? data[0]?.mtime ?? "",
|
||||||
device: params.device,
|
public_build: meta?.public_build ?? !params.version.startsWith("."),
|
||||||
|
development_mode: meta?.development_mode ?? 0,
|
||||||
|
update: {
|
||||||
|
uf2:
|
||||||
|
(data.find(
|
||||||
|
(entry) =>
|
||||||
|
entry.type === "file" &&
|
||||||
|
entry.name === (meta?.update?.uf2 ?? "CURRENT.UF2"),
|
||||||
|
) as FileListing) ?? undefined,
|
||||||
|
ota:
|
||||||
|
data.find(
|
||||||
|
(entry) =>
|
||||||
|
entry.type === "file" &&
|
||||||
|
entry.name === (meta?.update?.ota ?? "firmware.bin"),
|
||||||
|
) ?? undefined,
|
||||||
|
esptool: meta?.update?.esptool ?? undefined,
|
||||||
|
},
|
||||||
|
files: data.filter(
|
||||||
|
(entry) =>
|
||||||
|
entry.type === "file" && (!meta?.files || entry.name in meta.files),
|
||||||
|
) as FileListing[],
|
||||||
|
spi_flash: meta?.spi_flash ?? undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}) satisfies PageLoad;
|
}) satisfies PageLoad;
|
||||||
|
|||||||
41
src/routes/(app)/ccos/[device]/[version]/meta.ts
Normal file
41
src/routes/(app)/ccos/[device]/[version]/meta.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
export interface VersionMeta {
|
||||||
|
version: string;
|
||||||
|
target: string;
|
||||||
|
git_commit: string;
|
||||||
|
git_is_dirty: boolean;
|
||||||
|
git_date: string;
|
||||||
|
public_build: boolean;
|
||||||
|
development_mode: number;
|
||||||
|
update: {
|
||||||
|
ota: string | null;
|
||||||
|
uf2: string | null;
|
||||||
|
esptool: EspToolData | null;
|
||||||
|
};
|
||||||
|
files: string[];
|
||||||
|
spi_flash: SPIFlashInfo | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SPIFlashInfo {
|
||||||
|
type: string;
|
||||||
|
size: string;
|
||||||
|
connection: SPIConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SPIConnection {
|
||||||
|
clk: number;
|
||||||
|
q: number;
|
||||||
|
d: number;
|
||||||
|
hd: number;
|
||||||
|
cs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EspToolData {
|
||||||
|
chip: string;
|
||||||
|
baud: string;
|
||||||
|
before: string;
|
||||||
|
after: string;
|
||||||
|
flash_mode: string;
|
||||||
|
flash_freq: string;
|
||||||
|
flash_size: string;
|
||||||
|
files: Record<string, string>;
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
buildIndex($chords, $osLayout).then(searchIndex.set);
|
buildIndex($chords, $osLayout).then(searchIndex.set);
|
||||||
});
|
});
|
||||||
|
|
||||||
function encodeChord(chord: ChordInfo, osLayout: Map<string, string>) {
|
function encodeChord(chord: ChordInfo, osLayout: Map<string, string>, onlyPhrase: boolean = false) {
|
||||||
const plainPhrase: string[] = [""];
|
const plainPhrase: string[] = [""];
|
||||||
const extraActions: string[] = [];
|
const extraActions: string[] = [];
|
||||||
const extraCodes: string[] = [];
|
const extraCodes: string[] = [];
|
||||||
@@ -103,6 +103,10 @@
|
|||||||
return result ?? `0x${it.toString(16)}`;
|
return result ?? `0x${it.toString(16)}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (onlyPhrase) {
|
||||||
|
return plainPhrase.join();
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...plainPhrase,
|
...plainPhrase,
|
||||||
`+${input.join("+")}`,
|
`+${input.join("+")}`,
|
||||||
@@ -182,7 +186,7 @@
|
|||||||
function downloadVocabulary() {
|
function downloadVocabulary() {
|
||||||
const vocabulary = new Set(
|
const vocabulary = new Set(
|
||||||
$chords.map((it) =>
|
$chords.map((it) =>
|
||||||
"phrase" in it ? plainPhrase(it.phrase, $osLayout).trim() : "",
|
"phrase" in it ? encodeChord(it, $osLayout, true).trim() : "",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
vocabulary.delete("");
|
vocabulary.delete("");
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import type { Action } from "svelte/action";
|
import type { Action } from "svelte/action";
|
||||||
import ConfirmChallenge from "./ConfirmChallenge.svelte";
|
import ConfirmChallenge from "./ConfirmChallenge.svelte";
|
||||||
import tippy from "tippy.js";
|
import tippy from "tippy.js";
|
||||||
|
import { mount, unmount } from "svelte";
|
||||||
|
|
||||||
export const confirmChallenge: Action<
|
export const confirmChallenge: Action<
|
||||||
HTMLElement,
|
HTMLElement,
|
||||||
{ onConfirm: () => void; challenge: string }
|
{ onConfirm: () => void; challenge: string }
|
||||||
> = (node, { onConfirm, challenge }) => {
|
> = (node, { onConfirm, challenge }) => {
|
||||||
let component: ConfirmChallenge | undefined;
|
let component: {} | undefined;
|
||||||
let target: HTMLElement | undefined;
|
let target: HTMLElement | undefined;
|
||||||
const edit = tippy(node, {
|
const edit = tippy(node, {
|
||||||
interactive: true,
|
interactive: true,
|
||||||
@@ -15,15 +16,22 @@ export const confirmChallenge: Action<
|
|||||||
target = instance.popper.querySelector(".tippy-content") as HTMLElement;
|
target = instance.popper.querySelector(".tippy-content") as HTMLElement;
|
||||||
target.classList.add("active");
|
target.classList.add("active");
|
||||||
if (component === undefined) {
|
if (component === undefined) {
|
||||||
component = new ConfirmChallenge({ target, props: { challenge } });
|
component = mount(ConfirmChallenge, {
|
||||||
component.$on("confirm", () => {
|
target,
|
||||||
edit.hide();
|
props: {
|
||||||
onConfirm();
|
challenge,
|
||||||
|
onconfirm() {
|
||||||
|
edit.hide();
|
||||||
|
onConfirm();
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onHidden() {
|
onHidden() {
|
||||||
component?.$destroy();
|
if (component) {
|
||||||
|
unmount(component);
|
||||||
|
}
|
||||||
target?.classList.remove("active");
|
target?.classList.remove("active");
|
||||||
component = undefined;
|
component = undefined;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export default defineConfig({
|
|||||||
"client/**/*.{js,css,ico,woff2,csv,png,webp,svg,webmanifest}",
|
"client/**/*.{js,css,ico,woff2,csv,png,webp,svg,webmanifest}",
|
||||||
"prerendered/**/*.html",
|
"prerendered/**/*.html",
|
||||||
],
|
],
|
||||||
|
globIgnores: ["prerendered/pages/ccos/**/*"],
|
||||||
ignoreURLParametersMatching: [/^import|redirectUrl|loginToken$/],
|
ignoreURLParametersMatching: [/^import|redirectUrl|loginToken$/],
|
||||||
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
|
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user