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';
|
2026-05-27 08:06:44 +07:00
|
|
|
import { useEditorStore } from '../store/useEditorStore';
|
|
|
|
|
import { useProjectStore } from '../store/useProjectStore';
|
|
|
|
|
import { useCompileLogsStore } from '../store/useCompileLogsStore';
|
2026-07-17 11:06:28 +07:00
|
|
|
import { showMessageDialog } from '../store/useMessageDialogStore';
|
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'
|
feat(desktop): skip welcome screen, robust openExternal, in-app nav
Three coordinated changes that fix the "Waiting for browser…" hang
and unblock first-launch UX on the Tauri desktop build:
1. desktop/index.ts — DON'T mountWelcome unconditionally on first
launch. Before, an empty keychain (no key yet) forced the
welcome / sign-in screen on top of the editor, gating 100% of
the app behind an account. Now the editor opens directly:
compile + run + sim + save .vlx all work for free (they're
upstream OSS features), and the license check still runs in
the background just to populate state for the GraceBanner
(which shows for invalid keys — locked, tampered, in
soft/hard grace). Pro-only features (ESP32 QEMU download,
agent IA) prompt for license at use time, where it actually
matters. Matches the "try before you buy" expectation a
desktop install creates.
2. desktop/tauriBridge.ts — rewrite `openExternal` to try every
known IPC path in cascade order and log via the desktop debug
file which one worked. The previous implementation invoked
`plugin:shell|open` with `{ path: url }`, which silently
failed (no ACL match + wrong arg shape) and fell back to
`window.open`, which inside a Tauri webview is a no-op for
external URLs — the browser never opened. New cascade:
plugin:opener|open_url (paired with tauri-plugin-opener which
ships in this revision), then plugin:shell|open with both
`{ path, with: null }` and `{ url }` shapes, then the
window.__TAURI__.shell / opener high-level wrappers that
specific Tauri 2.x flag combos expose. Each attempt logged
via the dlog helper so the next operator can see exactly
which path was used (or that all failed) without devtools.
3. desktop/menu.ts — new `navigate-route` action type. Routes
bundled in the SPA (DocsPage, ExamplesPage, AboutPage) that
used to open velxio.dev in the system browser now navigate
in-window via history.pushState + popstate (mirrors the
locale-switch handler). Respects the current locale prefix
so `/examples` from `/es/editor` lands at `/es/examples`
instead of jumping back to English.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 03:57:46 +07:00
|
|
|
| 'set-locale'
|
|
|
|
|
| 'navigate-route';
|
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;
|
feat(desktop): skip welcome screen, robust openExternal, in-app nav
Three coordinated changes that fix the "Waiting for browser…" hang
and unblock first-launch UX on the Tauri desktop build:
1. desktop/index.ts — DON'T mountWelcome unconditionally on first
launch. Before, an empty keychain (no key yet) forced the
welcome / sign-in screen on top of the editor, gating 100% of
the app behind an account. Now the editor opens directly:
compile + run + sim + save .vlx all work for free (they're
upstream OSS features), and the license check still runs in
the background just to populate state for the GraceBanner
(which shows for invalid keys — locked, tampered, in
soft/hard grace). Pro-only features (ESP32 QEMU download,
agent IA) prompt for license at use time, where it actually
matters. Matches the "try before you buy" expectation a
desktop install creates.
2. desktop/tauriBridge.ts — rewrite `openExternal` to try every
known IPC path in cascade order and log via the desktop debug
file which one worked. The previous implementation invoked
`plugin:shell|open` with `{ path: url }`, which silently
failed (no ACL match + wrong arg shape) and fell back to
`window.open`, which inside a Tauri webview is a no-op for
external URLs — the browser never opened. New cascade:
plugin:opener|open_url (paired with tauri-plugin-opener which
ships in this revision), then plugin:shell|open with both
`{ path, with: null }` and `{ url }` shapes, then the
window.__TAURI__.shell / opener high-level wrappers that
specific Tauri 2.x flag combos expose. Each attempt logged
via the dlog helper so the next operator can see exactly
which path was used (or that all failed) without devtools.
3. desktop/menu.ts — new `navigate-route` action type. Routes
bundled in the SPA (DocsPage, ExamplesPage, AboutPage) that
used to open velxio.dev in the system browser now navigate
in-window via history.pushState + popstate (mirrors the
locale-switch handler). Respects the current locale prefix
so `/examples` from `/es/editor` lands at `/es/examples`
instead of jumping back to English.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 03:57:46 +07:00
|
|
|
// Only present when action='navigate-route'. Absolute pathname
|
|
|
|
|
// (e.g. '/examples', '/docs', '/about') — navigated via React
|
|
|
|
|
// Router so the current locale prefix gets applied.
|
|
|
|
|
route?: 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':
|
2026-05-27 08:06:44 +07:00
|
|
|
newProject();
|
|
|
|
|
return;
|
2026-05-23 04:08:46 +07:00
|
|
|
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;
|
feat(desktop): skip welcome screen, robust openExternal, in-app nav
Three coordinated changes that fix the "Waiting for browser…" hang
and unblock first-launch UX on the Tauri desktop build:
1. desktop/index.ts — DON'T mountWelcome unconditionally on first
launch. Before, an empty keychain (no key yet) forced the
welcome / sign-in screen on top of the editor, gating 100% of
the app behind an account. Now the editor opens directly:
compile + run + sim + save .vlx all work for free (they're
upstream OSS features), and the license check still runs in
the background just to populate state for the GraceBanner
(which shows for invalid keys — locked, tampered, in
soft/hard grace). Pro-only features (ESP32 QEMU download,
agent IA) prompt for license at use time, where it actually
matters. Matches the "try before you buy" expectation a
desktop install creates.
2. desktop/tauriBridge.ts — rewrite `openExternal` to try every
known IPC path in cascade order and log via the desktop debug
file which one worked. The previous implementation invoked
`plugin:shell|open` with `{ path: url }`, which silently
failed (no ACL match + wrong arg shape) and fell back to
`window.open`, which inside a Tauri webview is a no-op for
external URLs — the browser never opened. New cascade:
plugin:opener|open_url (paired with tauri-plugin-opener which
ships in this revision), then plugin:shell|open with both
`{ path, with: null }` and `{ url }` shapes, then the
window.__TAURI__.shell / opener high-level wrappers that
specific Tauri 2.x flag combos expose. Each attempt logged
via the dlog helper so the next operator can see exactly
which path was used (or that all failed) without devtools.
3. desktop/menu.ts — new `navigate-route` action type. Routes
bundled in the SPA (DocsPage, ExamplesPage, AboutPage) that
used to open velxio.dev in the system browser now navigate
in-window via history.pushState + popstate (mirrors the
locale-switch handler). Respects the current locale prefix
so `/examples` from `/es/editor` lands at `/es/examples`
instead of jumping back to English.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 03:57:46 +07:00
|
|
|
case 'navigate-route':
|
|
|
|
|
if (payload?.route) navigateTo(payload.route);
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(desktop): skip welcome screen, robust openExternal, in-app nav
Three coordinated changes that fix the "Waiting for browser…" hang
and unblock first-launch UX on the Tauri desktop build:
1. desktop/index.ts — DON'T mountWelcome unconditionally on first
launch. Before, an empty keychain (no key yet) forced the
welcome / sign-in screen on top of the editor, gating 100% of
the app behind an account. Now the editor opens directly:
compile + run + sim + save .vlx all work for free (they're
upstream OSS features), and the license check still runs in
the background just to populate state for the GraceBanner
(which shows for invalid keys — locked, tampered, in
soft/hard grace). Pro-only features (ESP32 QEMU download,
agent IA) prompt for license at use time, where it actually
matters. Matches the "try before you buy" expectation a
desktop install creates.
2. desktop/tauriBridge.ts — rewrite `openExternal` to try every
known IPC path in cascade order and log via the desktop debug
file which one worked. The previous implementation invoked
`plugin:shell|open` with `{ path: url }`, which silently
failed (no ACL match + wrong arg shape) and fell back to
`window.open`, which inside a Tauri webview is a no-op for
external URLs — the browser never opened. New cascade:
plugin:opener|open_url (paired with tauri-plugin-opener which
ships in this revision), then plugin:shell|open with both
`{ path, with: null }` and `{ url }` shapes, then the
window.__TAURI__.shell / opener high-level wrappers that
specific Tauri 2.x flag combos expose. Each attempt logged
via the dlog helper so the next operator can see exactly
which path was used (or that all failed) without devtools.
3. desktop/menu.ts — new `navigate-route` action type. Routes
bundled in the SPA (DocsPage, ExamplesPage, AboutPage) that
used to open velxio.dev in the system browser now navigate
in-window via history.pushState + popstate (mirrors the
locale-switch handler). Respects the current locale prefix
so `/examples` from `/es/editor` lands at `/es/examples`
instead of jumping back to English.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 03:57:46 +07:00
|
|
|
function navigateTo(route: string): void {
|
|
|
|
|
// Prefix with the current locale if we're on a non-default one,
|
|
|
|
|
// so `/examples` from `/es/editor` lands at `/es/examples` instead
|
|
|
|
|
// of switching back to English. Mirrors how LanguageSwitcher does it.
|
|
|
|
|
const cur = window.location.pathname;
|
|
|
|
|
const localeMatch = cur.match(/^\/([a-z]{2}(?:-[a-z]{2})?)\b/);
|
|
|
|
|
const prefix = localeMatch && LOCALES.includes(localeMatch[1] as Locale)
|
|
|
|
|
? `/${localeMatch[1]}`
|
|
|
|
|
: '';
|
|
|
|
|
const normalised = route.startsWith('/') ? route : `/${route}`;
|
|
|
|
|
const next = `${prefix}${normalised}`;
|
|
|
|
|
if (next === cur) return;
|
|
|
|
|
// history.pushState + popstate keeps React Router happy without
|
|
|
|
|
// reloading the SPA (Monaco state, simulator state, sidecar
|
|
|
|
|
// connection all preserved).
|
|
|
|
|
window.history.pushState(null, '', next);
|
|
|
|
|
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
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) {
|
2026-07-17 11:06:28 +07:00
|
|
|
showMessageDialog(`Failed to open .vlx: ${(err as Error).message}`, {
|
|
|
|
|
kind: 'error',
|
|
|
|
|
});
|
2026-05-23 04:08:46 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
document.body.removeChild(input);
|
|
|
|
|
});
|
|
|
|
|
input.click();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 08:06:44 +07:00
|
|
|
/**
|
|
|
|
|
* Wipe the current workspace and start from the default Blink sketch +
|
|
|
|
|
* empty canvas. Issue #210: the menu action used to just dispatch a
|
|
|
|
|
* CustomEvent that nobody listened to.
|
|
|
|
|
*
|
|
|
|
|
* Confirms first if the user has unsaved changes (any modified file
|
|
|
|
|
* or any component on the canvas). The Tauri-side menu can't show a
|
|
|
|
|
* confirm dialog cheaply, so we use the browser-native `confirm()`
|
|
|
|
|
* here — fine for the desktop bundle where it renders as a modal.
|
|
|
|
|
*/
|
|
|
|
|
function newProject(): void {
|
|
|
|
|
const sim = useSimulatorStore.getState();
|
|
|
|
|
const editor = useEditorStore.getState();
|
|
|
|
|
const project = useProjectStore.getState();
|
|
|
|
|
const compileLogs = useCompileLogsStore.getState();
|
|
|
|
|
|
|
|
|
|
const hasWork =
|
|
|
|
|
sim.boards.length > 0 ||
|
|
|
|
|
sim.components.length > 0 ||
|
|
|
|
|
sim.wires.length > 0 ||
|
|
|
|
|
editor.files.some((f) => f.modified) ||
|
|
|
|
|
project.currentProject !== null;
|
|
|
|
|
|
|
|
|
|
if (hasWork) {
|
|
|
|
|
const ok = window.confirm(
|
|
|
|
|
'Start a new project? Any unsaved changes will be lost.',
|
|
|
|
|
);
|
|
|
|
|
if (!ok) return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stop any running simulation first so workers / bridges shut down
|
|
|
|
|
// cleanly. Idempotent — no-op if nothing is running.
|
|
|
|
|
if (sim.running) {
|
|
|
|
|
sim.stopSimulation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Drop every board (also disconnects its bridges + removes wires
|
|
|
|
|
// touching it). Iterate over a snapshot copy since removeBoard
|
|
|
|
|
// mutates the array.
|
|
|
|
|
for (const board of [...sim.boards]) {
|
|
|
|
|
sim.removeBoard(board.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Any non-board components + wires that weren't connected to a
|
|
|
|
|
// board still need to go.
|
|
|
|
|
sim.setComponents([]);
|
|
|
|
|
sim.setWires([]);
|
|
|
|
|
|
|
|
|
|
// Reset the editor to the default Blink sketch. loadFiles takes
|
|
|
|
|
// a {name, content}[] and rebuilds the file list, picking the
|
|
|
|
|
// first .ino as active.
|
|
|
|
|
editor.loadFiles([
|
|
|
|
|
{
|
|
|
|
|
name: 'sketch.ino',
|
|
|
|
|
content:
|
|
|
|
|
'// Arduino Blink Example\nvoid setup() {\n pinMode(LED_BUILTIN, OUTPUT);\n}\n\nvoid loop() {\n digitalWrite(LED_BUILTIN, HIGH);\n delay(1000);\n digitalWrite(LED_BUILTIN, LOW);\n delay(1000);\n}\n',
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Drop project metadata so the next Save .vlx doesn't reuse the
|
|
|
|
|
// previous project's slug / name.
|
|
|
|
|
project.clearCurrentProject();
|
|
|
|
|
|
|
|
|
|
// Clear the compile output panel so old build logs don't carry over.
|
|
|
|
|
compileLogs.clear();
|
2026-07-18 13:24:06 +07:00
|
|
|
|
|
|
|
|
// Leave whatever project URL we were on: staying there would reload the
|
|
|
|
|
// OLD project over this fresh workspace on refresh. replaceState (not
|
|
|
|
|
// navigateTo's pushState) so the back button can't pop to the stale
|
|
|
|
|
// project URL either; the popstate dispatch lets React Router render
|
|
|
|
|
// the plain editor route.
|
|
|
|
|
const cur = window.location.pathname;
|
|
|
|
|
const localeMatch = cur.match(/^\/([a-z]{2}(?:-[a-z]{2})?)\b/);
|
|
|
|
|
const prefix = localeMatch && LOCALES.includes(localeMatch[1] as Locale)
|
|
|
|
|
? `/${localeMatch[1]}`
|
|
|
|
|
: '';
|
|
|
|
|
const editorPath = `${prefix}/editor`;
|
|
|
|
|
if (cur !== editorPath) {
|
|
|
|
|
window.history.replaceState(null, '', editorPath);
|
|
|
|
|
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
|
|
|
}
|
2026-05-27 08:06:44 +07:00
|
|
|
}
|
|
|
|
|
|
2026-05-23 04:08:46 +07:00
|
|
|
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) {
|
2026-07-17 11:06:28 +07:00
|
|
|
showMessageDialog('Update plugin not available in this build.');
|
2026-05-23 04:08:46 +07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const update = await updater.check();
|
|
|
|
|
if (update) {
|
|
|
|
|
await update.downloadAndInstall();
|
|
|
|
|
} else {
|
2026-07-17 11:06:28 +07:00
|
|
|
showMessageDialog('Velxio Desktop is up to date.', { kind: 'success' });
|
2026-05-23 04:08:46 +07:00
|
|
|
}
|
|
|
|
|
} catch (err) {
|
2026-07-17 11:06:28 +07:00
|
|
|
showMessageDialog(`Update check failed: ${(err as Error).message}`, {
|
|
|
|
|
kind: 'error',
|
|
|
|
|
});
|
2026-05-23 04:08:46 +07:00
|
|
|
}
|
|
|
|
|
}
|