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 { useSimulatorStore } from '../../store/useSimulatorStore';
|
|
|
|
|
import {
|
|
|
|
|
hasEditorCommand,
|
|
|
|
|
runEditorCommand,
|
|
|
|
|
subscribeEditorCommands,
|
|
|
|
|
getEditorCommandsVersion,
|
|
|
|
|
type EditorCommandId,
|
|
|
|
|
} from '../../lib/editorCommands';
|
|
|
|
|
import './EditorMenuBar.css';
|
|
|
|
|
|
|
|
|
|
type Item =
|
|
|
|
|
| { kind: 'command'; id: EditorCommandId; label: string; shortcut?: string }
|
2026-07-30 22:31:31 +07:00
|
|
|
| { 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' };
|
|
|
|
|
|
2026-07-30 22:31:31 +07:00
|
|
|
// 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';
|
2026-07-30 22:31:31 +07:00
|
|
|
|
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();
|
2026-07-30 22:31:31 +07:00
|
|
|
const [open, setOpen] = useState<'file' | 'edit' | '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 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' },
|
|
|
|
|
{ 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)') },
|
|
|
|
|
{
|
|
|
|
|
kind: 'command',
|
|
|
|
|
id: 'project.exportScreenshot',
|
|
|
|
|
label: t('editor.toolbar.exportScreenshotLabel', 'Schematic image (PNG)'),
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
2026-07-30 22:31:31 +07:00
|
|
|
const helpItems: Item[] = [
|
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') },
|
2026-07-30 22:31:31 +07:00
|
|
|
{ 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') },
|
2026-07-30 22:31:31 +07:00
|
|
|
{ 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') },
|
2026-07-30 22:31:31 +07:00
|
|
|
{ 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') },
|
|
|
|
|
];
|
|
|
|
|
|
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 editItems: Item[] = [
|
|
|
|
|
{ kind: 'separator' }, // placeholder: undo/redo render specially above
|
|
|
|
|
{ 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') },
|
|
|
|
|
];
|
|
|
|
|
|
2026-07-30 22:31:31 +07:00
|
|
|
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}</span>
|
|
|
|
|
{item.shortcut && <span className="emb-shortcut">{item.shortcut}</span>}
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-30 22:31:31 +07:00
|
|
|
const menu = (which: 'file' | 'edit' | '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 === '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.map((item, i) =>
|
|
|
|
|
item.kind === 'separator' ? (
|
|
|
|
|
<div key={`sep-${i}`} className="emb-separator" />
|
2026-07-30 22:31:31 +07:00
|
|
|
) : item.kind === 'link' ? (
|
|
|
|
|
renderLink(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
|
|
|
) : (
|
|
|
|
|
renderCommand(item)
|
|
|
|
|
),
|
|
|
|
|
)}
|
|
|
|
|
</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)}
|
2026-07-30 22:31:31 +07:00
|
|
|
{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>
|
|
|
|
|
);
|
|
|
|
|
};
|