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;
|
||||
|
||||
# Frontend SPA routing — must be last so specific locations above take precedence
|
||||
location = / {
|
||||
return 301 /velxio/editor;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<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
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
"@types/node": "^24.11.0",
|
||||
"@types/react": "^19.2.7",
|
||||
"@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/ui": "^4.0.18",
|
||||
"eslint": "^9.39.1",
|
||||
|
|
@ -38,77 +38,16 @@
|
|||
"jsdom": "^28.1.0",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.48.0",
|
||||
"vite": "^7.3.1",
|
||||
"vite": "^7.3.6",
|
||||
"vitest": "^4.0.18"
|
||||
}
|
||||
},
|
||||
"../wokwi-libs/avr8js": {
|
||||
"version": "0.21.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"
|
||||
}
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"../wokwi-libs/wokwi-elements": {
|
||||
"name": "@wokwi/elements",
|
||||
"version": "1.9.2",
|
||||
"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"
|
||||
}
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"node_modules/@acemir/cssom": {
|
||||
"version": "0.9.31",
|
||||
|
|
@ -2176,9 +2115,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
|
||||
"integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
|
||||
"integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -2193,7 +2132,7 @@
|
|||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"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": {
|
||||
|
|
@ -5007,13 +4946,13 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
|
||||
"integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"esbuild": "^0.27.0 || ^0.28.0",
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"postcss": "^8.5.6",
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
"@types/node": "^24.11.0",
|
||||
"@types/react": "^19.2.7",
|
||||
"@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/ui": "^4.0.18",
|
||||
"eslint": "^9.39.1",
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
"jsdom": "^28.1.0",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.48.0",
|
||||
"vite": "^7.3.1",
|
||||
"vite": "^7.3.6",
|
||||
"vitest": "^4.0.18"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,169 +4,169 @@
|
|||
|
||||
<url>
|
||||
<loc>https://velxio.dev/</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/editor</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/examples</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/intro</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/getting-started</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/emulator</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/esp32-emulation</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/riscv-emulation</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/rp2040-emulation</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/raspberry-pi3-emulation</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/components</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/architecture</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/wokwi-libs</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/mcp</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/setup</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/docs/roadmap</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/arduino-simulator</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/arduino-emulator</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/atmega328p-simulator</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/arduino-mega-simulator</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/esp32-simulator</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/esp32-s3-simulator</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/esp32-c3-simulator</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/raspberry-pi-pico-simulator</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/raspberry-pi-simulator</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.85</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/v2</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://velxio.dev/about</loc>
|
||||
<lastmod>2026-04-01</lastmod>
|
||||
<lastmod>2026-07-05</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ body {
|
|||
|
||||
/* ── App shell ──────────────────────────────────── */
|
||||
.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;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
|
@ -160,7 +161,8 @@ body {
|
|||
.app-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
overflow-y: auto; /* Allow scroll if content taller than container */
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,26 +41,29 @@ export const CodeEditor = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', width: '100%' }}>
|
||||
<Editor
|
||||
// key forces a fresh editor instance per file (preserves undo/redo per file)
|
||||
key={activeFileId}
|
||||
height="100%"
|
||||
language={activeFile ? getLanguage(activeFile.name) : 'cpp'}
|
||||
theme={theme}
|
||||
value={activeFile?.content ?? ''}
|
||||
onChange={(value) => {
|
||||
if (activeFileId) setFileContent(activeFileId, value || '');
|
||||
}}
|
||||
onMount={handleEditorMount}
|
||||
options={{
|
||||
minimap: { enabled: true },
|
||||
fontSize,
|
||||
automaticLayout: true,
|
||||
scrollBeyondLastLine: false,
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
/>
|
||||
<div style={{ height: '100%', width: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ flex: 1, minHeight: 0 }}>
|
||||
<Editor
|
||||
// key forces a fresh editor instance per file (preserves undo/redo per file)
|
||||
key={activeFileId}
|
||||
height="100%"
|
||||
language={activeFile ? getLanguage(activeFile.name) : 'cpp'}
|
||||
theme={theme}
|
||||
value={activeFile?.content ?? ''}
|
||||
onChange={(value) => {
|
||||
if (activeFileId) setFileContent(activeFileId, value || '');
|
||||
}}
|
||||
onMount={handleEditorMount}
|
||||
options={{
|
||||
minimap: { enabled: true },
|
||||
fontSize,
|
||||
automaticLayout: true,
|
||||
scrollBeyondLastLine: true,
|
||||
wordWrap: 'on',
|
||||
stickyScroll: { enabled: false },
|
||||
}}
|
||||
/>
|
||||
</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();
|
||||
setMissingLibHint(false);
|
||||
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 {
|
||||
const errText = result.error || result.stderr || 'Compile failed';
|
||||
setMessage({ type: 'error', text: errText });
|
||||
|
|
|
|||
|
|
@ -57,8 +57,21 @@ export const SerialMonitor: React.FC = () => {
|
|||
const [inputValue, setInputValue] = useState('');
|
||||
const [lineEnding, setLineEnding] = useState<'none' | 'nl' | 'cr' | 'both'>('nl');
|
||||
const [autoscroll, setAutoscroll] = useState(true);
|
||||
const [hardwareSerialActive, setHardwareSerialActive] = useState(false);
|
||||
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
|
||||
useEffect(() => {
|
||||
if (activeBoardId) setActiveTabId(activeBoardId);
|
||||
|
|
@ -153,6 +166,20 @@ export const SerialMonitor: React.FC = () => {
|
|||
{activeBoard?.serialBaudRate != null && activeBoard.serialBaudRate > 0 && (
|
||||
<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}>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
|
@ -216,7 +243,11 @@ export const SerialMonitor: React.FC = () => {
|
|||
return parts;
|
||||
}
|
||||
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>
|
||||
|
||||
{/* Input row */}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export const EditorPage: React.FC = () => {
|
|||
enabled: params.get('embed') === 'true',
|
||||
hideEditor: params.get('hideEditor') === 'true',
|
||||
lockComponents: params.get('lockComponents') === 'true',
|
||||
desktopLayout: params.get('desktopLayout') === 'true',
|
||||
};
|
||||
});
|
||||
const [embedHideComponentPicker, setEmbedHideComponentPicker] = useState(false);
|
||||
|
|
@ -146,8 +147,10 @@ export const EditorPage: React.FC = () => {
|
|||
const [explorerOpen, setExplorerOpen] = useState(true);
|
||||
const [explorerWidth, setExplorerWidth] = useState(EXPLORER_DEFAULT);
|
||||
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
|
||||
const [mobileView, setMobileView] = useState<'code' | 'circuit'>(embedMode.enabled ? 'circuit' : 'code');
|
||||
// Embed mode with desktopLayout: show code first on mobile (developer playground)
|
||||
const [mobileView, setMobileView] = useState<'code' | 'circuit'>(
|
||||
embedMode.desktopLayout ? 'code' : embedMode.enabled ? 'circuit' : 'code'
|
||||
);
|
||||
const user = useAuthStore((s) => s.user);
|
||||
|
||||
const handleSaveClick = useCallback(() => {
|
||||
|
|
@ -183,16 +186,17 @@ export const EditorPage: React.FC = () => {
|
|||
return () => window.removeEventListener('keydown', handler);
|
||||
}, [handleSaveClick]);
|
||||
|
||||
// Prevent body scroll on the editor page
|
||||
// Prevent scroll gestures but allow viewport adjustment when keyboard appears
|
||||
useEffect(() => {
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
html.style.overflow = 'hidden';
|
||||
body.style.overflow = 'hidden';
|
||||
window.scrollTo(0, 0);
|
||||
// Use touch-action: none to prevent scroll gestures without blocking
|
||||
// browser viewport resize when virtual keyboard appears
|
||||
html.style.touchAction = 'none';
|
||||
body.style.touchAction = 'none';
|
||||
return () => {
|
||||
html.style.overflow = '';
|
||||
body.style.overflow = '';
|
||||
html.style.touchAction = '';
|
||||
body.style.touchAction = '';
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -268,7 +272,7 @@ export const EditorPage: React.FC = () => {
|
|||
{!embedMode.enabled && <AppHeader />}
|
||||
|
||||
{/* ── Mobile tab bar (top, above panels) ── */}
|
||||
{isMobile && !(embedMode.enabled && embedMode.hideEditor) && (
|
||||
{(isMobile || embedMode.desktopLayout) && !(embedMode.enabled && embedMode.hideEditor) && (
|
||||
<nav className="mobile-tab-bar">
|
||||
<button
|
||||
className={`mobile-tab-btn${mobileView === 'code' ? ' mobile-tab-btn--active' : ''}`}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ class EmbedBridge {
|
|||
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. */
|
||||
private stopReadyBroadcast() {
|
||||
if (this._readyInterval) {
|
||||
|
|
@ -137,6 +142,27 @@ class EmbedBridge {
|
|||
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': {
|
||||
// Parent missed the initial ready signal — re-send it
|
||||
this.notifyReady();
|
||||
|
|
|
|||
|
|
@ -263,6 +263,7 @@ interface SimulatorState {
|
|||
serialWriteToBoard: (boardId: string, text: string) => void;
|
||||
clearSerialOutput: () => void;
|
||||
clearBoardSerialOutput: (boardId: string) => void;
|
||||
appendSerialOutput: (boardId: string, text: string) => void;
|
||||
}
|
||||
|
||||
// ── 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),
|
||||
}));
|
||||
},
|
||||
|
||||
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