velxio/frontend/src/desktop/menu.ts

149 lines
4.6 KiB
TypeScript
Raw Normal View History

/**
* Native menubar event bridge.
*
* The Tauri shell (pro/desktop/src-tauri/src/menu.rs in velxio-prod)
* builds a Velxio / File / Edit / View / Help menubar. Internal items
* (Save .vlx, Open .vlx, Toggle Serial Monitor, Find, ) emit a
* `velxio://menu` event with `{ action: '<id>' }`. URL items (Docs,
* Examples, Discord, GitHub) are opened directly from Rust and don't
* reach this listener.
*
* Actions handled directly here (no further plumbing needed):
* - save-vlx, open-vlx triggerDownloadVlx / file picker
* - toggle-serial-monitor useSimulatorStore.toggleSerialMonitor()
* - check-for-updates tauri-plugin-updater check()
*
* Actions forwarded to whoever's listening as a window CustomEvent
* `velxio:menu:<action>`:
* - new-project, find-in-editor, toggle-file-explorer
*
* No-op outside Tauri (e.g. running the bundle in a regular browser
* for debugging) listen() returns a no-op when the global event
* API isn't present.
*/
import { listen } from './tauriBridge';
import { dlog } from './log';
import { triggerDownloadVlx, importVlxFile } from '../utils/vlxFile';
import { useSimulatorStore } from '../store/useSimulatorStore';
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
import { switchLocale } from '../i18n/path';
import { LOCALES, type Locale } from '../i18n/config';
type MenuAction =
| 'new-project'
| 'save-vlx'
| 'open-vlx'
| 'find-in-editor'
| 'toggle-file-explorer'
| 'toggle-serial-monitor'
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
| 'check-for-updates'
| 'set-locale';
interface MenuEventPayload {
action: MenuAction;
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
// Only present when action='set-locale'. Matches an entry in
// i18n/config.ts::LOCALES.
locale?: string;
}
let installed = false;
export async function installDesktopMenuListener(): Promise<void> {
if (installed) return;
installed = true;
await listen<MenuEventPayload>('velxio://menu', (event) => {
dlog('menu event', event.payload);
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
void handle(event.payload.action, event.payload);
});
}
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
async function handle(action: MenuAction, payload?: MenuEventPayload): Promise<void> {
switch (action) {
case 'save-vlx':
triggerDownloadVlx();
return;
case 'open-vlx':
pickAndImportVlx();
return;
case 'toggle-serial-monitor':
useSimulatorStore.getState().toggleSerialMonitor();
return;
case 'new-project':
case 'find-in-editor':
case 'toggle-file-explorer':
window.dispatchEvent(new CustomEvent(`velxio:menu:${action}`));
return;
case 'check-for-updates':
await checkForUpdates();
return;
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
case 'set-locale':
if (payload?.locale) setLocale(payload.locale);
return;
}
}
function setLocale(locale: string): void {
// Defensive: ignore unknown locales coming from the menu so a
// stale shell doesn't navigate to a broken URL.
if (!(LOCALES as readonly string[]).includes(locale)) {
dlog('set-locale: ignoring unknown locale', { locale });
return;
}
const target = locale as Locale;
const next =
switchLocale(window.location.pathname, target) +
window.location.search +
window.location.hash;
if (next === window.location.pathname + window.location.search + window.location.hash) {
return;
}
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
// history.pushState + popstate lets React Router pick the change up
// without a full reload, preserving the editor state. Reload would
// re-spawn the sidecar handshake and lose Monaco/sim state for ~5s.
window.history.pushState(null, '', next);
window.dispatchEvent(new PopStateEvent('popstate'));
}
function pickAndImportVlx(): void {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.vlx,application/json';
input.style.display = 'none';
document.body.appendChild(input);
input.addEventListener('change', async () => {
const file = input.files?.[0];
if (file) {
try {
await importVlxFile(file);
} catch (err) {
// eslint-disable-next-line no-alert
alert(`Failed to open .vlx: ${(err as Error).message}`);
}
}
document.body.removeChild(input);
});
input.click();
}
async function checkForUpdates(): Promise<void> {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updater = (window as any).__TAURI__?.updater;
if (!updater?.check) {
// eslint-disable-next-line no-alert
alert('Update plugin not available in this build.');
return;
}
const update = await updater.check();
if (update) {
await update.downloadAndInstall();
} else {
// eslint-disable-next-line no-alert
alert('Velxio Desktop is up to date.');
}
} catch (err) {
// eslint-disable-next-line no-alert
alert(`Update check failed: ${(err as Error).message}`);
}
}