mirror of
https://github.com/nimbold/Firelink.git
synced 2026-09-10 09:45:44 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ca87fd6a4 |
@@ -308,16 +308,18 @@ async fn download_handler(
|
||||
None => return Err(StatusCode::BAD_REQUEST),
|
||||
};
|
||||
|
||||
let is_hidden = state
|
||||
.app_handle
|
||||
.get_webview_window("main")
|
||||
.and_then(|window| window.is_visible().ok())
|
||||
.is_some_and(|is_visible| !is_visible);
|
||||
crate::restore_main_window(&state.app_handle);
|
||||
if is_hidden {
|
||||
// Sleep briefly to let the webview wake up from macOS App Nap
|
||||
// otherwise the IPC event emitted immediately after is dropped.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
|
||||
if let Some(window) = state.app_handle.get_webview_window("main") {
|
||||
let is_visible = window.is_visible().unwrap_or(true);
|
||||
if !is_visible {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
// Sleep briefly to let the webview wake up from macOS App Nap
|
||||
// otherwise the IPC event emitted immediately after is dropped.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
|
||||
} else {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
|
||||
if !wait_for_frontend(&state.frontend_ready).await {
|
||||
|
||||
+1
-57
@@ -3659,7 +3659,6 @@ pub struct AppState {
|
||||
#[derive(Default)]
|
||||
struct MainWindowRestoreState {
|
||||
requested: AtomicBool,
|
||||
startup_complete: AtomicBool,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -3955,26 +3954,7 @@ where
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn restore_main_window(app_handle: &tauri::AppHandle) {
|
||||
let startup_complete = app_handle
|
||||
.try_state::<MainWindowRestoreState>()
|
||||
.is_none_or(|state| state.startup_complete.load(Ordering::Acquire));
|
||||
if !startup_complete {
|
||||
if let Some(state) = app_handle.try_state::<MainWindowRestoreState>() {
|
||||
// The first window can be requested by a single-instance callback
|
||||
// or an opened .torrent path while the native host is still being
|
||||
// constructed. Do not touch the HWND until RunEvent::Ready.
|
||||
state.requested.store(true, Ordering::Release);
|
||||
// Ready may have won the transition between the first load and
|
||||
// the request store. Re-check before returning so that request is
|
||||
// serviced by this call instead of being left pending forever.
|
||||
if !state.startup_complete.load(Ordering::Acquire) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
fn restore_main_window(app_handle: &tauri::AppHandle) {
|
||||
let Some(window) = app_handle.get_webview_window("main") else {
|
||||
if let Some(state) = app_handle.try_state::<MainWindowRestoreState>() {
|
||||
state.requested.store(true, Ordering::Release);
|
||||
@@ -3987,24 +3967,6 @@ pub(crate) fn restore_main_window(app_handle: &tauri::AppHandle) {
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
|
||||
fn mark_main_window_startup_complete(app_handle: &tauri::AppHandle) {
|
||||
if let Some(state) = app_handle.try_state::<MainWindowRestoreState>() {
|
||||
state.startup_complete.store(true, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn reveal_main_window(app_handle: &tauri::AppHandle) {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
if let Err(error) = window.set_focusable(true) {
|
||||
log::warn!("Could not make the main window focusable: {error}");
|
||||
}
|
||||
if let Err(error) = window.show() {
|
||||
eprintln!("Failed to reveal the main window: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_pending_main_window(app_handle: &tauri::AppHandle) {
|
||||
if app_handle
|
||||
.try_state::<MainWindowRestoreState>()
|
||||
@@ -18550,17 +18512,6 @@ pub fn run() {
|
||||
main_window_builder = main_window_builder
|
||||
.inner_size(startup_size.width as f64, startup_size.height as f64)
|
||||
.prevent_overflow();
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Wry installs its parent WM_SETFOCUS handler while WebView2
|
||||
// is still being initialized. Keep the host hidden and
|
||||
// non-activatable until RunEvent::Ready so Windows cannot
|
||||
// re-enter that handler during native construction.
|
||||
main_window_builder = main_window_builder
|
||||
.visible(false)
|
||||
.focused(false)
|
||||
.focusable(false);
|
||||
}
|
||||
main_window_builder
|
||||
.build()
|
||||
.map_err(|error| format!("failed to create main window: {error}"))?;
|
||||
@@ -19896,13 +19847,6 @@ pub fn run() {
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application")
|
||||
.run(|app_handle, event| match event {
|
||||
tauri::RunEvent::Ready => {
|
||||
mark_main_window_startup_complete(app_handle);
|
||||
#[cfg(target_os = "windows")]
|
||||
reveal_main_window(app_handle);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
restore_pending_main_window(app_handle);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
tauri::RunEvent::Opened { urls } => {
|
||||
let paths = collect_opened_torrent_paths(
|
||||
|
||||
@@ -439,7 +439,6 @@ pub fn open_download_properties_window(
|
||||
.visible(false)
|
||||
// A hidden WebView2 must not request focus during construction. The
|
||||
// native reveal path focuses it after the window is visible.
|
||||
.focused(false)
|
||||
.transparent(true);
|
||||
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
|
||||
let builder = builder.decorations(false);
|
||||
|
||||
+126
-2
@@ -1,4 +1,128 @@
|
||||
import { StrictMode, type ComponentType } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "@fontsource-variable/inter/wght.css";
|
||||
import "@fontsource-variable/noto-sans-hebrew/wght.css";
|
||||
import "@fontsource-variable/noto-sans-sc/wght.css";
|
||||
import "@fontsource-variable/outfit/wght.css";
|
||||
import "@fontsource-variable/roboto/wght.css";
|
||||
import "@fontsource-variable/vazirmatn/wght.css";
|
||||
import "./index.css";
|
||||
import { i18nReady } from "./i18n";
|
||||
import { ErrorBoundary } from "./components/ErrorBoundary";
|
||||
import { ToastProvider } from "./contexts/ToastContext";
|
||||
import { error as logError, warn as logWarn, initLogger } from "./utils/logger";
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import { invokeCommand as invoke } from './ipc';
|
||||
|
||||
const isPropertiesWindow = getCurrentWindow().label.startsWith('properties-');
|
||||
|
||||
void initLogger();
|
||||
|
||||
const serializeConsoleArguments = (values: unknown[]) => values.map(value => {
|
||||
if (value instanceof Error) return `${value.name}: ${value.message}\n${value.stack || ''}`;
|
||||
if (typeof value === 'string') return value;
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}).join(' ');
|
||||
|
||||
const redactConsoleMessage = (message: string) => message
|
||||
.replace(/(authorization|cookie|password|token|secret)\s*[:=]\s*([^\s,;]+)/gi, '$1=[redacted]')
|
||||
.replace(/(https?:\/\/[^\s?]+)\?[^\s]+/g, '$1?[redacted]');
|
||||
|
||||
const originalConsoleError = console.error.bind(console);
|
||||
const originalConsoleWarn = console.warn.bind(console);
|
||||
console.error = (...values: unknown[]) => {
|
||||
originalConsoleError(...values);
|
||||
void logError(redactConsoleMessage(serializeConsoleArguments(values))).catch(() => undefined);
|
||||
};
|
||||
console.warn = (...values: unknown[]) => {
|
||||
originalConsoleWarn(...values);
|
||||
void logWarn(redactConsoleMessage(serializeConsoleArguments(values))).catch(() => undefined);
|
||||
};
|
||||
|
||||
const rootElement = document.getElementById("root");
|
||||
if (rootElement) {
|
||||
rootElement.textContent = "Firelink startup control";
|
||||
const renderRoot = (RootComponent: ComponentType) => {
|
||||
if (!rootElement) return;
|
||||
|
||||
createRoot(rootElement).render(
|
||||
<StrictMode>
|
||||
<ErrorBoundary>
|
||||
<ToastProvider>
|
||||
<RootComponent />
|
||||
</ToastProvider>
|
||||
</ErrorBoundary>
|
||||
</StrictMode>,
|
||||
);
|
||||
};
|
||||
|
||||
const PropertiesStartupFailure = () => (
|
||||
<main className="properties-window-shell flex h-screen min-h-0 flex-col items-center justify-center gap-4 bg-main-bg p-6 text-text-primary">
|
||||
<p role="alert">Download Properties could not be loaded.</p>
|
||||
<button
|
||||
type="button"
|
||||
className="app-button app-button-primary px-3 text-xs"
|
||||
onClick={() => {
|
||||
void getCurrentWindow().close().catch(error => {
|
||||
console.error('[PropertiesStartupFailure] close failed', error);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</main>
|
||||
);
|
||||
|
||||
const renderMainApp = async () => {
|
||||
if (!rootElement) return;
|
||||
|
||||
// Keep the child entrypoint isolated from the main application module. App
|
||||
// imports the persistent Zustand stores, whose module initialization issues
|
||||
// main-window-only IPC commands. Loading it in a Properties child creates a
|
||||
// second persistence owner and can race the bridge handshake.
|
||||
const RootComponent = (await import('./App')).default;
|
||||
renderRoot(RootComponent);
|
||||
};
|
||||
|
||||
const renderPropertiesApp = async () => {
|
||||
if (!rootElement) return;
|
||||
|
||||
try {
|
||||
// Properties starts with the synchronous English catalog and changes locale
|
||||
// after its first paint. Waiting for a lazy locale chunk here delays the
|
||||
// loading shell and makes native window startup visible to the user.
|
||||
const RootComponent = (await import('./components/PropertiesWindowApp')).PropertiesWindowApp;
|
||||
renderRoot(RootComponent);
|
||||
} catch (error) {
|
||||
// A failed lazy chunk must not leave the native window hidden forever. Show
|
||||
// a styled, closable failure state and use the same caller-validated native
|
||||
// reveal command as the normal child path.
|
||||
console.error('Failed to initialize the Properties window:', error);
|
||||
renderRoot(PropertiesStartupFailure);
|
||||
const fallbackSessionId = crypto.randomUUID();
|
||||
void invoke('properties_window_send_ready', { sessionId: fallbackSessionId })
|
||||
.then(() => invoke('properties_window_reveal', { sessionId: fallbackSessionId }))
|
||||
.catch(revealError => {
|
||||
console.error('Failed to reveal the Properties startup error:', revealError);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (isPropertiesWindow) {
|
||||
void renderPropertiesApp();
|
||||
} else {
|
||||
void i18nReady.then(renderMainApp).catch(error => {
|
||||
console.error('Failed to initialize localization:', error);
|
||||
void renderMainApp();
|
||||
});
|
||||
}
|
||||
|
||||
// Prevent the webview's default context menu ("Reload", etc.) on right-click.
|
||||
// Individual components that provide custom context menus call preventDefault()
|
||||
// in their own onContextMenu handlers, which fires before this document-level
|
||||
// listener and is unaffected.
|
||||
document.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user