mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-07-31 00:17:28 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b00f62ebec | ||
|
|
2025a2a690 |
Vendored
+23
-1
@@ -1,23 +1,45 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"applescript",
|
||||||
|
"autoconfig",
|
||||||
"autologin",
|
"autologin",
|
||||||
|
"cdylib",
|
||||||
"CFURL",
|
"CFURL",
|
||||||
|
"checkin",
|
||||||
"clippy",
|
"clippy",
|
||||||
|
"codegen",
|
||||||
"donutbrowser",
|
"donutbrowser",
|
||||||
|
"dtolnay",
|
||||||
|
"elif",
|
||||||
|
"gifs",
|
||||||
"launchservices",
|
"launchservices",
|
||||||
"mountpoint",
|
"mountpoint",
|
||||||
|
"Mullvad",
|
||||||
"nodecar",
|
"nodecar",
|
||||||
"ntlm",
|
"ntlm",
|
||||||
|
"objc",
|
||||||
|
"osascript",
|
||||||
|
"plasmohq",
|
||||||
"propertylist",
|
"propertylist",
|
||||||
|
"reqwest",
|
||||||
"rlib",
|
"rlib",
|
||||||
"rustc",
|
"rustc",
|
||||||
"serde",
|
"serde",
|
||||||
"shadcn",
|
"shadcn",
|
||||||
"signon",
|
"signon",
|
||||||
|
"sonner",
|
||||||
"sspi",
|
"sspi",
|
||||||
"staticlib",
|
"staticlib",
|
||||||
|
"swatinem",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"systempreferences",
|
"systempreferences",
|
||||||
"turbopack"
|
"tauri",
|
||||||
|
"titlebar",
|
||||||
|
"Torbrowser",
|
||||||
|
"turbopack",
|
||||||
|
"unlisten",
|
||||||
|
"wiremock",
|
||||||
|
"xattr",
|
||||||
|
"zhom"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@
|
|||||||
"rsc": true,
|
"rsc": true,
|
||||||
"tsx": true,
|
"tsx": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "",
|
"config": "tailwind.config.js",
|
||||||
"css": "src/styles/globals.css",
|
"css": "src/styles/globals.css",
|
||||||
"baseColor": "zinc",
|
"baseColor": "zinc",
|
||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
@@ -18,4 +18,4 @@
|
|||||||
"hooks": "@/hooks"
|
"hooks": "@/hooks"
|
||||||
},
|
},
|
||||||
"iconLibrary": "lucide"
|
"iconLibrary": "lucide"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
@@ -971,6 +971,8 @@ dependencies = [
|
|||||||
"directories",
|
"directories",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"objc2 0.6.1",
|
||||||
|
"objc2-app-kit",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ futures-util = "0.3"
|
|||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
core-foundation="0.10"
|
core-foundation="0.10"
|
||||||
|
objc2 = "0.6.1"
|
||||||
|
objc2-app-kit = { version = "0.3.1", features = ["NSWindow"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.13.0"
|
tempfile = "3.13.0"
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
"core:event:default",
|
"core:event:default",
|
||||||
|
"core:window:default",
|
||||||
|
"core:window:allow-start-dragging",
|
||||||
|
"core:window:allow-close",
|
||||||
|
"core:window:allow-minimize",
|
||||||
|
"core:window:allow-toggle-maximize",
|
||||||
"opener:default",
|
"opener:default",
|
||||||
"fs:default",
|
"fs:default",
|
||||||
"shell:allow-execute",
|
"shell:allow-execute",
|
||||||
|
|||||||
+119
-1
@@ -1,7 +1,7 @@
|
|||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tauri::{Emitter, Manager};
|
use tauri::{Emitter, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder};
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
|
|
||||||
// Store pending URLs that need to be handled when the window is ready
|
// Store pending URLs that need to be handled when the window is ready
|
||||||
@@ -58,6 +58,51 @@ use app_auto_updater::{
|
|||||||
get_app_version_info,
|
get_app_version_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Trait to extend WebviewWindow with transparent titlebar functionality
|
||||||
|
pub trait WindowExt {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn set_transparent_titlebar(&self, transparent: bool) -> Result<(), String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Runtime> WindowExt for WebviewWindow<R> {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn set_transparent_titlebar(&self, transparent: bool) -> Result<(), String> {
|
||||||
|
use objc2::rc::Retained;
|
||||||
|
use objc2_app_kit::{NSWindow, NSWindowStyleMask, NSWindowTitleVisibility};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ns_window: Retained<NSWindow> =
|
||||||
|
Retained::retain(self.ns_window().unwrap().cast()).unwrap();
|
||||||
|
|
||||||
|
if transparent {
|
||||||
|
// Hide the title text
|
||||||
|
ns_window.setTitleVisibility(NSWindowTitleVisibility(2)); // NSWindowTitleHidden
|
||||||
|
|
||||||
|
// Make titlebar transparent
|
||||||
|
ns_window.setTitlebarAppearsTransparent(true);
|
||||||
|
|
||||||
|
// Set full size content view
|
||||||
|
let current_mask = ns_window.styleMask();
|
||||||
|
let new_mask = NSWindowStyleMask(current_mask.0 | (1 << 15)); // NSFullSizeContentViewWindowMask
|
||||||
|
ns_window.setStyleMask(new_mask);
|
||||||
|
} else {
|
||||||
|
// Show the title text
|
||||||
|
ns_window.setTitleVisibility(NSWindowTitleVisibility(0)); // NSWindowTitleVisible
|
||||||
|
|
||||||
|
// Make titlebar opaque
|
||||||
|
ns_window.setTitlebarAppearsTransparent(false);
|
||||||
|
|
||||||
|
// Remove full size content view
|
||||||
|
let current_mask = ns_window.styleMask();
|
||||||
|
let new_mask = NSWindowStyleMask(current_mask.0 & !(1 << 15));
|
||||||
|
ns_window.setStyleMask(new_mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn greet() -> String {
|
fn greet() -> String {
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
@@ -124,6 +169,61 @@ async fn check_and_handle_startup_url(app_handle: tauri::AppHandle) -> Result<bo
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn set_window_background_color(
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
|
is_dark_mode: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
if let Some(window) = app_handle.get_webview_window("main") {
|
||||||
|
use objc2::rc::Retained;
|
||||||
|
use objc2_app_kit::{NSColor, NSWindow};
|
||||||
|
|
||||||
|
let ns_window: Retained<NSWindow> =
|
||||||
|
unsafe { Retained::retain(window.ns_window().unwrap().cast()).unwrap() };
|
||||||
|
|
||||||
|
let bg_color = if is_dark_mode {
|
||||||
|
// Dark mode - pure black background
|
||||||
|
unsafe { NSColor::colorWithRed_green_blue_alpha(0.0, 0.0, 0.0, 1.0) }
|
||||||
|
} else {
|
||||||
|
// Light mode - pure white background
|
||||||
|
unsafe { NSColor::colorWithRed_green_blue_alpha(1.0, 1.0, 1.0, 1.0) }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure this runs on the main thread for immediate visual update
|
||||||
|
unsafe {
|
||||||
|
// Set the window background color
|
||||||
|
ns_window.setBackgroundColor(Some(&bg_color));
|
||||||
|
|
||||||
|
// Force immediate visual updates using multiple refresh methods
|
||||||
|
ns_window.invalidateShadow();
|
||||||
|
ns_window.display();
|
||||||
|
|
||||||
|
// Ensure the window content is redrawn
|
||||||
|
if let Some(content_view) = ns_window.contentView() {
|
||||||
|
content_view.setNeedsDisplay(true);
|
||||||
|
content_view.displayIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger a window update
|
||||||
|
ns_window.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also emit an event to the frontend to ensure synchronization
|
||||||
|
let _ = app_handle.emit("window-background-updated", is_dark_mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
{
|
||||||
|
// For non-macOS platforms, we can't change the native window background
|
||||||
|
let _ = (app_handle, is_dark_mode); // Suppress unused variable warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
@@ -132,6 +232,23 @@ pub fn run() {
|
|||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
|
// Create the main window programmatically
|
||||||
|
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
||||||
|
.title("Donut Browser")
|
||||||
|
.inner_size(900.0, 600.0)
|
||||||
|
.resizable(false)
|
||||||
|
.fullscreen(false);
|
||||||
|
|
||||||
|
let window = win_builder.build().unwrap();
|
||||||
|
|
||||||
|
// Set transparent titlebar for macOS
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
if let Err(e) = window.set_transparent_titlebar(true) {
|
||||||
|
eprintln!("Failed to set transparent titlebar: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set up deep link handler
|
// Set up deep link handler
|
||||||
let handle = app.handle().clone();
|
let handle = app.handle().clone();
|
||||||
|
|
||||||
@@ -264,6 +381,7 @@ pub fn run() {
|
|||||||
check_for_app_updates_manual,
|
check_for_app_updates_manual,
|
||||||
download_and_install_app_update,
|
download_and_install_app_update,
|
||||||
get_app_version_info,
|
get_app_version_info,
|
||||||
|
set_window_background_color,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -10,15 +10,7 @@
|
|||||||
"frontendDist": "../dist"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [],
|
||||||
{
|
|
||||||
"title": "Donut Browser",
|
|
||||||
"width": 900,
|
|
||||||
"height": 600,
|
|
||||||
"resizable": false,
|
|
||||||
"fullscreen": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import "@/styles/globals.css";
|
|||||||
import { CustomThemeProvider } from "@/components/theme-provider";
|
import { CustomThemeProvider } from "@/components/theme-provider";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
|
import { WindowDragArea } from "@/components/window-drag-area";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@@ -26,6 +27,7 @@ export default function RootLayout({
|
|||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
<CustomThemeProvider>
|
<CustomThemeProvider>
|
||||||
|
<WindowDragArea />
|
||||||
<TooltipProvider>{children}</TooltipProvider>
|
<TooltipProvider>{children}</TooltipProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</CustomThemeProvider>
|
</CustomThemeProvider>
|
||||||
|
|||||||
+14
-8
@@ -48,6 +48,7 @@ export default function Home() {
|
|||||||
useState<BrowserProfile | null>(null);
|
useState<BrowserProfile | null>(null);
|
||||||
const [currentProfileForVersionChange, setCurrentProfileForVersionChange] =
|
const [currentProfileForVersionChange, setCurrentProfileForVersionChange] =
|
||||||
useState<BrowserProfile | null>(null);
|
useState<BrowserProfile | null>(null);
|
||||||
|
const [hasCheckedStartupPrompt, setHasCheckedStartupPrompt] = useState(false);
|
||||||
|
|
||||||
// Simple profiles loader without updates check (for use as callback)
|
// Simple profiles loader without updates check (for use as callback)
|
||||||
const loadProfiles = useCallback(async () => {
|
const loadProfiles = useCallback(async () => {
|
||||||
@@ -110,6 +111,9 @@ export default function Home() {
|
|||||||
}, [loadProfilesWithUpdateCheck, checkForUpdates]);
|
}, [loadProfilesWithUpdateCheck, checkForUpdates]);
|
||||||
|
|
||||||
const checkStartupPrompt = async () => {
|
const checkStartupPrompt = async () => {
|
||||||
|
// Only check once during app startup to prevent reopening after dismissing notifications
|
||||||
|
if (hasCheckedStartupPrompt) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shouldShow = await invoke<boolean>(
|
const shouldShow = await invoke<boolean>(
|
||||||
"should_show_settings_on_startup",
|
"should_show_settings_on_startup",
|
||||||
@@ -117,8 +121,10 @@ export default function Home() {
|
|||||||
if (shouldShow) {
|
if (shouldShow) {
|
||||||
setSettingsDialogOpen(true);
|
setSettingsDialogOpen(true);
|
||||||
}
|
}
|
||||||
|
setHasCheckedStartupPrompt(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to check startup prompt:", error);
|
console.error("Failed to check startup prompt:", error);
|
||||||
|
setHasCheckedStartupPrompt(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -394,13 +400,13 @@ export default function Home() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 gap-8 sm:p-12 font-[family-name:var(--font-geist-sans)]">
|
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 gap-8 sm:p-12 font-[family-name:var(--font-geist-sans)] bg-white dark:bg-black">
|
||||||
<main className="flex flex-col gap-8 row-start-2 items-center w-full max-w-3xl">
|
<main className="flex flex-col row-start-2 gap-8 items-center w-full max-w-3xl">
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex justify-between items-center">
|
||||||
<CardTitle>Profiles</CardTitle>
|
<CardTitle>Profiles</CardTitle>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex gap-2 items-center">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -409,9 +415,9 @@ export default function Home() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSettingsDialogOpen(true);
|
setSettingsDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex gap-2 items-center"
|
||||||
>
|
>
|
||||||
<GoGear className="h-4 w-4" />
|
<GoGear className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Settings</TooltipContent>
|
<TooltipContent>Settings</TooltipContent>
|
||||||
@@ -423,9 +429,9 @@ export default function Home() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCreateProfileDialogOpen(true);
|
setCreateProfileDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-2"
|
className="flex gap-2 items-center"
|
||||||
>
|
>
|
||||||
<GoPlus className="h-4 w-4" />
|
<GoPlus className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Create a new profile</TooltipContent>
|
<TooltipContent>Create a new profile</TooltipContent>
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
|||||||
<DialogTitle>Settings</DialogTitle>
|
<DialogTitle>Settings</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="grid gap-6 py-4 overflow-y-auto flex-1 min-h-0">
|
<div className="grid overflow-y-auto flex-1 gap-6 py-4 min-h-0">
|
||||||
{/* Appearance Section */}
|
{/* Appearance Section */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Label className="text-base font-medium">Appearance</Label>
|
<Label className="text-base font-medium">Appearance</Label>
|
||||||
@@ -172,7 +172,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
|
|||||||
|
|
||||||
{/* Default Browser Section */}
|
{/* Default Browser Section */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex justify-between items-center">
|
||||||
<Label className="text-base font-medium">Default Browser</Label>
|
<Label className="text-base font-medium">Default Browser</Label>
|
||||||
<Badge variant={isDefaultBrowser ? "default" : "secondary"}>
|
<Badge variant={isDefaultBrowser ? "default" : "secondary"}>
|
||||||
{isDefaultBrowser ? "Active" : "Inactive"}
|
{isDefaultBrowser ? "Active" : "Inactive"}
|
||||||
|
|||||||
@@ -15,8 +15,15 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|||||||
"--normal-bg": "var(--popover)",
|
"--normal-bg": "var(--popover)",
|
||||||
"--normal-text": "var(--popover-foreground)",
|
"--normal-text": "var(--popover-foreground)",
|
||||||
"--normal-border": "var(--border)",
|
"--normal-border": "var(--border)",
|
||||||
|
zIndex: 99999,
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
|
toastOptions={{
|
||||||
|
style: {
|
||||||
|
zIndex: 99999,
|
||||||
|
pointerEvents: "auto",
|
||||||
|
},
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,17 +47,17 @@ export function UpdateNotificationComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 p-4 max-w-md bg-background border border-border rounded-lg shadow-lg">
|
<div className="flex flex-col gap-3 p-4 max-w-md rounded-lg border shadow-lg bg-background border-border">
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex gap-2 justify-between items-start">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex gap-2 items-center">
|
||||||
<span className="font-semibold text-foreground">
|
<span className="font-semibold text-foreground">
|
||||||
{browserDisplayName} Update Available
|
{browserDisplayName} Update Available
|
||||||
</span>
|
</span>
|
||||||
<Badge
|
<Badge
|
||||||
variant={notification.is_stable_update ? "default" : "secondary"}
|
variant={notification.is_stable_update ? "default" : "secondary"}
|
||||||
>
|
>
|
||||||
{notification.is_stable_update ? "Stable" : "Beta"}
|
{notification.is_stable_update ? "Stable" : "Nightly"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
@@ -71,20 +71,20 @@ export function UpdateNotificationComponent({
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await onDismiss(notification.id);
|
await onDismiss(notification.id);
|
||||||
}}
|
}}
|
||||||
className="h-6 w-6 p-0 shrink-0"
|
className="p-0 w-6 h-6 shrink-0"
|
||||||
>
|
>
|
||||||
<FaTimes className="h-3 w-3" />
|
<FaTimes className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex gap-2 items-center">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleUpdateClick}
|
onClick={handleUpdateClick}
|
||||||
disabled={isUpdating}
|
disabled={isUpdating}
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-2"
|
className="flex gap-2 items-center"
|
||||||
>
|
>
|
||||||
<FaDownload className="h-3 w-3" />
|
<FaDownload className="w-3 h-3" />
|
||||||
Update
|
Update
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function WindowDragArea() {
|
||||||
|
const [isMacOS, setIsMacOS] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if we're on macOS using user agent detection
|
||||||
|
const checkPlatform = () => {
|
||||||
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
|
setIsMacOS(userAgent.includes("mac"));
|
||||||
|
};
|
||||||
|
|
||||||
|
checkPlatform();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
|
// Only handle left mouse button
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
|
||||||
|
// Start dragging asynchronously
|
||||||
|
const startDrag = async () => {
|
||||||
|
try {
|
||||||
|
const window = getCurrentWindow();
|
||||||
|
await window.startDragging();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to start window dragging:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void startDrag();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only render on macOS
|
||||||
|
if (!isMacOS) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed top-0 right-0 left-0 z-50 h-8 cursor-move"
|
||||||
|
style={{
|
||||||
|
// Ensure it's above all other content
|
||||||
|
zIndex: 9999,
|
||||||
|
// Make it transparent but still capture mouse events
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
// Prevent text selection during drag
|
||||||
|
userSelect: "none",
|
||||||
|
WebkitUserSelect: "none",
|
||||||
|
}}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
// Prevent context menu
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -135,6 +135,10 @@ export function useAppUpdateNotifications() {
|
|||||||
id: "app-update",
|
id: "app-update",
|
||||||
duration: Number.POSITIVE_INFINITY, // Persistent until user action
|
duration: Number.POSITIVE_INFINITY, // Persistent until user action
|
||||||
position: "top-left",
|
position: "top-left",
|
||||||
|
style: {
|
||||||
|
zIndex: 99999, // Ensure app updates appear above dialogs
|
||||||
|
pointerEvents: "auto", // Ensure app updates remain interactive
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@@ -232,8 +232,10 @@ export function useUpdateNotifications(
|
|||||||
id: notification.id,
|
id: notification.id,
|
||||||
duration: Number.POSITIVE_INFINITY, // Persistent until user action
|
duration: Number.POSITIVE_INFINITY, // Persistent until user action
|
||||||
position: "top-right",
|
position: "top-right",
|
||||||
// Remove transparent styling to fix background issue
|
style: {
|
||||||
style: undefined,
|
zIndex: 99999, // Ensure notifications appear above dialogs
|
||||||
|
pointerEvents: "auto", // Ensure notifications remain interactive
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ export function showToast(props: ToastProps & { id?: string }) {
|
|||||||
border: "none",
|
border: "none",
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
zIndex: 99999,
|
||||||
|
pointerEvents: "auto",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (props.type === "error") {
|
} else if (props.type === "error") {
|
||||||
@@ -127,6 +129,8 @@ export function showToast(props: ToastProps & { id?: string }) {
|
|||||||
border: "none",
|
border: "none",
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
zIndex: 99999,
|
||||||
|
pointerEvents: "auto",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -138,6 +142,8 @@ export function showToast(props: ToastProps & { id?: string }) {
|
|||||||
border: "none",
|
border: "none",
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
zIndex: 99999,
|
||||||
|
pointerEvents: "auto",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,3 +123,23 @@
|
|||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure Sonner toasts appear above all dialogs and remain interactive */
|
||||||
|
.toaster,
|
||||||
|
[data-sonner-toaster] {
|
||||||
|
z-index: 99999 !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-sonner-toast] {
|
||||||
|
z-index: 99999 !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure toast buttons and interactive elements work */
|
||||||
|
[data-sonner-toast] button,
|
||||||
|
[data-sonner-toast] [role="button"],
|
||||||
|
[data-sonner-toast] input,
|
||||||
|
[data-sonner-toast] select {
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
darkMode: "class",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
black: "#000000",
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
dark: "#000000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user