Add Windows support via a Tauri (WebView2) shell

zero-native has no Windows target yet, so add a parallel Tauri shell
for Windows that reuses the same frontend:

- src-tauri/: Rust commands (yt_search, yt_streams, store_load,
  store_save) mirroring the Zig bridge; spawns bundled yt-dlp.exe with
  CREATE_NO_WINDOW; data stored in app_data_dir
- frontend bridge now auto-detects window.__TAURI__ vs window.zero and
  routes accordingly; CSP updated for Tauri IPC
- tauri.conf.json bundles yt-dlp.exe as a resource and builds nsis/msi
- scripts/make-icon.js generates appicon.png for 'tauri icon'
- README: native Windows build steps + WSLg fallback note
This commit is contained in:
Jonathan Sykes
2026-06-14 10:04:27 +08:00
parent a171cae417
commit bac6665b3d
12 changed files with 527 additions and 18 deletions

View File

@@ -9,19 +9,25 @@
* ========================================================================== */
// ---------- Native bridge adapter ----------
async function invoke(command, payload = {}) {
if (window.zero && typeof window.zero.invoke === 'function') {
return await window.zero.invoke(command, payload);
}
throw new Error('Native bridge unavailable — run this inside zero-native (zig build run).');
// Works against two shells from the same frontend:
// • Tauri (Windows / WebView2): window.__TAURI__.core.invoke, snake_case commands
// • zero-native (Linux / macOS): window.zero.invoke, dotted commands
const TAURI = window.__TAURI__ && window.__TAURI__.core ? window.__TAURI__.core : null;
const ZERO = window.zero && typeof window.zero.invoke === 'function' ? window.zero : null;
// call(zeroName, tauriName, payload) — routes to whichever shell is present.
async function call(zeroName, tauriName, payload = {}) {
if (TAURI) return await TAURI.invoke(tauriName, payload);
if (ZERO) return await ZERO.invoke(zeroName, payload);
throw new Error('No native bridge available — run inside the YT Player app.');
}
const API = {
search: (query) => invoke('yt.search', { query }),
getStreams: (videoId) => invoke('yt.streams', { videoId }),
loadData: () => invoke('store.load', {}),
search: (query) => call('yt.search', 'yt_search', { query }),
getStreams: (videoId) => call('yt.streams', 'yt_streams', { videoId }),
loadData: () => call('store.load', 'store_load', {}),
// data is sent pre-stringified so the native side can write it verbatim.
saveData: (data) => invoke('store.save', { data: JSON.stringify(data) }),
saveData: (data) => call('store.save', 'store_save', { data: JSON.stringify(data) }),
};
// ---------- State ----------