diff --git a/frontend/src/components/editor/EditorMenuBar.css b/frontend/src/components/editor/EditorMenuBar.css index 502bc19a..ae014e3e 100644 --- a/frontend/src/components/editor/EditorMenuBar.css +++ b/frontend/src/components/editor/EditorMenuBar.css @@ -83,3 +83,18 @@ background: #3c3c3c; margin: 4px 6px; } + +/* Same PRO pill the old toolbar overflow used (tb-overflow-pro) — users + should know an item is premium BEFORE clicking, not via a surprise + upgrade prompt. */ +.emb-pro { + font-size: 9.5px; + font-weight: 700; + letter-spacing: 0.5px; + padding: 2px 6px; + border-radius: 3px; + background: linear-gradient(135deg, #ffd54f 0%, #ffb300 100%); + color: #1a1a1a; + flex-shrink: 0; + margin-left: 8px; +} diff --git a/frontend/src/components/editor/EditorMenuBar.tsx b/frontend/src/components/editor/EditorMenuBar.tsx index 72e13adb..5f9c9d3e 100644 --- a/frontend/src/components/editor/EditorMenuBar.tsx +++ b/frontend/src/components/editor/EditorMenuBar.tsx @@ -31,7 +31,7 @@ import { import './EditorMenuBar.css'; type Item = - | { kind: 'command'; id: EditorCommandId; label: string; shortcut?: string } + | { kind: 'command'; id: EditorCommandId; label: string; shortcut?: string; pro?: boolean } | { kind: 'link'; href: string; label: string } | { kind: 'separator' }; @@ -89,12 +89,20 @@ export const EditorMenuBar: React.FC = () => { { kind: 'separator' }, { kind: 'command', id: 'project.import', label: t('editor.toolbar.importLabel', 'Import project') }, { kind: 'command', id: 'project.export', label: t('editor.toolbar.exportLabel', 'Export project (.zip)') }, - { kind: 'command', id: 'project.exportBom', label: t('editor.toolbar.exportBomLabel', 'Bill of Materials (CSV)') }, + { kind: 'command', id: 'project.exportBom', label: t('editor.toolbar.exportBomLabel', 'Bill of Materials (CSV)'), pro: true }, { kind: 'command', id: 'project.exportScreenshot', label: t('editor.toolbar.exportScreenshotLabel', 'Schematic image (PNG)'), + pro: true, }, + { kind: 'separator' }, + // The toolbar's "..." menu folded in here — same actions, same PRO + // pills, one button fewer in the strip. + { kind: 'command', id: 'project.share', label: t('editor.toolbar.shareLabel', 'Share / Embed') }, + { kind: 'command', id: 'project.githubSync', label: t('editor.toolbar.githubSyncLabel', 'Sync to GitHub'), pro: true }, + { kind: 'command', id: 'firmware.upload', label: t('editor.toolbar.uploadFirmwareLabel', 'Upload firmware') }, + { kind: 'command', id: 'sim.record', label: t('editor.toolbar.recordLabel', 'Record simulation'), pro: true }, ]; const helpItems: Item[] = [ @@ -142,7 +150,10 @@ export const EditorMenuBar: React.FC = () => { runEditorCommand(item.id); }} > - {item.label} + + {item.label} + {item.pro && PRO} + {item.shortcut && {item.shortcut}} ); diff --git a/frontend/src/components/editor/EditorToolbar.css b/frontend/src/components/editor/EditorToolbar.css index 50e73035..3a071aa9 100644 --- a/frontend/src/components/editor/EditorToolbar.css +++ b/frontend/src/components/editor/EditorToolbar.css @@ -350,67 +350,11 @@ } /* ── Overflow (…) menu ──────────────────────────── */ -.tb-overflow-wrap { - position: relative; -} -.tb-btn-overflow { - color: #9d9d9d; -} -.tb-btn-overflow:hover { - background: rgba(255, 255, 255, 0.07); - color: #ccc; -} -.tb-btn-overflow-active { - background: rgba(255, 255, 255, 0.1); - color: #fff; -} -.tb-overflow-menu { - position: absolute; - top: calc(100% + 6px); - right: 0; - z-index: 200; - background: #2a2a2c; - border: 1px solid #3a3a3c; - border-radius: 10px; - padding: 6px; - min-width: 180px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6); - display: flex; - flex-direction: column; - gap: 2px; -} -.tb-overflow-item { - display: flex; - align-items: center; - gap: 10px; - width: 100%; - padding: 8px 10px; - background: transparent; - border: none; - border-radius: 7px; - color: #ccc; - font-size: 13px; - font-family: inherit; - cursor: pointer; - text-align: left; - white-space: nowrap; - transition: - background 0.12s, - color 0.12s; -} -.tb-overflow-item:hover { - background: rgba(255, 255, 255, 0.08); - color: #fff; -} -.tb-overflow-item svg { - flex-shrink: 0; - color: var(--color-fg-default); -} /* ── Run split-button (multi-board: primary Run = all boards + menu) ──── */ .tb-run-split { @@ -468,24 +412,10 @@ cursor: default; } -.tb-overflow-label { - flex: 1; - text-align: left; -} /* Pro pill — signals premium items in the overflow menu BEFORE the user clicks. Used in place of a hidden gate so free users aren't surprised by an upgrade prompt on what looked like a free button. */ -.tb-overflow-pro { - font-size: 9.5px; - font-weight: 700; - letter-spacing: 0.5px; - padding: 2px 6px; - border-radius: 3px; - background: linear-gradient(135deg, #ffd54f 0%, #ffb300 100%); - color: #1a1a1a; - flex-shrink: 0; -} /* ── Error detail bar (for long compiler errors) ── */ .toolbar-error-detail { diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx index 71589ec6..843e3b7f 100644 --- a/frontend/src/components/editor/EditorToolbar.tsx +++ b/frontend/src/components/editor/EditorToolbar.tsx @@ -255,8 +255,6 @@ export const EditorToolbar = ({ const firmwareInputRef = useRef(null); const toolbarRef = useRef(null); const [missingLibHint, setMissingLibHint] = useState(false); - const [moreMenuOpen, setMoreMenuOpen] = useState(false); - const moreMenuRef = useRef(null); // Split-button menu for the multi-board Run control ("Run all" / "Run active only"). const [runMenuOpen, setRunMenuOpen] = useState(false); const runMenuRef = useRef(null); @@ -291,23 +289,6 @@ export const EditorToolbar = ({ return () => window.removeEventListener('velxio-circuit-fault', onFault); }, [setCompileLogs]); - useEffect(() => { - if (!moreMenuOpen) return; - const onClickOutside = (e: MouseEvent) => { - if (moreMenuRef.current && !moreMenuRef.current.contains(e.target as Node)) { - setMoreMenuOpen(false); - } - }; - const onEsc = (e: KeyboardEvent) => { - if (e.key === 'Escape') setMoreMenuOpen(false); - }; - document.addEventListener('mousedown', onClickOutside); - document.addEventListener('keydown', onEsc); - return () => { - document.removeEventListener('mousedown', onClickOutside); - document.removeEventListener('keydown', onEsc); - }; - }, [moreMenuOpen]); // Close the Run split-menu on outside click / Escape (mirrors the more-menu). useEffect(() => { @@ -1393,24 +1374,40 @@ export const EditorToolbar = ({ // File-menu commands owned by this toolbar (the handlers close over its // state). Registered through a latest-ref so the one-time registration // always invokes the current render's closure, never a stale one. - const menuCommandsRef = useRef({ + const makeMenuCommands = () => ({ import: () => importInputRef.current?.click(), export: () => void handleExport(), bom: () => void handleExportBom(), screenshot: () => void handleExportScreenshot(), + firmware: () => firmwareInputRef.current?.click(), + // Pro actions fire the same window events the old "..." menu items + // fired; without the overlay they are silent no-ops, which is fine — + // OSS builds cannot have linked repos or shared projects anyway. + share: () => + window.dispatchEvent(new CustomEvent('velxio-pro-share-prompt', { + detail: { projectId: currentProject?.id ?? null }, + })), + githubSync: () => + window.dispatchEvent(new CustomEvent('velxio-pro-github-sync-prompt', { + detail: { projectId: currentProject?.id ?? null }, + })), + record: () => + window.dispatchEvent(new CustomEvent('velxio-pro-replay-record-toggle', { + detail: { projectId: currentProject?.id ?? null }, + })), }); - menuCommandsRef.current = { - import: () => importInputRef.current?.click(), - export: () => void handleExport(), - bom: () => void handleExportBom(), - screenshot: () => void handleExportScreenshot(), - }; + const menuCommandsRef = useRef(makeMenuCommands()); + menuCommandsRef.current = makeMenuCommands(); useEffect(() => { const offs = [ registerEditorCommand('project.import', () => menuCommandsRef.current.import()), registerEditorCommand('project.export', () => menuCommandsRef.current.export()), registerEditorCommand('project.exportBom', () => menuCommandsRef.current.bom()), registerEditorCommand('project.exportScreenshot', () => menuCommandsRef.current.screenshot()), + registerEditorCommand('firmware.upload', () => menuCommandsRef.current.firmware()), + registerEditorCommand('project.share', () => menuCommandsRef.current.share()), + registerEditorCommand('project.githubSync', () => menuCommandsRef.current.githubSync()), + registerEditorCommand('sim.record', () => menuCommandsRef.current.record()), ]; return () => offs.forEach((off) => off()); }, []); @@ -1757,104 +1754,8 @@ export const EditorToolbar = ({ a small "PRO" pill in the menu so users know they're premium BEFORE clicking, instead of being surprised by an upgrade prompt. */} -
- - {moreMenuOpen && ( -
- - {/* Sync to GitHub — Pro feature. Fires a window event the - pro overlay listens for; if no overlay is loaded (OSS - build) the click is a silent no-op which is fine — - OSS users can't have linked repos anyway. */} - - {/* Share / Embed — free for all users with a public project. - Watermark removal on the embed is the Pro perk; the - Share modal itself is open to everyone so they can - copy the link / iframe snippet. */} - - {/* Record simulation — Pro feature. Dispatches a toggle the - pro overlay handles (plan check, board-type check, - start/stop the recorder). OSS build → no listener → - silent no-op. */} - -
- )} -
- + {/* The "..." menu is gone: every item it held now lives in the + File menu (with PRO pills where they apply). */}
{/* Output Console toggle */} diff --git a/frontend/src/lib/editorCommands.ts b/frontend/src/lib/editorCommands.ts index f2f05b0f..6e3c493e 100644 --- a/frontend/src/lib/editorCommands.ts +++ b/frontend/src/lib/editorCommands.ts @@ -25,6 +25,10 @@ export type EditorCommandId = | 'project.exportBom' | 'project.exportScreenshot' | 'file.new' + | 'project.share' + | 'project.githubSync' + | 'firmware.upload' + | 'sim.record' | 'view.reset' | 'view.zoomIn' | 'view.zoomOut';