825160ee46
Chronological port of every Tauri release commit (v0.11.4 -> v0.15.2)
into the Electron rebuild, plus Discord-style audio handling that goes
beyond the Tauri original.
Highlights:
- All 17 Tauri release commits ported (audio fixes, custom notification
sound, Discord-style chat UX, profile banner, changelog page,
Discord-parity call UX, screen-share echo + re-watch UX, audio-loop
fix).
- Native napi-rs audio-loopback addon with WASAPI process-loopback:
* EXCLUDE_TARGET_PROCESS_TREE for full-screen shares -> peers
never hear themselves echoed back through the capture.
* INCLUDE_TARGET_PROCESS_TREE for window shares -> only the
picked window's audio is captured, not the whole OS mixer
(Discord parity).
* HWND -> PID resolution via Win32 GetWindowThreadProcessId.
- Discord-style screen-source picker (thumbnail grid, screens vs
apps tabs, live-refreshing thumbnails).
- Hash routing fix for packaged builds (file:// can't resolve
BrowserRouter paths).
- Tauri sources removed (apps/desktop/src-tauri).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
180 lines
7.5 KiB
TypeScript
180 lines
7.5 KiB
TypeScript
import { lazy, Suspense } from 'react';
|
|
import { HashRouter, Navigate, Outlet, Route, Routes } from 'react-router-dom';
|
|
|
|
import { AppShell } from './components/AppShell';
|
|
import { CrashToast } from './components/CrashToast';
|
|
import { ErrorBoundary } from './components/ErrorBoundary';
|
|
import { SpinnerIcon } from './components/icons';
|
|
import { UpdateToast } from './components/UpdateToast';
|
|
import { RequireAdmin, RequireAuth, RequireDevice } from './components/guards';
|
|
import { AuthProvider } from './context/AuthContext';
|
|
import { CallProvider } from './context/CallContext';
|
|
import { ConversationsProvider } from './context/ConversationsContext';
|
|
import { FriendshipsProvider } from './context/FriendshipsContext';
|
|
import { ThemeProvider } from './context/ThemeContext';
|
|
import { AuthPage } from './pages/AuthPage';
|
|
import { ChatsEmptyState, ChatsPage } from './pages/ChatsPage';
|
|
import { ConversationPage } from './pages/ConversationPage';
|
|
|
|
// Routes rarely visited on first render are pulled out of the initial bundle.
|
|
// AuthPage stays eager because it's the first screen unauthenticated users
|
|
// see; ChatsPage + ConversationPage stay eager because every authenticated
|
|
// session renders them immediately.
|
|
const AdminPage = lazy(() => import('./pages/AdminPage').then((m) => ({ default: m.AdminPage })));
|
|
const AuthCallbackPage = lazy(() =>
|
|
import('./pages/AuthCallbackPage').then((m) => ({ default: m.AuthCallbackPage })),
|
|
);
|
|
const DevicePage = lazy(() => import('./pages/DevicePage').then((m) => ({ default: m.DevicePage })));
|
|
const FriendsPage = lazy(() =>
|
|
import('./pages/FriendsPage').then((m) => ({ default: m.FriendsPage })),
|
|
);
|
|
const SettingsPage = lazy(() =>
|
|
import('./pages/SettingsPage').then((m) => ({ default: m.SettingsPage })),
|
|
);
|
|
const ChangelogPage = lazy(() =>
|
|
import('./pages/ChangelogPage').then((m) => ({ default: m.ChangelogPage })),
|
|
);
|
|
|
|
function RouteSuspense({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<Suspense
|
|
fallback={
|
|
<div className="flex min-h-full w-full items-center justify-center bg-surface-3">
|
|
<SpinnerIcon className="h-5 w-5 text-accent" />
|
|
</div>
|
|
}
|
|
>
|
|
{children}
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
// Isolates each top-level route so a crash in one page doesn't take the whole
|
|
// shell down. The boundary auto-retries via `ErrorBoundary`'s backoff schedule.
|
|
function RouteBoundary({ scope }: { scope: string }) {
|
|
return (
|
|
<ErrorBoundary scope={scope}>
|
|
<Outlet />
|
|
</ErrorBoundary>
|
|
);
|
|
}
|
|
|
|
export function App() {
|
|
return (
|
|
<ErrorBoundary scope="root">
|
|
<ThemeProvider>
|
|
<AuthProvider>
|
|
<FriendshipsProvider>
|
|
<ConversationsProvider>
|
|
<CallProvider>
|
|
<HashRouter
|
|
future={{
|
|
// Opt into v7 behaviour early so the upgrade is a no-op:
|
|
// - `v7_startTransition` wraps navigations in startTransition
|
|
// so Suspense / concurrent rendering deal with the new tree
|
|
// - `v7_relativeSplatPath` matches relative paths inside
|
|
// splat routes against the parent splat segment (not the
|
|
// full matched path). Our tree has no splat routes today
|
|
// but this kills the runtime warning and future-proofs.
|
|
v7_startTransition: true,
|
|
v7_relativeSplatPath: true,
|
|
}}
|
|
>
|
|
<Routes>
|
|
<Route element={<RouteBoundary scope="auth" />}>
|
|
<Route path="/auth" element={<AuthPage />} />
|
|
<Route
|
|
path="/auth/callback"
|
|
element={
|
|
<RouteSuspense>
|
|
<AuthCallbackPage />
|
|
</RouteSuspense>
|
|
}
|
|
/>
|
|
</Route>
|
|
<Route element={<RequireAuth />}>
|
|
<Route element={<RouteBoundary scope="device" />}>
|
|
<Route
|
|
path="/device"
|
|
element={
|
|
<RouteSuspense>
|
|
<DevicePage />
|
|
</RouteSuspense>
|
|
}
|
|
/>
|
|
</Route>
|
|
<Route element={<RequireDevice />}>
|
|
<Route element={<AppShell />}>
|
|
<Route index element={<Navigate to="/chats" replace />} />
|
|
<Route element={<RouteBoundary scope="chats" />}>
|
|
<Route path="/chats" element={<ChatsPage />}>
|
|
<Route index element={<ChatsEmptyState />} />
|
|
<Route
|
|
path=":id"
|
|
element={
|
|
<ErrorBoundary scope="conversation">
|
|
<ConversationPage />
|
|
</ErrorBoundary>
|
|
}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
<Route element={<RouteBoundary scope="friends" />}>
|
|
<Route
|
|
path="/friends"
|
|
element={
|
|
<RouteSuspense>
|
|
<FriendsPage />
|
|
</RouteSuspense>
|
|
}
|
|
/>
|
|
</Route>
|
|
<Route element={<RouteBoundary scope="settings" />}>
|
|
<Route
|
|
path="/settings"
|
|
element={
|
|
<RouteSuspense>
|
|
<SettingsPage />
|
|
</RouteSuspense>
|
|
}
|
|
/>
|
|
</Route>
|
|
<Route element={<RouteBoundary scope="changelog" />}>
|
|
<Route
|
|
path="/changelog"
|
|
element={
|
|
<RouteSuspense>
|
|
<ChangelogPage />
|
|
</RouteSuspense>
|
|
}
|
|
/>
|
|
</Route>
|
|
<Route element={<RequireAdmin />}>
|
|
<Route element={<RouteBoundary scope="admin" />}>
|
|
<Route
|
|
path="/admin"
|
|
element={
|
|
<RouteSuspense>
|
|
<AdminPage />
|
|
</RouteSuspense>
|
|
}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
</Route>
|
|
</Route>
|
|
</Route>
|
|
<Route path="*" element={<Navigate to="/chats" replace />} />
|
|
</Routes>
|
|
<UpdateToast />
|
|
<CrashToast />
|
|
</HashRouter>
|
|
</CallProvider>
|
|
</ConversationsProvider>
|
|
</FriendshipsProvider>
|
|
</AuthProvider>
|
|
</ThemeProvider>
|
|
</ErrorBoundary>
|
|
);
|
|
}
|