feat(editor): view-mode toggle, agent-chat slot, toolbar polish
Changes that ship to OSS — all benign for self-hosters, but most are extension points the velxio-prod overlay (and any private fork) needs to plug an in-editor AI chat into the page. Editor: - 3-way view-mode toggle (code / both / circuit) in the unified toolbar. Lets users hide a pane to give a right-docked sidebar (e.g. the AI chat overlay) more breathing room. Persisted in useEditorStore. - Default file explorer narrower (210 → 165 px); min 110. - Removed the redundant `tb-board-pill` (icon + "Editing: X" tooltip); the BoardSelector dropdown elsewhere already shows the active board. - Inlined Import/Export/Upload-firmware buttons; the 3-dot overflow menu gave up too much discoverability. Removed dead overflow state. Simulator: - Fix: global Delete/Backspace handler in SimulatorCanvas no longer fires when the event target is an INPUT/TEXTAREA/SELECT/contentEditable — affected any in-page text field, not just the chat overlay. Overlay extensibility: - New `data-velxio-slot="agent-chat"` at the bottom of EditorPage so pro overlays can portal a chat panel into the editor without forking the page. - vite.config.ts: preserveSymlinks=true when VITE_PRO_BUILD is set. Lets local-dev junctions (overlay tree → frontend/src/pro) resolve bare imports back to the OSS node_modules without resolving symlinks. Deps: - Added react-markdown + remark-gfm (rendered chat output) and @google/genai + zod (overlay agent loop). Tree-shaken from the OSS bundle when no pro code imports them. gitignore: - Ignore backend/app/pro/ and frontend/src/pro/ junctions used by developers running a private overlay against the OSS dev server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bb14ab663a
commit
70cc8c2e11
|
|
@ -40,6 +40,17 @@ frontend/dist/
|
|||
frontend/.vite/
|
||||
backend/dist/
|
||||
|
||||
# Local-dev junctions to a private overlay repo (e.g. velxio-prod). Created
|
||||
# by the user when running the pro stack against the OSS dev server; never
|
||||
# committed because they point at out-of-tree code that isn't part of the
|
||||
# open-source project. The OSS Vite alias falls back to a no-op stub when
|
||||
# these aren't present.
|
||||
backend/app/pro/
|
||||
frontend/src/pro/
|
||||
|
||||
# Temporary vitest config used to run pro overlay tests locally.
|
||||
frontend/pro-vitest.config.ts
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"test:ui": "vitest --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^0.6.0",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@wokwi/elements": "1.9.2",
|
||||
|
|
@ -39,10 +40,13 @@
|
|||
"lucide-react": "^0.460.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^7.13.1",
|
||||
"rp2040js": "1.3.2",
|
||||
"react-syntax-highlighter": "^16.1.1",
|
||||
"recharts": "^3.8.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"rp2040js": "1.3.2",
|
||||
"zod": "^3.23.8",
|
||||
"zustand": "^5.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -367,6 +367,13 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
/* ── View-mode toggle: desktop only (mobile uses bottom-nav) ─────── */
|
||||
@media (max-width: 768px) {
|
||||
.view-mode-toggle {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Header responsive ───────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.app-header {
|
||||
|
|
|
|||
|
|
@ -113,25 +113,8 @@ export const EditorToolbar = ({
|
|||
const importInputRef = useRef<HTMLInputElement>(null);
|
||||
const firmwareInputRef = useRef<HTMLInputElement>(null);
|
||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||
const [overflowOpen, setOverflowOpen] = useState(false);
|
||||
const overflowMenuRef = useRef<HTMLDivElement>(null);
|
||||
const [missingLibHint, setMissingLibHint] = useState(false);
|
||||
|
||||
// (ResizeObserver removed — Library Manager is always visible now,
|
||||
// only import/export live in the overflow menu)
|
||||
|
||||
// Close overflow dropdown on outside click
|
||||
useEffect(() => {
|
||||
if (!overflowOpen) return;
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (overflowMenuRef.current && !overflowMenuRef.current.contains(e.target as Node)) {
|
||||
setOverflowOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handler);
|
||||
return () => document.removeEventListener('mousedown', handler);
|
||||
}, [overflowOpen]);
|
||||
|
||||
// Compile All / Run All — runs sequentially, logs to console (no dialog)
|
||||
const [compileAllRunning, setCompileAllRunning] = useState(false);
|
||||
|
||||
|
|
@ -635,48 +618,33 @@ export const EditorToolbar = ({
|
|||
<>
|
||||
<div className="editor-toolbar-wrapper" style={{ position: 'relative' }}>
|
||||
<div className="editor-toolbar" ref={toolbarRef}>
|
||||
{/* Active board context pill */}
|
||||
{activeBoard && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<div
|
||||
className="tb-board-pill"
|
||||
style={{
|
||||
borderColor: BOARD_PILL_COLOR[activeBoard.boardKind],
|
||||
color: BOARD_PILL_COLOR[activeBoard.boardKind],
|
||||
}}
|
||||
title={`Editing: ${BOARD_KIND_LABELS[activeBoard.boardKind]}`}
|
||||
>
|
||||
<span className="tb-board-pill-icon">{BOARD_PILL_ICON[activeBoard.boardKind]}</span>
|
||||
<span className="tb-board-pill-label">
|
||||
{BOARD_KIND_LABELS[activeBoard.boardKind]}
|
||||
</span>
|
||||
{activeBoard.running && <span className="tb-board-pill-running" title="Running" />}
|
||||
</div>
|
||||
{BOARD_SUPPORTS_MICROPYTHON.has(activeBoard.boardKind) && (
|
||||
<select
|
||||
className="tb-lang-select"
|
||||
value={activeBoard.languageMode ?? 'arduino'}
|
||||
onChange={(e) => {
|
||||
if (activeBoardId)
|
||||
setBoardLanguageMode(activeBoardId, e.target.value as LanguageMode);
|
||||
}}
|
||||
title="Language mode"
|
||||
style={{
|
||||
background: '#2d2d2d',
|
||||
color: '#ccc',
|
||||
border: '1px solid #444',
|
||||
borderRadius: 4,
|
||||
padding: '2px 4px',
|
||||
fontSize: 11,
|
||||
cursor: 'pointer',
|
||||
outline: 'none',
|
||||
}}
|
||||
>
|
||||
<option value="arduino">Arduino C++</option>
|
||||
<option value="micropython">MicroPython</option>
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
{/* MicroPython language selector — only when active board supports it.
|
||||
The board context pill that used to live here was removed: it
|
||||
duplicated the BoardSelector dropdown elsewhere in the toolbar. */}
|
||||
{activeBoard && BOARD_SUPPORTS_MICROPYTHON.has(activeBoard.boardKind) && (
|
||||
<select
|
||||
className="tb-lang-select"
|
||||
value={activeBoard.languageMode ?? 'arduino'}
|
||||
onChange={(e) => {
|
||||
if (activeBoardId)
|
||||
setBoardLanguageMode(activeBoardId, e.target.value as LanguageMode);
|
||||
}}
|
||||
title="Language mode"
|
||||
style={{
|
||||
background: '#2d2d2d',
|
||||
color: '#ccc',
|
||||
border: '1px solid #444',
|
||||
borderRadius: 4,
|
||||
padding: '2px 4px',
|
||||
fontSize: 11,
|
||||
cursor: 'pointer',
|
||||
outline: 'none',
|
||||
marginRight: 4,
|
||||
}}
|
||||
>
|
||||
<option value="arduino">Arduino C++</option>
|
||||
<option value="micropython">MicroPython</option>
|
||||
</select>
|
||||
)}
|
||||
|
||||
<div className="toolbar-group">
|
||||
|
|
@ -868,95 +836,42 @@ export const EditorToolbar = ({
|
|||
<span className="tb-libraries-label">Libraries</span>
|
||||
</button>
|
||||
|
||||
{/* Import / Export — overflow menu */}
|
||||
<div className="tb-overflow-wrap" ref={overflowMenuRef}>
|
||||
<button
|
||||
onClick={() => setOverflowOpen((v) => !v)}
|
||||
className={`tb-btn tb-btn-overflow${overflowOpen ? ' tb-btn-overflow-active' : ''}`}
|
||||
title="Import / Export"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<circle cx="5" cy="12" r="2" />
|
||||
<circle cx="12" cy="12" r="2" />
|
||||
<circle cx="19" cy="12" r="2" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{overflowOpen && (
|
||||
<div className="tb-overflow-menu">
|
||||
<button
|
||||
className="tb-overflow-item"
|
||||
onClick={() => {
|
||||
importInputRef.current?.click();
|
||||
setOverflowOpen(false);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||
<polyline points="7 10 12 15 17 10" />
|
||||
<line x1="12" y1="15" x2="12" y2="3" />
|
||||
</svg>
|
||||
Import zip
|
||||
</button>
|
||||
<button
|
||||
className="tb-overflow-item"
|
||||
onClick={() => {
|
||||
handleExport();
|
||||
setOverflowOpen(false);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||
<polyline points="17 8 12 3 7 8" />
|
||||
<line x1="12" y1="3" x2="12" y2="15" />
|
||||
</svg>
|
||||
Export zip
|
||||
</button>
|
||||
<div style={{ borderTop: '1px solid #3c3c3c', margin: '4px 0' }} />
|
||||
<button
|
||||
className="tb-overflow-item"
|
||||
onClick={() => {
|
||||
firmwareInputRef.current?.click();
|
||||
setOverflowOpen(false);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
|
||||
<line x1="12" y1="15" x2="12" y2="22" />
|
||||
<polyline points="8 18 12 22 16 18" />
|
||||
</svg>
|
||||
Upload firmware (.hex, .bin, .elf)
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Import zip — was previously hidden in a 3-dot overflow menu;
|
||||
inlined since there's space and the discoverability cost
|
||||
outweighed the toolbar savings. */}
|
||||
<button
|
||||
onClick={() => importInputRef.current?.click()}
|
||||
className="tb-btn"
|
||||
title="Import a project from a .zip file"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||
<polyline points="7 10 12 15 17 10" />
|
||||
<line x1="12" y1="15" x2="12" y2="3" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleExport()}
|
||||
className="tb-btn"
|
||||
title="Export the current project as a .zip file"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||
<polyline points="17 8 12 3 7 8" />
|
||||
<line x1="12" y1="3" x2="12" y2="15" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => firmwareInputRef.current?.click()}
|
||||
className="tb-btn"
|
||||
title="Upload firmware (.hex, .bin, .elf, .ihex) to bypass compilation"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
|
||||
<line x1="12" y1="15" x2="12" y2="22" />
|
||||
<polyline points="8 18 12 22 16 18" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className="tb-divider" />
|
||||
|
||||
|
|
|
|||
|
|
@ -955,6 +955,16 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
|
|||
// Handle keyboard delete for components and boards
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Skip when the user is typing in an input/textarea/contenteditable —
|
||||
// otherwise Backspace inside the AI chat (or any future text field)
|
||||
// also asks to delete the active board.
|
||||
const t = e.target as HTMLElement | null;
|
||||
if (t) {
|
||||
const tag = t.tagName;
|
||||
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || t.isContentEditable) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||
if (selectedComponentId) {
|
||||
removeComponent(selectedComponentId);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { SaveProjectModal } from '../components/layout/SaveProjectModal';
|
|||
import { LoginPromptModal } from '../components/layout/LoginPromptModal';
|
||||
import { GitHubStarBanner } from '../components/layout/GitHubStarBanner';
|
||||
import { useSimulatorStore, DEFAULT_BOARD_POSITION } from '../store/useSimulatorStore';
|
||||
import { useEditorStore } from '../store/useEditorStore';
|
||||
import { useOscilloscopeStore } from '../store/useOscilloscopeStore';
|
||||
import { useAuthStore } from '../store/useAuthStore';
|
||||
import { useProjectStore } from '../store/useProjectStore';
|
||||
|
|
@ -38,9 +39,9 @@ const BOTTOM_PANEL_MIN = 80;
|
|||
const BOTTOM_PANEL_MAX = 600;
|
||||
const BOTTOM_PANEL_DEFAULT = 200;
|
||||
|
||||
const EXPLORER_MIN = 120;
|
||||
const EXPLORER_MIN = 110;
|
||||
const EXPLORER_MAX = 500;
|
||||
const EXPLORER_DEFAULT = 210;
|
||||
const EXPLORER_DEFAULT = 165;
|
||||
|
||||
const resizeHandleStyle: React.CSSProperties = {
|
||||
height: 5,
|
||||
|
|
@ -64,6 +65,10 @@ export const EditorPage: React.FC = () => {
|
|||
const autoSave = useAutoSaveProject();
|
||||
|
||||
const [editorWidthPct, setEditorWidthPct] = useState(45);
|
||||
// Desktop-only 3-way layout switch (code-only / circuit-only / both).
|
||||
// Lets users hide a pane to give the right-docked chat more room.
|
||||
const viewMode = useEditorStore((s) => s.viewMode);
|
||||
const setViewMode = useEditorStore((s) => s.setViewMode);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const resizingRef = useRef(false);
|
||||
const serialMonitorOpen = useSimulatorStore((s) => s.serialMonitorOpen);
|
||||
|
|
@ -355,6 +360,66 @@ export const EditorPage: React.FC = () => {
|
|||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
{/* View-mode toggle: Code / Both / Circuit. Lets users hide a
|
||||
pane to give the right-docked AI chat more breathing room.
|
||||
Hidden on mobile — there's already a code/circuit toggle in
|
||||
the mobile bottom-nav. */}
|
||||
<div
|
||||
role="group"
|
||||
aria-label="View mode"
|
||||
className="view-mode-toggle"
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: 1,
|
||||
background: '#252526',
|
||||
border: '1px solid #3c3c3c',
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
alignSelf: 'center',
|
||||
margin: '0 6px',
|
||||
}}
|
||||
>
|
||||
{(
|
||||
[
|
||||
{ key: 'code', label: 'Code', path: 'M16 18l6-6-6-6M8 6l-6 6 6 6' },
|
||||
{ key: 'both', label: 'Both', path: 'M3 3h7v18H3zM14 3h7v18h-7z' },
|
||||
{ key: 'circuit', label: 'Circuit', path: 'M5 12h14M12 5v14' },
|
||||
] as const
|
||||
).map((m) => (
|
||||
<button
|
||||
key={m.key}
|
||||
onClick={() => setViewMode(m.key)}
|
||||
aria-pressed={viewMode === m.key}
|
||||
style={{
|
||||
background: viewMode === m.key ? '#0e639c' : 'transparent',
|
||||
color: viewMode === m.key ? 'white' : '#aaa',
|
||||
border: 'none',
|
||||
height: 28,
|
||||
padding: '0 10px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
cursor: 'pointer',
|
||||
fontSize: 12,
|
||||
fontFamily: 'inherit',
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d={m.path} />
|
||||
</svg>
|
||||
<span>{m.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="unified-toolbar-editor">
|
||||
<EditorToolbar
|
||||
consoleOpen={consoleOpen}
|
||||
|
|
@ -373,8 +438,17 @@ export const EditorPage: React.FC = () => {
|
|||
<div
|
||||
className="editor-panel"
|
||||
style={{
|
||||
width: isMobile ? '100%' : `${editorWidthPct}%`,
|
||||
display: isMobile && mobileView !== 'code' ? 'none' : 'flex',
|
||||
width: isMobile
|
||||
? '100%'
|
||||
: viewMode === 'code'
|
||||
? '100%'
|
||||
: viewMode === 'circuit'
|
||||
? '0%'
|
||||
: `${editorWidthPct}%`,
|
||||
display:
|
||||
(isMobile && mobileView !== 'code') || (!isMobile && viewMode === 'circuit')
|
||||
? 'none'
|
||||
: 'flex',
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
|
|
@ -477,8 +551,8 @@ export const EditorPage: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Resize handle (desktop only) */}
|
||||
{!isMobile && (
|
||||
{/* Resize handle (desktop only, and only when both panes are visible) */}
|
||||
{!isMobile && viewMode === 'both' && (
|
||||
<div className="resize-handle" onMouseDown={handleResizeMouseDown}>
|
||||
<div className="resize-handle-grip" />
|
||||
</div>
|
||||
|
|
@ -488,8 +562,17 @@ export const EditorPage: React.FC = () => {
|
|||
<div
|
||||
className="simulator-panel"
|
||||
style={{
|
||||
width: isMobile ? '100%' : `${100 - editorWidthPct}%`,
|
||||
display: isMobile && mobileView !== 'circuit' ? 'none' : 'flex',
|
||||
width: isMobile
|
||||
? '100%'
|
||||
: viewMode === 'circuit'
|
||||
? '100%'
|
||||
: viewMode === 'code'
|
||||
? '0%'
|
||||
: `${100 - editorWidthPct}%`,
|
||||
display:
|
||||
(isMobile && mobileView !== 'circuit') || (!isMobile && viewMode === 'code')
|
||||
? 'none'
|
||||
: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
|
|
@ -526,6 +609,9 @@ export const EditorPage: React.FC = () => {
|
|||
{saveModalOpen && <SaveProjectModal onClose={() => setSaveModalOpen(false)} />}
|
||||
{loginPromptOpen && <LoginPromptModal onClose={() => setLoginPromptOpen(false)} />}
|
||||
{showStarBanner && <GitHubStarBanner onClose={handleDismissStarBanner} />}
|
||||
{/* Slot reserved for the private pro overlay (e.g. agent chat panel).
|
||||
Self-hosted builds without an overlay see nothing here. */}
|
||||
<div data-velxio-slot="agent-chat" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -78,12 +78,21 @@ const DEFAULT_FILE: WorkspaceFile = {
|
|||
/** Default file group for the initial Arduino Uno board */
|
||||
const DEFAULT_GROUP_ID = 'group-arduino-uno';
|
||||
|
||||
/**
|
||||
* Editor view layout. Lets the user collapse either pane to give the chat
|
||||
* (right-docked) more breathing room, or to focus on one half of the
|
||||
* workflow.
|
||||
*/
|
||||
export type EditorViewMode = 'code' | 'circuit' | 'both';
|
||||
|
||||
interface EditorState {
|
||||
files: WorkspaceFile[];
|
||||
activeFileId: string;
|
||||
openFileIds: string[];
|
||||
theme: 'vs-dark' | 'light';
|
||||
fontSize: number;
|
||||
viewMode: EditorViewMode;
|
||||
setViewMode: (mode: EditorViewMode) => void;
|
||||
|
||||
// ── File groups (one per board) ──────────────────────────────────────────
|
||||
/** Map of groupId → WorkspaceFile[]. Stored as plain object for Zustand. */
|
||||
|
|
@ -137,6 +146,8 @@ export const useEditorStore = create<EditorState>((set, get) => ({
|
|||
openFileIds: [MAIN_ID],
|
||||
theme: 'vs-dark',
|
||||
fontSize: 14,
|
||||
viewMode: 'both',
|
||||
setViewMode: (mode) => set({ viewMode: mode }),
|
||||
|
||||
// File groups — initial state has one group for the default Arduino Uno board
|
||||
fileGroups: {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,12 @@ export default defineConfig({
|
|||
alias: {
|
||||
'@pro': proOverlayPath,
|
||||
},
|
||||
// When the pro overlay is wired in via a junction (Windows local-dev
|
||||
// pattern), Vite's default resolution walks symlinks to the real path,
|
||||
// which breaks relative imports from pro into upstream sibling dirs.
|
||||
// Keeping the symlink-as-path lets `../../store/...` from pro resolve
|
||||
// back into the OSS tree's src/.
|
||||
preserveSymlinks: !!process.env.VITE_PRO_BUILD,
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue