feat(frontend): DeployProgress UI, SerialMonitor BLE toggle, EmbedBridge improvements
- Add DeployProgress component (compile/transfer/flash progress bar) - SerialMonitor: add BLE/Hardware deploy toggle support - EditorToolbar: wire deploy state to progress - EmbedBridge: improved message passing for embed mode - useSimulatorStore: add deploy state tracking - CodeEditor: disable stickyScroll, config tweaks - nginx.conf: remove legacy redirect config - package.json: dependency cleanup
This commit is contained in:
parent
9402b6c40f
commit
867bbf3de9
|
|
@ -75,10 +75,6 @@ server {
|
||||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml application/rss+xml;
|
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml application/rss+xml;
|
||||||
|
|
||||||
# Frontend SPA routing — must be last so specific locations above take precedence
|
# Frontend SPA routing — must be last so specific locations above take precedence
|
||||||
location = / {
|
|
||||||
return 301 /velxio/editor;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════════════
|
<!-- ═══════════════════════════════════════════════
|
||||||
PRIMARY SEO
|
PRIMARY SEO
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
"@types/node": "^24.11.0",
|
"@types/node": "^24.11.0",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.2.0",
|
||||||
"@vitest/coverage-v8": "^4.0.18",
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
"@vitest/ui": "^4.0.18",
|
"@vitest/ui": "^4.0.18",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
|
|
@ -38,77 +38,16 @@
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^28.1.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.48.0",
|
"typescript-eslint": "^8.48.0",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.6",
|
||||||
"vitest": "^4.0.18"
|
"vitest": "^4.0.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"../wokwi-libs/avr8js": {
|
"../wokwi-libs/avr8js": {
|
||||||
"version": "0.21.0",
|
"version": "1.0.0"
|
||||||
"license": "MIT",
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^22.0.0",
|
|
||||||
"@wokwi/elements": "^1.9.1",
|
|
||||||
"eslint": "^9.0.0",
|
|
||||||
"eslint-config-prettier": "^10.1.8",
|
|
||||||
"eslint-plugin-json": "^4.0.1",
|
|
||||||
"eslint-plugin-prettier": "^5.5.5",
|
|
||||||
"husky": "^9.1.7",
|
|
||||||
"lint-staged": "^16.2.7",
|
|
||||||
"prettier": "^3.8.1",
|
|
||||||
"prettier-plugin-organize-imports": "^4.1.0",
|
|
||||||
"rimraf": "^6.1.2",
|
|
||||||
"tsx": "^4.21.0",
|
|
||||||
"typescript": "^5.9.3",
|
|
||||||
"typescript-eslint": "^8.55.0",
|
|
||||||
"vite": "^7.3.1",
|
|
||||||
"vitest": "^4.0.18"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 20.19.0",
|
|
||||||
"npm": ">= 10.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"../wokwi-libs/wokwi-elements": {
|
"../wokwi-libs/wokwi-elements": {
|
||||||
"name": "@wokwi/elements",
|
"name": "@wokwi/elements",
|
||||||
"version": "1.9.2",
|
"version": "1.0.0"
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/react": ">=16",
|
|
||||||
"lit": "^3.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/core": "^7.15.0",
|
|
||||||
"@babel/runtime": "^7.15.3",
|
|
||||||
"@lit-labs/ssr": "^3.3.1",
|
|
||||||
"@rollup/plugin-commonjs": "^28.0.6",
|
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
||||||
"@storybook/addon-docs": "9.1.1",
|
|
||||||
"@storybook/web-components": "^9.1.1",
|
|
||||||
"@storybook/web-components-vite": "^9.1.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.39.0",
|
|
||||||
"@typescript-eslint/parser": "^8.39.0",
|
|
||||||
"babel-loader": "^9.1.0",
|
|
||||||
"eslint": "^8.27.0",
|
|
||||||
"eslint-config-prettier": "^10.1.8",
|
|
||||||
"eslint-plugin-json": "^4.0.1",
|
|
||||||
"eslint-plugin-prettier": "^5.5.4",
|
|
||||||
"eslint-plugin-storybook": "^9.1.1",
|
|
||||||
"husky": "^8.0.0",
|
|
||||||
"hygen": "^6.1.0",
|
|
||||||
"lint-staged": "^10.2.11",
|
|
||||||
"prettier": "^3.5.3",
|
|
||||||
"react-is": "^16.13.1",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"rollup": "^4.59.0",
|
|
||||||
"sharp": "^0.34.3",
|
|
||||||
"terser": "^5.43.1",
|
|
||||||
"typescript": "^5.9.0",
|
|
||||||
"vitest": "^3.2.4",
|
|
||||||
"web-component-analyzer": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/@acemir/cssom": {
|
"node_modules/@acemir/cssom": {
|
||||||
"version": "0.9.31",
|
"version": "0.9.31",
|
||||||
|
|
@ -2176,9 +2115,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "5.1.4",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
|
||||||
"integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==",
|
"integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -2193,7 +2132,7 @@
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vitest/coverage-v8": {
|
"node_modules/@vitest/coverage-v8": {
|
||||||
|
|
@ -5007,13 +4946,13 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.1",
|
"version": "7.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
|
||||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
"integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0 || ^0.28.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
"picomatch": "^4.0.3",
|
"picomatch": "^4.0.3",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
"@types/node": "^24.11.0",
|
"@types/node": "^24.11.0",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.2.0",
|
||||||
"@vitest/coverage-v8": "^4.0.18",
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
"@vitest/ui": "^4.0.18",
|
"@vitest/ui": "^4.0.18",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^28.1.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.48.0",
|
"typescript-eslint": "^8.48.0",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.6",
|
||||||
"vitest": "^4.0.18"
|
"vitest": "^4.0.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,169 +4,169 @@
|
||||||
|
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/</loc>
|
<loc>https://velxio.dev/</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>1</priority>
|
<priority>1</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/editor</loc>
|
<loc>https://velxio.dev/editor</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/examples</loc>
|
<loc>https://velxio.dev/examples</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs</loc>
|
<loc>https://velxio.dev/docs</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/intro</loc>
|
<loc>https://velxio.dev/docs/intro</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/getting-started</loc>
|
<loc>https://velxio.dev/docs/getting-started</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/emulator</loc>
|
<loc>https://velxio.dev/docs/emulator</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/esp32-emulation</loc>
|
<loc>https://velxio.dev/docs/esp32-emulation</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/riscv-emulation</loc>
|
<loc>https://velxio.dev/docs/riscv-emulation</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/rp2040-emulation</loc>
|
<loc>https://velxio.dev/docs/rp2040-emulation</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/raspberry-pi3-emulation</loc>
|
<loc>https://velxio.dev/docs/raspberry-pi3-emulation</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/components</loc>
|
<loc>https://velxio.dev/docs/components</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/architecture</loc>
|
<loc>https://velxio.dev/docs/architecture</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/wokwi-libs</loc>
|
<loc>https://velxio.dev/docs/wokwi-libs</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/mcp</loc>
|
<loc>https://velxio.dev/docs/mcp</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/setup</loc>
|
<loc>https://velxio.dev/docs/setup</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.6</priority>
|
<priority>0.6</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/docs/roadmap</loc>
|
<loc>https://velxio.dev/docs/roadmap</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.6</priority>
|
<priority>0.6</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/arduino-simulator</loc>
|
<loc>https://velxio.dev/arduino-simulator</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/arduino-emulator</loc>
|
<loc>https://velxio.dev/arduino-emulator</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/atmega328p-simulator</loc>
|
<loc>https://velxio.dev/atmega328p-simulator</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.85</priority>
|
<priority>0.85</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/arduino-mega-simulator</loc>
|
<loc>https://velxio.dev/arduino-mega-simulator</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.85</priority>
|
<priority>0.85</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/esp32-simulator</loc>
|
<loc>https://velxio.dev/esp32-simulator</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/esp32-s3-simulator</loc>
|
<loc>https://velxio.dev/esp32-s3-simulator</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.85</priority>
|
<priority>0.85</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/esp32-c3-simulator</loc>
|
<loc>https://velxio.dev/esp32-c3-simulator</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.85</priority>
|
<priority>0.85</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/raspberry-pi-pico-simulator</loc>
|
<loc>https://velxio.dev/raspberry-pi-pico-simulator</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/raspberry-pi-simulator</loc>
|
<loc>https://velxio.dev/raspberry-pi-simulator</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.85</priority>
|
<priority>0.85</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/v2</loc>
|
<loc>https://velxio.dev/v2</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://velxio.dev/about</loc>
|
<loc>https://velxio.dev/about</loc>
|
||||||
<lastmod>2026-04-01</lastmod>
|
<lastmod>2026-07-05</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.7</priority>
|
<priority>0.7</priority>
|
||||||
</url>
|
</url>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ body {
|
||||||
|
|
||||||
/* ── App shell ──────────────────────────────────── */
|
/* ── App shell ──────────────────────────────────── */
|
||||||
.app {
|
.app {
|
||||||
height: 100vh;
|
height: 100%; /* Fill iframe container — allows iframe scroll when content overflows */
|
||||||
|
min-height: 100dvh; /* But at least viewport height when no overflow */
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -160,7 +161,8 @@ body {
|
||||||
.app-container {
|
.app-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow-y: auto; /* Allow scroll if content taller than container */
|
||||||
|
overflow-x: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,26 +41,29 @@ export const CodeEditor = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', width: '100%' }}>
|
<div style={{ height: '100%', width: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
<Editor
|
<div style={{ flex: 1, minHeight: 0 }}>
|
||||||
// key forces a fresh editor instance per file (preserves undo/redo per file)
|
<Editor
|
||||||
key={activeFileId}
|
// key forces a fresh editor instance per file (preserves undo/redo per file)
|
||||||
height="100%"
|
key={activeFileId}
|
||||||
language={activeFile ? getLanguage(activeFile.name) : 'cpp'}
|
height="100%"
|
||||||
theme={theme}
|
language={activeFile ? getLanguage(activeFile.name) : 'cpp'}
|
||||||
value={activeFile?.content ?? ''}
|
theme={theme}
|
||||||
onChange={(value) => {
|
value={activeFile?.content ?? ''}
|
||||||
if (activeFileId) setFileContent(activeFileId, value || '');
|
onChange={(value) => {
|
||||||
}}
|
if (activeFileId) setFileContent(activeFileId, value || '');
|
||||||
onMount={handleEditorMount}
|
}}
|
||||||
options={{
|
onMount={handleEditorMount}
|
||||||
minimap: { enabled: true },
|
options={{
|
||||||
fontSize,
|
minimap: { enabled: true },
|
||||||
automaticLayout: true,
|
fontSize,
|
||||||
scrollBeyondLastLine: false,
|
automaticLayout: true,
|
||||||
wordWrap: 'on',
|
scrollBeyondLastLine: true,
|
||||||
}}
|
wordWrap: 'on',
|
||||||
/>
|
stickyScroll: { enabled: false },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
/* DeployProgress — overlay bar shown below the editor toolbar during hardware deploy. */
|
||||||
|
|
||||||
|
.deploy-progress {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: -32px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #1e1e1e;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
font-size: 12px;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploy-progress-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
color: #ccc;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploy-progress-message {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploy-progress-counter {
|
||||||
|
color: #888;
|
||||||
|
font-size: 11px;
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploy-progress-bar-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
background: #333;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploy-progress-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.3s ease, background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deploy-progress-spinner {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid #333;
|
||||||
|
border-top-color: #3b82f6;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: deploy-spin 0.8s linear infinite;
|
||||||
|
margin: 2px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes deploy-spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import React from 'react';
|
||||||
|
import type { DeployState } from '../../types/deployer';
|
||||||
|
import './DeployProgress.css';
|
||||||
|
|
||||||
|
interface DeployProgressProps {
|
||||||
|
state: DeployState;
|
||||||
|
message: string;
|
||||||
|
completed: number;
|
||||||
|
total: number;
|
||||||
|
percent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATE_GRADIENT: Record<string, string> = {
|
||||||
|
compiling: 'linear-gradient(90deg, #3b82f6, #60a5fa)', // blue
|
||||||
|
transferring: 'linear-gradient(90deg, #f59e0b, #fbbf24)', // amber/yellow
|
||||||
|
flashing: 'linear-gradient(90deg, #22c55e, #4ade80)', // green
|
||||||
|
serial_bridge:'linear-gradient(90deg, #8b5cf6, #a78bfa)', // purple
|
||||||
|
success: 'linear-gradient(90deg, #22c55e, #4ade80)', // green
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATE_SPINNER_COLOR: Record<string, string> = {
|
||||||
|
compiling: '#3b82f6',
|
||||||
|
transferring: '#f59e0b',
|
||||||
|
flashing: '#22c55e',
|
||||||
|
serial_bridge:'#8b5cf6',
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_GRADIENT = 'linear-gradient(90deg, #3b82f6, #22c55e)';
|
||||||
|
const DEFAULT_SPINNER_COLOR = '#3b82f6';
|
||||||
|
|
||||||
|
export const DeployProgress: React.FC<DeployProgressProps> = ({
|
||||||
|
state,
|
||||||
|
message,
|
||||||
|
completed,
|
||||||
|
total,
|
||||||
|
percent,
|
||||||
|
}) => {
|
||||||
|
const showBar = total > 0 && (state === 'transferring' || state === 'flashing' || state === 'serial_bridge');
|
||||||
|
const showSpinner = state === 'compiling' || state === 'pairing' || (state === 'transferring' && total === 0);
|
||||||
|
const barGradient = STATE_GRADIENT[state] ?? DEFAULT_GRADIENT;
|
||||||
|
const spinnerColor = STATE_SPINNER_COLOR[state] ?? DEFAULT_SPINNER_COLOR;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="deploy-progress">
|
||||||
|
<div className="deploy-progress-header">
|
||||||
|
<span className="deploy-progress-message">{message}</span>
|
||||||
|
{showBar && (
|
||||||
|
<span className="deploy-progress-counter">
|
||||||
|
Chunk {completed}/{total} ({percent}%)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{showBar && (
|
||||||
|
<div className="deploy-progress-bar-container">
|
||||||
|
<div
|
||||||
|
className="deploy-progress-bar-fill"
|
||||||
|
style={{ width: `${percent}%`, background: barGradient }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showSpinner && (
|
||||||
|
<div
|
||||||
|
className="deploy-progress-spinner"
|
||||||
|
style={{ borderTopColor: spinnerColor }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -147,6 +147,8 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
||||||
markCompiled();
|
markCompiled();
|
||||||
setMissingLibHint(false);
|
setMissingLibHint(false);
|
||||||
embedBridge.notifyCompileResult(true);
|
embedBridge.notifyCompileResult(true);
|
||||||
|
// NEW: push hex to parent (embed mode only — EmbedBridge.send is no-op if not embedded)
|
||||||
|
embedBridge.notifyHexReady(activeBoardId, program, activeBoard?.boardKind ?? null);
|
||||||
} else {
|
} else {
|
||||||
const errText = result.error || result.stderr || 'Compile failed';
|
const errText = result.error || result.stderr || 'Compile failed';
|
||||||
setMessage({ type: 'error', text: errText });
|
setMessage({ type: 'error', text: errText });
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,21 @@ export const SerialMonitor: React.FC = () => {
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
const [lineEnding, setLineEnding] = useState<'none' | 'nl' | 'cr' | 'both'>('nl');
|
const [lineEnding, setLineEnding] = useState<'none' | 'nl' | 'cr' | 'both'>('nl');
|
||||||
const [autoscroll, setAutoscroll] = useState(true);
|
const [autoscroll, setAutoscroll] = useState(true);
|
||||||
|
const [hardwareSerialActive, setHardwareSerialActive] = useState(false);
|
||||||
const outputRef = useRef<HTMLPreElement>(null);
|
const outputRef = useRef<HTMLPreElement>(null);
|
||||||
|
|
||||||
|
// Subscribe to CustomEvents from EmbedBridge for hardware serial
|
||||||
|
useEffect(() => {
|
||||||
|
const onStart = () => setHardwareSerialActive(true);
|
||||||
|
const onStop = () => setHardwareSerialActive(false);
|
||||||
|
window.addEventListener('velxio-hardware-serial-start', onStart);
|
||||||
|
window.addEventListener('velxio-hardware-serial-stop', onStop);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('velxio-hardware-serial-start', onStart);
|
||||||
|
window.removeEventListener('velxio-hardware-serial-stop', onStop);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Sync active tab to activeBoardId when it changes
|
// Sync active tab to activeBoardId when it changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeBoardId) setActiveTabId(activeBoardId);
|
if (activeBoardId) setActiveTabId(activeBoardId);
|
||||||
|
|
@ -153,6 +166,20 @@ export const SerialMonitor: React.FC = () => {
|
||||||
{activeBoard?.serialBaudRate != null && activeBoard.serialBaudRate > 0 && (
|
{activeBoard?.serialBaudRate != null && activeBoard.serialBaudRate > 0 && (
|
||||||
<span style={styles.baudRate}>{activeBoard.serialBaudRate.toLocaleString()} baud</span>
|
<span style={styles.baudRate}>{activeBoard.serialBaudRate.toLocaleString()} baud</span>
|
||||||
)}
|
)}
|
||||||
|
{hardwareSerialActive && (
|
||||||
|
<span style={{
|
||||||
|
color: '#4ec9b0',
|
||||||
|
fontSize: 11,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
background: '#1e1e1e',
|
||||||
|
border: '1px solid #4ec9b0',
|
||||||
|
borderRadius: 3,
|
||||||
|
padding: '1px 6px',
|
||||||
|
fontWeight: 600,
|
||||||
|
}}>
|
||||||
|
● Hardware
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<label style={styles.autoscrollLabel}>
|
<label style={styles.autoscrollLabel}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
@ -216,7 +243,11 @@ export const SerialMonitor: React.FC = () => {
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
})() : (activeBoard?.running ? 'Waiting for serial data...\n' : 'Start simulation to see serial output.\n')}
|
})() : (activeBoard?.running ? (
|
||||||
|
hardwareSerialActive
|
||||||
|
? 'Hardware serial connected. Waiting for data...\n'
|
||||||
|
: 'Waiting for serial data...\n'
|
||||||
|
) : 'Start simulation to see serial output.\n')}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
{/* Input row */}
|
{/* Input row */}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ export const EditorPage: React.FC = () => {
|
||||||
enabled: params.get('embed') === 'true',
|
enabled: params.get('embed') === 'true',
|
||||||
hideEditor: params.get('hideEditor') === 'true',
|
hideEditor: params.get('hideEditor') === 'true',
|
||||||
lockComponents: params.get('lockComponents') === 'true',
|
lockComponents: params.get('lockComponents') === 'true',
|
||||||
|
desktopLayout: params.get('desktopLayout') === 'true',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const [embedHideComponentPicker, setEmbedHideComponentPicker] = useState(false);
|
const [embedHideComponentPicker, setEmbedHideComponentPicker] = useState(false);
|
||||||
|
|
@ -146,8 +147,10 @@ export const EditorPage: React.FC = () => {
|
||||||
const [explorerOpen, setExplorerOpen] = useState(true);
|
const [explorerOpen, setExplorerOpen] = useState(true);
|
||||||
const [explorerWidth, setExplorerWidth] = useState(EXPLORER_DEFAULT);
|
const [explorerWidth, setExplorerWidth] = useState(EXPLORER_DEFAULT);
|
||||||
const [isMobile, setIsMobile] = useState(() => window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`).matches);
|
const [isMobile, setIsMobile] = useState(() => window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`).matches);
|
||||||
// Embed mode on mobile: show circuit first (student needs to wire); standalone: show code
|
// Embed mode with desktopLayout: show code first on mobile (developer playground)
|
||||||
const [mobileView, setMobileView] = useState<'code' | 'circuit'>(embedMode.enabled ? 'circuit' : 'code');
|
const [mobileView, setMobileView] = useState<'code' | 'circuit'>(
|
||||||
|
embedMode.desktopLayout ? 'code' : embedMode.enabled ? 'circuit' : 'code'
|
||||||
|
);
|
||||||
const user = useAuthStore((s) => s.user);
|
const user = useAuthStore((s) => s.user);
|
||||||
|
|
||||||
const handleSaveClick = useCallback(() => {
|
const handleSaveClick = useCallback(() => {
|
||||||
|
|
@ -183,16 +186,17 @@ export const EditorPage: React.FC = () => {
|
||||||
return () => window.removeEventListener('keydown', handler);
|
return () => window.removeEventListener('keydown', handler);
|
||||||
}, [handleSaveClick]);
|
}, [handleSaveClick]);
|
||||||
|
|
||||||
// Prevent body scroll on the editor page
|
// Prevent scroll gestures but allow viewport adjustment when keyboard appears
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
html.style.overflow = 'hidden';
|
// Use touch-action: none to prevent scroll gestures without blocking
|
||||||
body.style.overflow = 'hidden';
|
// browser viewport resize when virtual keyboard appears
|
||||||
window.scrollTo(0, 0);
|
html.style.touchAction = 'none';
|
||||||
|
body.style.touchAction = 'none';
|
||||||
return () => {
|
return () => {
|
||||||
html.style.overflow = '';
|
html.style.touchAction = '';
|
||||||
body.style.overflow = '';
|
body.style.touchAction = '';
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -268,7 +272,7 @@ export const EditorPage: React.FC = () => {
|
||||||
{!embedMode.enabled && <AppHeader />}
|
{!embedMode.enabled && <AppHeader />}
|
||||||
|
|
||||||
{/* ── Mobile tab bar (top, above panels) ── */}
|
{/* ── Mobile tab bar (top, above panels) ── */}
|
||||||
{isMobile && !(embedMode.enabled && embedMode.hideEditor) && (
|
{(isMobile || embedMode.desktopLayout) && !(embedMode.enabled && embedMode.hideEditor) && (
|
||||||
<nav className="mobile-tab-bar">
|
<nav className="mobile-tab-bar">
|
||||||
<button
|
<button
|
||||||
className={`mobile-tab-btn${mobileView === 'code' ? ' mobile-tab-btn--active' : ''}`}
|
className={`mobile-tab-btn${mobileView === 'code' ? ' mobile-tab-btn--active' : ''}`}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,11 @@ class EmbedBridge {
|
||||||
this.send('velxio:compile_result', { success });
|
this.send('velxio:compile_result', { success });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Notify parent that compilation finished and hex is available. Push model. */
|
||||||
|
notifyHexReady(boardId: string | null, hex: string | null, boardKind: string | null) {
|
||||||
|
this.send('velxio:hex_ready', { boardId, hex, boardKind });
|
||||||
|
}
|
||||||
|
|
||||||
/** Parent received ready — stop broadcasting. */
|
/** Parent received ready — stop broadcasting. */
|
||||||
private stopReadyBroadcast() {
|
private stopReadyBroadcast() {
|
||||||
if (this._readyInterval) {
|
if (this._readyInterval) {
|
||||||
|
|
@ -137,6 +142,27 @@ class EmbedBridge {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'elemes:push_serial': {
|
||||||
|
const text = (event.data.text as string) || '';
|
||||||
|
const targetBoardId = (event.data.boardId as string) || null;
|
||||||
|
const store = useSimulatorStore.getState();
|
||||||
|
const boardId = targetBoardId ?? (store.activeBoardId as string | null);
|
||||||
|
if (boardId) {
|
||||||
|
store.appendSerialOutput(boardId, text);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'elemes:start_hardware_serial': {
|
||||||
|
window.dispatchEvent(new CustomEvent('velxio-hardware-serial-start'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'elemes:stop_hardware_serial': {
|
||||||
|
window.dispatchEvent(new CustomEvent('velxio-hardware-serial-stop'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'elemes:ping': {
|
case 'elemes:ping': {
|
||||||
// Parent missed the initial ready signal — re-send it
|
// Parent missed the initial ready signal — re-send it
|
||||||
this.notifyReady();
|
this.notifyReady();
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,7 @@ interface SimulatorState {
|
||||||
serialWriteToBoard: (boardId: string, text: string) => void;
|
serialWriteToBoard: (boardId: string, text: string) => void;
|
||||||
clearSerialOutput: () => void;
|
clearSerialOutput: () => void;
|
||||||
clearBoardSerialOutput: (boardId: string) => void;
|
clearBoardSerialOutput: (boardId: string) => void;
|
||||||
|
appendSerialOutput: (boardId: string, text: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Helper: create a simulator for a given board kind ─────────────────────
|
// ── Helper: create a simulator for a given board kind ─────────────────────
|
||||||
|
|
@ -1343,6 +1344,16 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
boards: s.boards.map((b) => b.id === boardId ? { ...b, serialOutput: '' } : b),
|
boards: s.boards.map((b) => b.id === boardId ? { ...b, serialOutput: '' } : b),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
appendSerialOutput: (boardId: string, text: string) => {
|
||||||
|
const isActive = get().activeBoardId === boardId;
|
||||||
|
set((s) => ({
|
||||||
|
...(isActive ? { serialOutput: s.serialOutput + text } : {}),
|
||||||
|
boards: s.boards.map((b) =>
|
||||||
|
b.id === boardId ? { ...b, serialOutput: (b.serialOutput ?? '') + text } : b
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue