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
|
|
|
/**
|
|
|
|
|
* editorCommands — a tiny id → handler registry for the editor menu bar.
|
|
|
|
|
*
|
|
|
|
|
* The actions the File/Edit menus expose (save, open, export, reset the
|
|
|
|
|
* canvas view…) already exist, but they live inside the closures of four
|
|
|
|
|
* different components: EditorPage owns save/new, FileExplorer owns open,
|
|
|
|
|
* EditorToolbar owns import/export, SimulatorCanvas owns the view. A menu
|
|
|
|
|
* mounted in the app header can reach none of them by props without
|
|
|
|
|
* threading callbacks through the whole page tree.
|
|
|
|
|
*
|
|
|
|
|
* So the owners register their handler under a stable id on mount (and
|
|
|
|
|
* unregister on unmount), and the menu invokes by id — the same
|
|
|
|
|
* registration-seam pattern the codebase already uses for pro examples,
|
|
|
|
|
* guest setups and board builtins. A menu item whose command is not
|
|
|
|
|
* currently registered renders disabled, which is also the truth: with no
|
|
|
|
|
* canvas mounted there is nothing to reset.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
export type EditorCommandId =
|
|
|
|
|
| 'project.new'
|
|
|
|
|
| 'project.save'
|
|
|
|
|
| 'project.open'
|
|
|
|
|
| 'project.import'
|
|
|
|
|
| 'project.export'
|
|
|
|
|
| 'project.exportBom'
|
|
|
|
|
| 'project.exportScreenshot'
|
|
|
|
|
| 'file.new'
|
2026-07-31 00:18:37 +07:00
|
|
|
| 'project.share'
|
|
|
|
|
| 'project.githubSync'
|
|
|
|
|
| 'firmware.upload'
|
|
|
|
|
| 'sim.record'
|
feat(editor): View and Language menus — full desktop-menu parity
The menubar now matches the desktop app's native menu set: File, Edit,
View, Language, Help.
View collects what the user asked to reach from a menu: Compile (Ctrl+B),
Run, Stop, Reset up top; the panel toggles — File Explorer, Output
Console, Serial Monitor, Oscilloscope/Logic Analyzer — in the middle
(store-backed ones render a live check, like a real desktop menu); and
the canvas view actions (center, zoom) move here from Edit, which goes
back to being undo/redo only, as menus have always worked.
Language lists the nine locales with the current one checked, switching
through the same switchLocale path the header globe uses — so language is
reachable from the menubar regardless of the account state, on top of
living in the signed-in account menu.
Run/Stop/Compile/Reset and the console toggle register through the
editorCommands seam from EditorToolbar (their handlers close over its
state); the explorer toggle from EditorPage; serial and scope call their
stores directly, same as undo/redo.
2026-07-31 02:05:55 +07:00
|
|
|
| 'sim.compile'
|
|
|
|
|
| 'sim.run'
|
|
|
|
|
| 'sim.stop'
|
|
|
|
|
| 'sim.resetBoard'
|
|
|
|
|
| 'view.toggleExplorer'
|
|
|
|
|
| 'view.toggleConsole'
|
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
|
|
|
| 'view.reset'
|
|
|
|
|
| 'view.zoomIn'
|
|
|
|
|
| 'view.zoomOut';
|
|
|
|
|
|
|
|
|
|
const handlers = new Map<EditorCommandId, () => void>();
|
|
|
|
|
|
|
|
|
|
// Menus re-render on registration changes so items enable/disable live.
|
|
|
|
|
let version = 0;
|
|
|
|
|
const listeners = new Set<() => void>();
|
|
|
|
|
|
|
|
|
|
export function registerEditorCommand(id: EditorCommandId, run: () => void): () => void {
|
|
|
|
|
handlers.set(id, run);
|
|
|
|
|
version++;
|
|
|
|
|
for (const l of listeners) l();
|
|
|
|
|
return () => {
|
|
|
|
|
// Only clear if we are still the registered handler — a remount
|
|
|
|
|
// registers the new closure BEFORE the old effect's cleanup runs.
|
|
|
|
|
if (handlers.get(id) === run) {
|
|
|
|
|
handlers.delete(id);
|
|
|
|
|
version++;
|
|
|
|
|
for (const l of listeners) l();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function hasEditorCommand(id: EditorCommandId): boolean {
|
|
|
|
|
return handlers.has(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function runEditorCommand(id: EditorCommandId): void {
|
|
|
|
|
handlers.get(id)?.();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function subscribeEditorCommands(cb: () => void): () => void {
|
|
|
|
|
listeners.add(cb);
|
|
|
|
|
return () => {
|
|
|
|
|
listeners.delete(cb);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getEditorCommandsVersion(): number {
|
|
|
|
|
return version;
|
|
|
|
|
}
|