feat: enhance EditorToolbar with always-visible Libraries button and missing library hint; remove overflow collapse logic
This commit is contained in:
parent
09118fcdef
commit
9d45484fce
|
|
@ -88,7 +88,34 @@
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Libraries */
|
/* Libraries button (always visible, with label) */
|
||||||
|
.tb-btn-libraries {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 4px 10px 4px 8px;
|
||||||
|
border: 1px solid rgba(0, 184, 212, 0.3);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: rgba(0, 184, 212, 0.08);
|
||||||
|
color: #00e5ff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.tb-btn-libraries:hover {
|
||||||
|
background: rgba(0, 184, 212, 0.18);
|
||||||
|
border-color: rgba(0, 184, 212, 0.5);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.tb-libraries-label {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy lib icon-only button (for overflow items) */
|
||||||
.tb-btn-lib {
|
.tb-btn-lib {
|
||||||
color: #00b8d4;
|
color: #00b8d4;
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +124,52 @@
|
||||||
color: #00e5ff;
|
color: #00e5ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Missing library hint banner */
|
||||||
|
.tb-lib-hint {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 5px 12px;
|
||||||
|
background: rgba(255, 152, 0, 0.1);
|
||||||
|
border-bottom: 1px solid rgba(255, 152, 0, 0.25);
|
||||||
|
color: #ffb74d;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.tb-lib-hint svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #ffa726;
|
||||||
|
}
|
||||||
|
.tb-lib-hint-btn {
|
||||||
|
background: rgba(0, 184, 212, 0.15);
|
||||||
|
border: 1px solid rgba(0, 184, 212, 0.4);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #00e5ff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: inherit;
|
||||||
|
padding: 2px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.12s;
|
||||||
|
}
|
||||||
|
.tb-lib-hint-btn:hover {
|
||||||
|
background: rgba(0, 184, 212, 0.3);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.tb-lib-hint-close {
|
||||||
|
margin-left: auto;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #888;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 4px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.tb-lib-hint-close:hover {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
/* Output Console toggle */
|
/* Output Console toggle */
|
||||||
.tb-btn-output {
|
.tb-btn-output {
|
||||||
color: #9d9d9d;
|
color: #9d9d9d;
|
||||||
|
|
|
||||||
|
|
@ -67,20 +67,12 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
||||||
const [installModalOpen, setInstallModalOpen] = useState(false);
|
const [installModalOpen, setInstallModalOpen] = useState(false);
|
||||||
const importInputRef = useRef<HTMLInputElement>(null);
|
const importInputRef = useRef<HTMLInputElement>(null);
|
||||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
const toolbarRef = useRef<HTMLDivElement>(null);
|
||||||
const [overflowCollapsed, setOverflowCollapsed] = useState(false);
|
|
||||||
const [overflowOpen, setOverflowOpen] = useState(false);
|
const [overflowOpen, setOverflowOpen] = useState(false);
|
||||||
const overflowMenuRef = useRef<HTMLDivElement>(null);
|
const overflowMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [missingLibHint, setMissingLibHint] = useState(false);
|
||||||
|
|
||||||
// Collapse secondary buttons when toolbar is too narrow
|
// (ResizeObserver removed — Library Manager is always visible now,
|
||||||
useEffect(() => {
|
// only import/export live in the overflow menu)
|
||||||
const el = toolbarRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
const ro = new ResizeObserver(([entry]) => {
|
|
||||||
setOverflowCollapsed(entry.contentRect.width < 500);
|
|
||||||
});
|
|
||||||
ro.observe(el);
|
|
||||||
return () => ro.disconnect();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Close overflow dropdown on outside click
|
// Close overflow dropdown on outside click
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -143,8 +135,14 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
||||||
compileBoardProgram(activeBoardId, program);
|
compileBoardProgram(activeBoardId, program);
|
||||||
}
|
}
|
||||||
setMessage({ type: 'success', text: 'Compiled successfully' });
|
setMessage({ type: 'success', text: 'Compiled successfully' });
|
||||||
|
setMissingLibHint(false);
|
||||||
} else {
|
} else {
|
||||||
setMessage({ type: 'error', text: result.error || result.stderr || 'Compile failed' });
|
const errText = result.error || result.stderr || 'Compile failed';
|
||||||
|
setMessage({ type: 'error', text: errText });
|
||||||
|
// Detect missing library errors — common patterns:
|
||||||
|
// "No such file or directory" for #include, "fatal error: XXX.h"
|
||||||
|
const looksLikeMissingLib = /No such file or directory|fatal error:.*\.h|library not found/i.test(errText);
|
||||||
|
setMissingLibHint(looksLikeMissingLib);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errMsg = err instanceof Error ? err.message : 'Compile failed';
|
const errMsg = err instanceof Error ? err.message : 'Compile failed';
|
||||||
|
|
@ -428,104 +426,61 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
||||||
onChange={handleImportFile}
|
onChange={handleImportFile}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Secondary buttons — hidden when toolbar is narrow */}
|
{/* Library Manager — always visible with label */}
|
||||||
{!overflowCollapsed && (
|
<button
|
||||||
<>
|
onClick={() => setLibManagerOpen(true)}
|
||||||
{/* Import Wokwi zip */}
|
className="tb-btn-libraries"
|
||||||
<button
|
title="Search and install Arduino libraries"
|
||||||
onClick={() => importInputRef.current?.click()}
|
>
|
||||||
className="tb-btn tb-btn-lib"
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
title="Import Wokwi zip"
|
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z" />
|
||||||
>
|
<path d="m3.3 7 8.7 5 8.7-5" />
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<path d="M12 22V12" />
|
||||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
</svg>
|
||||||
<polyline points="7 10 12 15 17 10" />
|
<span className="tb-libraries-label">Libraries</span>
|
||||||
<line x1="12" y1="15" x2="12" y2="3" />
|
</button>
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Export Wokwi zip */}
|
{/* Import / Export — overflow menu */}
|
||||||
<button
|
<div className="tb-overflow-wrap" ref={overflowMenuRef}>
|
||||||
onClick={handleExport}
|
<button
|
||||||
className="tb-btn tb-btn-lib"
|
onClick={() => setOverflowOpen((v) => !v)}
|
||||||
title="Export as Wokwi zip"
|
className={`tb-btn tb-btn-overflow${overflowOpen ? ' tb-btn-overflow-active' : ''}`}
|
||||||
>
|
title="Import / Export"
|
||||||
<svg width="22" height="22" 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" />
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||||
<polyline points="17 8 12 3 7 8" />
|
<circle cx="5" cy="12" r="2" />
|
||||||
<line x1="12" y1="3" x2="12" y2="15" />
|
<circle cx="12" cy="12" r="2" />
|
||||||
</svg>
|
<circle cx="19" cy="12" r="2" />
|
||||||
</button>
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* Libraries */}
|
{overflowOpen && (
|
||||||
<button
|
<div className="tb-overflow-menu">
|
||||||
onClick={() => setLibManagerOpen(true)}
|
<button
|
||||||
className="tb-btn tb-btn-lib"
|
className="tb-overflow-item"
|
||||||
title="Library Manager"
|
onClick={() => { importInputRef.current?.click(); setOverflowOpen(false); }}
|
||||||
>
|
>
|
||||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z" />
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||||
<path d="m3.3 7 8.7 5 8.7-5" />
|
<polyline points="7 10 12 15 17 10" />
|
||||||
<path d="M12 22V12" />
|
<line x1="12" y1="15" x2="12" y2="3" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
Import zip
|
||||||
</>
|
</button>
|
||||||
)}
|
<button
|
||||||
|
className="tb-overflow-item"
|
||||||
{/* Overflow (…) button — shown when toolbar is narrow */}
|
onClick={() => { handleExport(); setOverflowOpen(false); }}
|
||||||
{overflowCollapsed && (
|
>
|
||||||
<div className="tb-overflow-wrap" ref={overflowMenuRef}>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<button
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
||||||
onClick={() => setOverflowOpen((v) => !v)}
|
<polyline points="17 8 12 3 7 8" />
|
||||||
className={`tb-btn tb-btn-overflow${overflowOpen ? ' tb-btn-overflow-active' : ''}`}
|
<line x1="12" y1="3" x2="12" y2="15" />
|
||||||
title="More actions"
|
</svg>
|
||||||
>
|
Export zip
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
</button>
|
||||||
<circle cx="5" cy="12" r="2" />
|
</div>
|
||||||
<circle cx="12" cy="12" r="2" />
|
)}
|
||||||
<circle cx="19" cy="12" r="2" />
|
</div>
|
||||||
</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 Wokwi 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 as Wokwi zip
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="tb-overflow-item"
|
|
||||||
onClick={() => { setLibManagerOpen(true); 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 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z" />
|
|
||||||
<path d="m3.3 7 8.7 5 8.7-5" />
|
|
||||||
<path d="M12 22V12" />
|
|
||||||
</svg>
|
|
||||||
Library Manager
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="tb-divider" />
|
<div className="tb-divider" />
|
||||||
|
|
||||||
|
|
@ -549,6 +504,24 @@ export const EditorToolbar = ({ consoleOpen, setConsoleOpen, compileLogs: _compi
|
||||||
<div className="toolbar-error-detail">{message.text}</div>
|
<div className="toolbar-error-detail">{message.text}</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Missing library hint */}
|
||||||
|
{missingLibHint && (
|
||||||
|
<div className="tb-lib-hint">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<line x1="12" y1="8" x2="12" y2="12" />
|
||||||
|
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||||
|
</svg>
|
||||||
|
<span>Missing library? Install it from the</span>
|
||||||
|
<button className="tb-lib-hint-btn" onClick={() => { setLibManagerOpen(true); setMissingLibHint(false); }}>
|
||||||
|
Library Manager
|
||||||
|
</button>
|
||||||
|
<button className="tb-lib-hint-close" onClick={() => setMissingLibHint(false)} title="Dismiss">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<LibraryManagerModal isOpen={libManagerOpen} onClose={() => setLibManagerOpen(false)} />
|
<LibraryManagerModal isOpen={libManagerOpen} onClose={() => setLibManagerOpen(false)} />
|
||||||
<InstallLibrariesModal
|
<InstallLibrariesModal
|
||||||
isOpen={installModalOpen}
|
isOpen={installModalOpen}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue