2026-05-23 04:08:46 +07:00
|
|
|
/**
|
|
|
|
|
* 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';
|
2026-05-23 04:08:46 +07:00
|
|
|
|
|
|
|
|
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';
|
2026-05-23 04:08:46 +07:00
|
|
|
|
|
|
|
|
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;
|
2026-05-23 04:08:46 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2026-05-23 04:08:46 +07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
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> {
|
2026-05-23 04:08:46 +07:00
|
|
|
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;
|
2026-05-23 04:08:46 +07:00
|
|
|
}
|
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'));
|
2026-05-23 04:08:46 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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}`);
|
|
|
|
|
}
|
|
|
|
|
}
|