velxio/frontend/src/components/editor/EditorMenuBar.tsx

382 lines
16 KiB
TypeScript
Raw Normal View History

feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
/**
* EditorMenuBar — File / Edit menus for the editor header.
*
* The editor grew a single toolbar row where every action, frequent or
* rare, competed for the same pixels; on small screens the buttons
* measurably overlapped. The classic fix is the classic desktop split:
* things you do every minute stay as buttons (Run, Stop, board, Add),
* things you do a few times per session move into menus. This is those
* menus.
*
* Actions are invoked through the editorCommands registry — their real
* owners (EditorPage, FileExplorer, EditorToolbar, SimulatorCanvas)
* register handlers on mount, so nothing here duplicates logic and an
* item whose owner is not mounted renders disabled. Undo/redo read the
* canvas history from the store directly, mirroring the canvas buttons.
*
* Menubar behaviour follows the desktop convention: click opens, click
* again closes, hovering a sibling while open switches menus, Escape and
* outside clicks close.
*/
import React, { useEffect, useRef, useState, useSyncExternalStore } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate } from 'react-router-dom';
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
import { useSimulatorStore } from '../../store/useSimulatorStore';
import { useOscilloscopeStore } from '../../store/useOscilloscopeStore';
import { LOCALES, LOCALE_META, type Locale } from '../../i18n/config';
import { getLocaleFromPath, switchLocale } from '../../i18n/path';
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
import {
hasEditorCommand,
runEditorCommand,
subscribeEditorCommands,
getEditorCommandsVersion,
type EditorCommandId,
} from '../../lib/editorCommands';
import './EditorMenuBar.css';
type Item =
| {
kind: 'command';
id: EditorCommandId;
label: string;
shortcut?: string;
pro?: boolean;
/** Hide the row entirely when no handler is registered, instead of
* the default "render disabled". For account-scoped items the
* absence of a handler is not "temporarily unavailable" but "does
* not apply here": OSS has no accounts at all, and in pro exactly
* one of Sign in / My projects is meaningful at a time. A greyed-out
* "My projects" would read as a broken feature in both. */
optional?: boolean;
}
| { kind: 'link'; href: string; label: string }
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
| { kind: 'separator' };
// Same links the desktop app's Help menu opens (pro/desktop menu.rs) — the
// web editor mirrors that structure so both feel like one product. They
// replace the marketing nav this header no longer shows, opening in a new
// tab so the editor (and any unsaved work) stays put.
const GITHUB_URL = 'https://github.com/davidmonterocrespo24/velxio';
const DISCORD_URL = 'https://discord.gg/3mARjJrh4E';
refactor(routes): OSS is the editor; marketing moves to the overlay The open-source build now ships exactly its product surface: /editor, the examples gallery (/examples, /examples/:id) and the per-example editor (/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted simulator landings and the v2/v2.5/v3 showcases move to the private overlay, registered through the same registerProRoutes seam that already carries login/admin/classroom. Root behaves per build: an overlay that registers an index route claims '/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor. The redirect waits for the overlay import to settle — same contract as markProExamplesSettled — so a velxio.dev visitor is never bounced into the editor because the landing was 300ms away from registering. Prerender and sitemap follow the split: entry-server pulls the marketing page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic import (the proven main.tsx pattern, with an OSS stub for tsc), and generate-sitemap lists only served routes in OSS builds (2 URLs) while pro builds stay byte-identical — verified against a pre-migration baseline: sitemap (37 URLs) and prerendered /, /about, /docs, /arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed asset names. OSS prerender drops exactly the 32 marketing pages (348→316). The OSS header slims down to match: Editor, Examples, GitHub, Discord — the marketing links only render in pro builds, where their routes exist. The editor Help menu links those pages absolutely (velxio.dev) in OSS, exactly like the desktop app's Help menu.
2026-07-30 22:50:14 +07:00
// In the OSS build the marketing pages live on velxio.dev, not in this app
// (the overlay registers them only in pro builds) — link absolute, exactly
// like the desktop app's Help menu does.
const SITE = import.meta.env.VITE_PRO_BUILD ? '' : 'https://velxio.dev';
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
export const EditorMenuBar: React.FC = () => {
const { t } = useTranslation();
const [open, setOpen] = useState<'file' | 'edit' | 'view' | 'account' | 'help' | null>(null);
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
const rootRef = useRef<HTMLDivElement>(null);
// Re-render when owners (un)register their commands.
useSyncExternalStore(subscribeEditorCommands, getEditorCommandsVersion);
const location = useLocation();
const navigate = useNavigate();
const currentLocale = getLocaleFromPath(location.pathname);
const serialOpen = useSimulatorStore((s) => s.serialMonitorOpen);
const toggleSerialMonitor = useSimulatorStore((s) => s.toggleSerialMonitor);
const scopeOpen = useOscilloscopeStore((s) => s.open);
const toggleOscilloscope = useOscilloscopeStore((s) => s.toggleOscilloscope);
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
const undo = useSimulatorStore((s) => s.undo);
const redo = useSimulatorStore((s) => s.redo);
const history = useSimulatorStore((s) => s.history);
const historyIndex = useSimulatorStore((s) => s.historyIndex);
useEffect(() => {
if (!open) return;
const onDown = (e: MouseEvent): void => {
if (!rootRef.current?.contains(e.target as Node)) setOpen(null);
};
const onKey = (e: KeyboardEvent): void => {
if (e.key === 'Escape') setOpen(null);
};
document.addEventListener('mousedown', onDown);
document.addEventListener('keydown', onKey);
return () => {
document.removeEventListener('mousedown', onDown);
document.removeEventListener('keydown', onKey);
};
}, [open]);
const fileItems: Item[] = [
{ kind: 'command', id: 'project.new', label: t('editor.menu.newProject', 'New workspace') },
{ kind: 'command', id: 'file.new', label: t('editor.menu.newFile', 'New file') },
{ kind: 'separator' },
// Third item, and deliberately at the head of the project group rather
// than tacked onto the "new …" pair above: it opens the user's saved
// work, which is what Open/Save below are about.
{
kind: 'command',
id: 'account.myProjects',
label: t('header.auth.myProjects', 'My projects'),
optional: true,
},
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
{ kind: 'command', id: 'project.open', label: t('editor.menu.open', 'Open project…') },
{
kind: 'command',
id: 'project.save',
label: t('editor.menu.save', 'Save project'),
shortcut: 'Ctrl+S',
},
{ 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)'), pro: true },
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
{
kind: 'command',
id: 'project.exportScreenshot',
label: t('editor.toolbar.exportScreenshotLabel', 'Schematic image (PNG)'),
pro: true,
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
},
{ 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 },
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
];
// Sign in / My projects for the Account menu. The bottom-left account
// dropdown gets these from its own (pro) markup; this menubar only ever
// hosted the shared `user-menu` slot, which is why the editor's Account
// menu had no way in or out of a session.
const accountItems: Item[] = [
{
kind: 'command',
id: 'account.myProjects',
label: t('header.auth.myProjects', 'My projects'),
optional: true,
},
{
kind: 'command',
id: 'account.login',
label: t('header.auth.signIn', 'Sign in'),
optional: true,
},
];
const helpItems: Item[] = [
feat(news): announce with a toast, open the modal only on request Entering the editor could stack two overlays: the starter-template dialog opened on mount, then four seconds later the "What's new" modal landed on top of it. Two dimmed backdrops over each other, and the smaller, *less* important surface in front — the z-indexes said it outright, news 10000 against the starter's 9000. The delay made it worse rather than gentler. Arriving after the user has started reading the templates is the most disruptive moment available; appearing instantly would at least have let them clear it before engaging. The rule this restores: a modal is for what BLOCKS, and an announcement never blocks. News now slides into the corner as a toast that dims nothing, steals no focus (role="status", polite), and retires by itself. The full post is one click behind "Read more" — a modal the user asked for is a categorically different thing from one thrown at them — and stays reachable from Help ▸ What's new once the toast is gone. Worth noting the audience argument too: the starter dialog only fires on a PRISTINE visit, i.e. for someone brand new, and "what's new" is meaningless without a baseline to compare against. Returning users, the ones it is actually written for, arrive with a project loaded and never saw the collision. Renamed NewsModal → NewsAnnouncer: it is not a modal by default any more, and leaving the old name would have mislead the next reader. Strings are new keys under `news.*`, filled in for all nine locales — the previous modal had "What's new" and "Got it" hardcoded in English. Toast sits at z-index 8000, deliberately BELOW the starter dialog, so if the two ever coincide again the blocking task keeps the foreground.
2026-08-11 00:52:55 +07:00
// Only present once a post has been delivered — the announcement is a
// toast now, and this is how it stays reachable after it retires.
{
kind: 'command',
id: 'help.whatsNew',
label: t('news.kicker', "What's new"),
optional: true,
},
refactor(routes): OSS is the editor; marketing moves to the overlay The open-source build now ships exactly its product surface: /editor, the examples gallery (/examples, /examples/:id) and the per-example editor (/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted simulator landings and the v2/v2.5/v3 showcases move to the private overlay, registered through the same registerProRoutes seam that already carries login/admin/classroom. Root behaves per build: an overlay that registers an index route claims '/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor. The redirect waits for the overlay import to settle — same contract as markProExamplesSettled — so a velxio.dev visitor is never bounced into the editor because the landing was 300ms away from registering. Prerender and sitemap follow the split: entry-server pulls the marketing page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic import (the proven main.tsx pattern, with an OSS stub for tsc), and generate-sitemap lists only served routes in OSS builds (2 URLs) while pro builds stay byte-identical — verified against a pre-migration baseline: sitemap (37 URLs) and prerendered /, /about, /docs, /arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed asset names. OSS prerender drops exactly the 32 marketing pages (348→316). The OSS header slims down to match: Editor, Examples, GitHub, Discord — the marketing links only render in pro builds, where their routes exist. The editor Help menu links those pages absolutely (velxio.dev) in OSS, exactly like the desktop app's Help menu.
2026-07-30 22:50:14 +07:00
{ kind: 'link', href: `${SITE}/docs`, label: t('header.nav.documentation', 'Documentation') },
{ kind: 'link', href: '/examples', label: t('header.nav.examples', 'Examples') },
refactor(routes): OSS is the editor; marketing moves to the overlay The open-source build now ships exactly its product surface: /editor, the examples gallery (/examples, /examples/:id) and the per-example editor (/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted simulator landings and the v2/v2.5/v3 showcases move to the private overlay, registered through the same registerProRoutes seam that already carries login/admin/classroom. Root behaves per build: an overlay that registers an index route claims '/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor. The redirect waits for the overlay import to settle — same contract as markProExamplesSettled — so a velxio.dev visitor is never bounced into the editor because the landing was 300ms away from registering. Prerender and sitemap follow the split: entry-server pulls the marketing page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic import (the proven main.tsx pattern, with an OSS stub for tsc), and generate-sitemap lists only served routes in OSS builds (2 URLs) while pro builds stay byte-identical — verified against a pre-migration baseline: sitemap (37 URLs) and prerendered /, /about, /docs, /arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed asset names. OSS prerender drops exactly the 32 marketing pages (348→316). The OSS header slims down to match: Editor, Examples, GitHub, Discord — the marketing links only render in pro builds, where their routes exist. The editor Help menu links those pages absolutely (velxio.dev) in OSS, exactly like the desktop app's Help menu.
2026-07-30 22:50:14 +07:00
{ kind: 'link', href: `${SITE}/pricing`, label: t('header.nav.pricing', 'Pricing') },
{ kind: 'separator' },
refactor(routes): OSS is the editor; marketing moves to the overlay The open-source build now ships exactly its product surface: /editor, the examples gallery (/examples, /examples/:id) and the per-example editor (/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted simulator landings and the v2/v2.5/v3 showcases move to the private overlay, registered through the same registerProRoutes seam that already carries login/admin/classroom. Root behaves per build: an overlay that registers an index route claims '/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor. The redirect waits for the overlay import to settle — same contract as markProExamplesSettled — so a velxio.dev visitor is never bounced into the editor because the landing was 300ms away from registering. Prerender and sitemap follow the split: entry-server pulls the marketing page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic import (the proven main.tsx pattern, with an OSS stub for tsc), and generate-sitemap lists only served routes in OSS builds (2 URLs) while pro builds stay byte-identical — verified against a pre-migration baseline: sitemap (37 URLs) and prerendered /, /about, /docs, /arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed asset names. OSS prerender drops exactly the 32 marketing pages (348→316). The OSS header slims down to match: Editor, Examples, GitHub, Discord — the marketing links only render in pro builds, where their routes exist. The editor Help menu links those pages absolutely (velxio.dev) in OSS, exactly like the desktop app's Help menu.
2026-07-30 22:50:14 +07:00
{ kind: 'link', href: SITE || '/', label: t('editor.menu.home', 'Velxio Home') },
{ kind: 'link', href: `${SITE}/blog/`, label: t('header.nav.blog', 'Blog') },
{ kind: 'link', href: `${SITE}/about`, label: t('editor.menu.about', 'About Velxio') },
{ kind: 'separator' },
{ kind: 'link', href: DISCORD_URL, label: t('editor.menu.discord', 'Discord Community') },
{ kind: 'link', href: GITHUB_URL, label: t('editor.menu.github', 'GitHub Repository') },
];
// Edit is undo/redo only (they render specially, with live history state);
// everything view-shaped lives in the View menu, like the desktop app.
const editItems: Item[] = [];
const viewItems: Item[] = [
{ kind: 'command', id: 'sim.compile', label: t('editor.menu.compile', 'Compile'), shortcut: 'Ctrl+B' },
{ kind: 'command', id: 'sim.run', label: t('editor.menu.run', 'Run') },
{ kind: 'command', id: 'sim.stop', label: t('editor.toolbar.stop', 'Stop') },
{ kind: 'command', id: 'sim.resetBoard', label: t('editor.toolbar.reset', 'Reset') },
{ kind: 'separator' },
{ kind: 'command', id: 'view.toggleExplorer', label: t('editor.menu.toggleExplorer', 'File Explorer') },
{ kind: 'command', id: 'view.toggleConsole', label: t('editor.menu.toggleConsole', 'Output Console') },
{ kind: 'separator' },
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
{ kind: 'command', id: 'view.reset', label: t('editor.menu.centerView', 'Center canvas view') },
{ kind: 'command', id: 'view.zoomIn', label: t('editor.canvas.zoomIn', 'Zoom in') },
{ kind: 'command', id: 'view.zoomOut', label: t('editor.canvas.zoomOut', 'Zoom out') },
];
const renderLink = (item: Extract<Item, { kind: 'link' }>): React.ReactNode => (
<a
key={item.href}
role="menuitem"
className="emb-item"
href={item.href}
target="_blank"
rel="noopener noreferrer"
onClick={() => setOpen(null)}
>
<span>{item.label}</span>
</a>
);
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
const renderCommand = (item: Extract<Item, { kind: 'command' }>): React.ReactNode => (
<button
key={item.id}
role="menuitem"
className="emb-item"
disabled={!hasEditorCommand(item.id)}
onClick={() => {
setOpen(null);
runEditorCommand(item.id);
}}
>
<span>
{item.label}
{item.pro && <span className="emb-pro">PRO</span>}
</span>
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
{item.shortcut && <span className="emb-shortcut">{item.shortcut}</span>}
</button>
);
const menu = (which: 'file' | 'edit' | 'view' | 'account' | 'help', label: string, items: Item[]): React.ReactNode => (
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
<div className="emb-root" key={which}>
<button
className={`emb-trigger${open === which ? ' emb-trigger-open' : ''}`}
aria-haspopup="menu"
aria-expanded={open === which}
onClick={() => setOpen((cur) => (cur === which ? null : which))}
onMouseEnter={() => setOpen((cur) => (cur && cur !== which ? which : cur))}
>
{label}
</button>
{open === which && (
<div className="emb-menu" role="menu">
{which === 'view' && (
<>
<button
role="menuitemcheckbox"
aria-checked={serialOpen}
className="emb-item"
onClick={() => {
setOpen(null);
toggleSerialMonitor();
}}
>
<span>{t('editor.canvas.toggleSerialMonitor', 'Serial Monitor')}</span>
<span className="emb-shortcut">{serialOpen ? '✓' : ''}</span>
</button>
<button
role="menuitemcheckbox"
aria-checked={scopeOpen}
className="emb-item"
onClick={() => {
setOpen(null);
toggleOscilloscope();
}}
>
<span>{t('editor.menu.toggleScope', 'Oscilloscope / Logic Analyzer')}</span>
<span className="emb-shortcut">{scopeOpen ? '✓' : ''}</span>
</button>
<div className="emb-separator" />
</>
)}
{which === 'account' && (
<>
{/* Session entry points, above the pro extras. Exactly one of
the two is registered at a time (pro registers by session
state) and neither exists in OSS, so this block renders
nothing in an OSS build. */}
{accountItems
.filter((item) => item.kind !== 'command' || hasEditorCommand(item.id))
.map((item) => (item.kind === 'command' ? renderCommand(item) : null))}
{accountItems.some(
(item) => item.kind === 'command' && hasEditorCommand(item.id),
) && <div className="emb-separator" />}
{/* Pro account items (PRO badge, Subscribe / Manage
subscription, licenses, history, replays, Privacy) mount
here via the SAME user-menu slot the bottom-left account
dropdown uses — one overlay renderer, two hosts. Clicking
any of them closes this menu (they navigate or open their
own modal). Empty in OSS builds. */}
<div
data-velxio-slot="user-menu"
style={{ display: 'contents' }}
onClick={() => setOpen(null)}
/>
<div className="emb-separator" />
{/* Language moved in here from its own top-level menu — the
menubar was one trigger too wide once the AI chat docks. */}
<div className="emb-section-label">
{t('editor.menu.language', 'Language')}
</div>
{LOCALES.map((loc) => (
<button
key={loc}
role="menuitemradio"
aria-checked={currentLocale === loc}
className="emb-item"
onClick={() => {
setOpen(null);
if (loc === currentLocale) return;
navigate(
switchLocale(location.pathname, loc as Locale) +
location.search +
location.hash,
);
}}
>
<span>{LOCALE_META[loc].nativeName}</span>
<span className="emb-shortcut">{currentLocale === loc ? '✓' : ''}</span>
</button>
))}
</>
)}
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
{which === 'edit' && (
<>
<button
role="menuitem"
className="emb-item"
disabled={historyIndex < 0}
onClick={() => {
setOpen(null);
undo();
}}
>
<span>{t('editor.menu.undo', 'Undo')}</span>
<span className="emb-shortcut">Ctrl+Z</span>
</button>
<button
role="menuitem"
className="emb-item"
disabled={historyIndex >= history.length - 1}
onClick={() => {
setOpen(null);
redo();
}}
>
<span>{t('editor.menu.redo', 'Redo')}</span>
<span className="emb-shortcut">Ctrl+Y</span>
</button>
</>
)}
{items
.filter(
(item) =>
item.kind !== 'command' || !item.optional || hasEditorCommand(item.id),
)
.map((item, i) =>
item.kind === 'separator' ? (
<div key={`sep-${i}`} className="emb-separator" />
) : item.kind === 'link' ? (
renderLink(item)
) : (
renderCommand(item)
),
)}
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
</div>
)}
</div>
);
return (
<div className="editor-menubar" ref={rootRef}>
{menu('file', t('editor.menu.file', 'File'), fileItems)}
{menu('edit', t('editor.menu.edit', 'Edit'), editItems)}
{menu('view', t('editor.menu.view', 'View'), viewItems)}
{menu('account', t('editor.menu.account', 'Account'), [])}
{menu('help', t('editor.menu.help', 'Help'), helpItems)}
feat(editor): menus File/Edit en la cabecera y editor sin nav de marketing La queja: demasiados botones en una sola fila, y en pantallas pequenas se solapan — medido, no impresion: a 1024px Code/Both/Circuit pisaban a Compile/Run/Stop por 9-20px y Add pisaba el chat por 29px, con el idioma y el usuario cortados fuera del viewport. Tres piezas: 1. En el editor, el nav de marketing (Home/Docs/Pricing/...) sobra: ya estas dentro, y cuesta exactamente el ancho que le falta a la toolbar. AppHeader gana la variante editorMenu — mismo mecanismo que ya usa la build de escritorio (VITE_DESKTOP) — que oculta el nav y pinta un menu File/Edit junto al logo. El logo sigue llevando a inicio; idioma, autoguardado, Share y usuario se quedan. 2. File/Edit al estilo escritorio: lo que se usa cada minuto sigue siendo boton; lo que se usa unas veces por sesion va al menu. File: nuevo workspace, nuevo fichero, abrir, guardar, importar, exportar, BOM, imagen del esquema. Edit: undo/redo (con su estado real del historial), centrar vista, zoom. Las acciones viven en cierres de cuatro componentes distintos, asi que hay un registro id->handler (lib/editorCommands, mismo patron de seam que registerProExamples o registerBoardBuiltins): cada dueno registra al montar y el menu invoca por id; un item sin dueno montado se pinta deshabilitado, que ademas es la verdad. El registro sobrevive al remontaje StrictMode (el cleanup viejo no borra al sucesor) y tiene test de eso. 3. La toolbar adelgaza: fuera Undo/Redo (Ctrl+Z/Y siguen), fuera los botones inline de Import/Export y sus gemelos responsivos del More (~70px recuperados); el More queda solo con los extras pro (firmware, GitHub, share, grabacion). El CSS responsivo de los botones retirados se va con ellos. Suite en verde (2300) y build del overlay verificado desde este arbol.
2026-07-30 21:26:36 +07:00
</div>
);
};