feat(editor): enhance toolbar layout with center slot for file tabs and improve responsiveness
This commit is contained in:
parent
7a26d01965
commit
b39041ca4c
|
|
@ -1,45 +1,89 @@
|
|||
/* ── Toolbar container ──────────────────────────── */
|
||||
/* ── Toolbar container ──────────────────────────────────────────────────────
|
||||
* Single-row layout: [left actions] [file tabs flex] [right actions]
|
||||
* The center slot (file tabs) flex-grows and scrolls horizontally so the
|
||||
* action icons on either side stay pinned and visible no matter how narrow
|
||||
* the editor pane gets when the user drags the editor/canvas divider.
|
||||
*
|
||||
* Uses CSS container queries so the Libraries label collapses based on the
|
||||
* toolbar's *own* width (not the viewport) — which is what changes when the
|
||||
* user drags the editor/canvas divider.
|
||||
*/
|
||||
.editor-toolbar-wrapper {
|
||||
container-type: inline-size;
|
||||
container-name: editor-toolbar;
|
||||
}
|
||||
.editor-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
height: 46px;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
padding: 0 6px;
|
||||
height: 38px;
|
||||
background: #252526;
|
||||
border-bottom: 1px solid #333;
|
||||
flex-shrink: 0;
|
||||
gap: 8px;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* ── Groups ─────────────────────────────────────── */
|
||||
.toolbar-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
gap: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-group-right {
|
||||
margin-left: auto;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* Center slot — fills the gap between left/right groups, allows file tabs
|
||||
to scroll horizontally inside it without pushing the icons off-screen. */
|
||||
.toolbar-center-slot {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
}
|
||||
.toolbar-center-slot > .file-tabs {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-bottom: none;
|
||||
background: transparent;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.toolbar-center-slot .file-tab {
|
||||
height: 100%;
|
||||
padding: 0 8px;
|
||||
min-width: 70px;
|
||||
max-width: 160px;
|
||||
font-size: 11.5px;
|
||||
}
|
||||
.toolbar-center-slot .file-tab-active {
|
||||
background: #1e1e1e;
|
||||
border-top: 2px solid #4fc3f7;
|
||||
}
|
||||
|
||||
/* ── Divider ─────────────────────────────────────── */
|
||||
.tb-divider {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
height: 18px;
|
||||
background: #3a3a3a;
|
||||
margin: 0 4px;
|
||||
margin: 0 3px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* ── Icon buttons ───────────────────────────────── */
|
||||
.tb-btn {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
color: #9d9d9d;
|
||||
|
|
@ -47,6 +91,7 @@
|
|||
background 0.12s,
|
||||
color 0.12s;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.tb-btn:disabled {
|
||||
|
|
@ -94,14 +139,15 @@
|
|||
.tb-btn-libraries {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 10px 4px 8px;
|
||||
gap: 4px;
|
||||
height: 26px;
|
||||
padding: 0 8px 0 7px;
|
||||
border: 1px solid rgba(0, 184, 212, 0.3);
|
||||
border-radius: 6px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 184, 212, 0.08);
|
||||
color: #00e5ff;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
white-space: nowrap;
|
||||
|
|
@ -110,6 +156,7 @@
|
|||
border-color 0.15s,
|
||||
color 0.15s;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
.tb-btn-libraries:hover {
|
||||
background: rgba(0, 184, 212, 0.18);
|
||||
|
|
@ -119,6 +166,27 @@
|
|||
.tb-libraries-label {
|
||||
line-height: 1;
|
||||
}
|
||||
/* Hide the label when the toolbar is narrow — icon alone keeps the button
|
||||
reachable without stealing horizontal space from the file tabs. The
|
||||
container query reacts to the toolbar's own width, so it triggers when
|
||||
the user drags the editor/canvas divider, not just on small viewports. */
|
||||
@container editor-toolbar (max-width: 600px) {
|
||||
.tb-libraries-label {
|
||||
display: none;
|
||||
}
|
||||
.tb-btn-libraries {
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
/* When even narrower, also collapse the board pill label. */
|
||||
@container editor-toolbar (max-width: 460px) {
|
||||
.tb-board-pill-label {
|
||||
display: none;
|
||||
}
|
||||
.tb-board-pill {
|
||||
padding: 3px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Legacy lib icon-only button (for overflow items) */
|
||||
.tb-btn-lib {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ interface EditorToolbarProps {
|
|||
setConsoleOpen: (open: boolean | ((v: boolean) => boolean)) => void;
|
||||
compileLogs: CompilationLog[];
|
||||
setCompileLogs: (logs: CompilationLog[] | ((prev: CompilationLog[]) => CompilationLog[])) => void;
|
||||
/**
|
||||
* Optional element rendered between the left action group and the right
|
||||
* action group. The editor passes <FileTabs /> here so the tabs share the
|
||||
* same row as the toolbar — keeping every action icon pinned and visible
|
||||
* regardless of how narrow the editor pane gets.
|
||||
*/
|
||||
centerSlot?: React.ReactNode;
|
||||
}
|
||||
|
||||
const BOARD_PILL_ICON: Record<BoardKind, string> = {
|
||||
|
|
@ -57,6 +64,7 @@ export const EditorToolbar = ({
|
|||
setConsoleOpen,
|
||||
compileLogs: _compileLogs,
|
||||
setCompileLogs,
|
||||
centerSlot,
|
||||
}: EditorToolbarProps) => {
|
||||
const { files, codeChangedSinceLastCompile, markCompiled } = useEditorStore();
|
||||
const {
|
||||
|
|
@ -613,8 +621,8 @@ export const EditorToolbar = ({
|
|||
>
|
||||
{compiling ? (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
|
|
@ -627,8 +635,8 @@ export const EditorToolbar = ({
|
|||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
|
|
@ -656,7 +664,7 @@ export const EditorToolbar = ({
|
|||
: 'Run (auto-compiles if needed)'
|
||||
}
|
||||
>
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<polygon points="5,3 19,12 5,21" />
|
||||
</svg>
|
||||
</button>
|
||||
|
|
@ -668,7 +676,7 @@ export const EditorToolbar = ({
|
|||
className="tb-btn tb-btn-stop"
|
||||
title="Stop"
|
||||
>
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||
</svg>
|
||||
</button>
|
||||
|
|
@ -681,8 +689,8 @@ export const EditorToolbar = ({
|
|||
title="Reset"
|
||||
>
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
|
|
@ -707,8 +715,8 @@ export const EditorToolbar = ({
|
|||
title="Compile all boards"
|
||||
>
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
|
|
@ -728,7 +736,7 @@ export const EditorToolbar = ({
|
|||
className="tb-btn tb-btn-run-all"
|
||||
title="Run all boards"
|
||||
>
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<polygon points="3,3 11,12 3,21" />
|
||||
<polygon points="13,3 21,12 13,21" />
|
||||
</svg>
|
||||
|
|
@ -737,6 +745,9 @@ export const EditorToolbar = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Center slot — file tabs share the row so action icons stay pinned. */}
|
||||
{centerSlot && <div className="toolbar-center-slot">{centerSlot}</div>}
|
||||
|
||||
<div className="toolbar-group toolbar-group-right">
|
||||
{/* Hidden file input for import (always present) */}
|
||||
<input
|
||||
|
|
@ -765,8 +776,8 @@ export const EditorToolbar = ({
|
|||
title="Search and install Arduino libraries"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
|
|
@ -788,7 +799,7 @@ export const EditorToolbar = ({
|
|||
className={`tb-btn tb-btn-overflow${overflowOpen ? ' tb-btn-overflow-active' : ''}`}
|
||||
title="Import / Export"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<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" />
|
||||
|
|
@ -880,8 +891,8 @@ export const EditorToolbar = ({
|
|||
title="Toggle Output Console"
|
||||
>
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
|
|
|
|||
|
|
@ -66,16 +66,67 @@ const COMP_DEFS: Record<string, CompDef> = {
|
|||
'wokwi-servo': { svg: 'servo.svg', w: 170, h: 120 },
|
||||
};
|
||||
|
||||
// ── Inline ATtiny85 SVG (no wokwi-element exists for this DIP-8 chip) ───────
|
||||
// Small schematic-style DIP-8 so multi-board ATtiny85 examples render in the
|
||||
// gallery preview. Sizes match the canvas board size (160×100 — see
|
||||
// `BoardOnCanvas.tsx::BOARD_SIZE.attiny85`) so wire centres land on the chip.
|
||||
const Attiny85InlinePreview: React.FC<{ w: number; h: number }> = ({ w, h }) => (
|
||||
<svg width={w} height={h} viewBox="0 0 160 100" xmlns="http://www.w3.org/2000/svg">
|
||||
{/* Pin stubs (left + right, 4 each) */}
|
||||
{[20, 40, 60, 80].map((py) => (
|
||||
<g key={py}>
|
||||
<line x1="30" y1={py} x2="2" y2={py} stroke="#aaa" strokeWidth="3" strokeLinecap="round" />
|
||||
<line
|
||||
x1="130"
|
||||
y1={py}
|
||||
x2="158"
|
||||
y2={py}
|
||||
stroke="#aaa"
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</g>
|
||||
))}
|
||||
{/* IC body */}
|
||||
<rect x="30" y="10" width="100" height="80" rx="4" fill="#1a1a2e" stroke="#4a4a7a" strokeWidth="1.5" />
|
||||
<path d="M73 10 A7 7 0 0 1 87 10" fill="none" stroke="#4a4a7a" strokeWidth="1.5" />
|
||||
<text x="80" y="48" fontSize="10" fontWeight="bold" fontFamily="monospace" fill="#c8c8f0" textAnchor="middle">
|
||||
ATtiny85
|
||||
</text>
|
||||
<text x="80" y="62" fontSize="8" fontFamily="monospace" fill="#7a7aaa" textAnchor="middle">
|
||||
8-bit AVR
|
||||
</text>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Board SVG definitions — used when the board is implicit (pico / esp32 pattern)
|
||||
// or when a multi-board example references the board by `boardKind`.
|
||||
//
|
||||
// IMPORTANT: every entry from `BoardKind` (frontend/src/types/board.ts) must
|
||||
// appear here, otherwise the gallery card falls back to "No components" and
|
||||
// the user sees an empty preview. New board kinds must be added in BOTH
|
||||
// places (the type union and this map).
|
||||
const BOARD_DEFS: Record<string, CompDef> = {
|
||||
'arduino-uno': { svg: 'arduino-uno.svg', w: 274, h: 202 },
|
||||
'arduino-nano': { svg: 'arduino-nano.svg', w: 170, h: 67 },
|
||||
'arduino-mega': { svg: 'arduino-mega.svg', w: 388, h: 192 },
|
||||
'raspberry-pi-pico': { svg: 'raspberry-pi-pico.svg', w: 79, h: 200 },
|
||||
'pi-pico-w': { svg: 'raspberry-pi-pico-w.svg', w: 105, h: 264 },
|
||||
'raspberry-pi-3': { svg: 'raspberry-pi-3.svg', w: 200, h: 135 },
|
||||
// ESP32 family — every variant currently maps to the DevKit V1 art (no
|
||||
// dedicated SVGs are extracted for the variants yet).
|
||||
esp32: { svg: 'esp32-devkit-v1.svg', w: 107, h: 204 },
|
||||
'esp32-devkit-c-v4': { svg: 'esp32-devkit-v1.svg', w: 107, h: 204 },
|
||||
'esp32-cam': { svg: 'esp32-devkit-v1.svg', w: 107, h: 204 },
|
||||
'wemos-lolin32-lite': { svg: 'esp32-devkit-v1.svg', w: 107, h: 204 },
|
||||
'esp32-s3': { svg: 'esp32-devkit-v1.svg', w: 107, h: 204 },
|
||||
'xiao-esp32-s3': { svg: 'esp32-devkit-v1.svg', w: 107, h: 204 },
|
||||
'arduino-nano-esp32': { svg: 'esp32-devkit-v1.svg', w: 107, h: 204 },
|
||||
'esp32-c3': { svg: 'esp32-devkit-v1.svg', w: 107, h: 204 },
|
||||
'xiao-esp32-c3': { svg: 'esp32-devkit-v1.svg', w: 107, h: 204 },
|
||||
'aitewinrobot-esp32c3-supermini': { svg: 'esp32-devkit-v1.svg', w: 107, h: 204 },
|
||||
// ATtiny85 — DIP-8, drawn inline (no wokwi element ships its SVG).
|
||||
attiny85: { svg: '', inline: Attiny85InlinePreview, w: 160, h: 100 },
|
||||
};
|
||||
|
||||
// LED color → SVG filename
|
||||
|
|
@ -123,9 +174,12 @@ function isBoardType(type: string): boolean {
|
|||
type.includes('arduino-uno') ||
|
||||
type.includes('arduino-nano') ||
|
||||
type.includes('arduino-mega') ||
|
||||
type.includes('esp32-devkit') ||
|
||||
type.includes('esp32') || // covers esp32, esp32-s3, esp32-c3, esp32-cam, …
|
||||
type.includes('raspberry-pi-pico') ||
|
||||
type.includes('nano-rp2040')
|
||||
type.includes('pi-pico-w') ||
|
||||
type.includes('nano-rp2040') ||
|
||||
type.includes('attiny85') ||
|
||||
type.includes('raspberry-pi-3')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -355,19 +355,19 @@ 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>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<EditorToolbar
|
||||
consoleOpen={consoleOpen}
|
||||
setConsoleOpen={setConsoleOpen}
|
||||
compileLogs={compileLogs}
|
||||
setCompileLogs={setCompileLogs}
|
||||
/* File tabs share the toolbar row — keeps every action
|
||||
icon pinned regardless of editor-pane width. */
|
||||
centerSlot={!isRaspberryPi3 ? <FileTabs /> : null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File tabs — hidden when Pi workspace is active */}
|
||||
{!isRaspberryPi3 && <FileTabs />}
|
||||
|
||||
{/* Editor area: Pi workspace or Monaco editor */}
|
||||
<div className="editor-wrapper" style={{ flex: 1, overflow: 'hidden', minHeight: 0 }}>
|
||||
{isRaspberryPi3 && activeBoardId ? (
|
||||
|
|
|
|||
Loading…
Reference in New Issue