mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 21:30:17 +08:00
64c068415e
Linux users reported the window UI (including native title bar buttons) couldn't receive clicks until manually maximizing and restoring the window. Root causes: (1) Tauri webview did not acquire focus on startup so first clicks were consumed by X11/Wayland click-to-activate (Tauri #10746, wry #637); (2) GTK surface input region failed to renegotiate on the visible:false + show() path under some WebKitGTK/compositor combinations. - Add linux_fix::nudge_main_window helper that performs set_focus plus a ±1px no-op resize after window show, with a 500ms reconciliation readback to compensate for dropped resize requests on slow compositors. - Wire the helper into every window re-show path: normal startup, deeplink, single_instance, tray show_main, and lightweight exit. - Set WEBKIT_DISABLE_COMPOSITING_MODE=1 at startup to avoid resize crashes and Wayland surface negotiation issues. - Remove data-tauri-drag-region on Linux from App.tsx header and the shared FullScreenPanel (used by all provider/MCP/workspace forms) to avoid Tauri #13440 in Wayland sessions. Extract drag-region constants to src/lib/platform.ts for reuse. All Rust changes are gated by #[cfg(target_os = "linux")]; frontend changes preserve macOS/Windows behavior via runtime isLinux() checks. Known limitation: tiling Wayland compositors ignore set_size, so GDK_BACKEND=x11 remains the user-side workaround.
159 lines
4.6 KiB
TypeScript
159 lines
4.6 KiB
TypeScript
import React from "react";
|
||
import { createPortal } from "react-dom";
|
||
import { motion, AnimatePresence } from "framer-motion";
|
||
import { ArrowLeft } from "lucide-react";
|
||
import { Button } from "@/components/ui/button";
|
||
import {
|
||
isWindows,
|
||
isLinux,
|
||
DRAG_REGION_ATTR,
|
||
DRAG_REGION_STYLE,
|
||
} from "@/lib/platform";
|
||
import { isTextEditableTarget } from "@/utils/domUtils";
|
||
|
||
interface FullScreenPanelProps {
|
||
isOpen: boolean;
|
||
title: string;
|
||
onClose: () => void;
|
||
children: React.ReactNode;
|
||
footer?: React.ReactNode;
|
||
}
|
||
|
||
const DRAG_BAR_HEIGHT = isWindows() || isLinux() ? 0 : 28; // px - match App.tsx
|
||
const HEADER_HEIGHT = 64; // px - match App.tsx
|
||
|
||
/**
|
||
* Reusable full-screen panel component
|
||
* Handles portal rendering, header with back button, and footer
|
||
* Uses solid theme colors without transparency
|
||
*/
|
||
export const FullScreenPanel: React.FC<FullScreenPanelProps> = ({
|
||
isOpen,
|
||
title,
|
||
onClose,
|
||
children,
|
||
footer,
|
||
}) => {
|
||
React.useEffect(() => {
|
||
if (isOpen) {
|
||
document.body.style.overflow = "hidden";
|
||
}
|
||
return () => {
|
||
document.body.style.overflow = "";
|
||
};
|
||
}, [isOpen]);
|
||
|
||
// ESC 键关闭面板
|
||
const onCloseRef = React.useRef(onClose);
|
||
|
||
React.useEffect(() => {
|
||
onCloseRef.current = onClose;
|
||
}, [onClose]);
|
||
|
||
React.useEffect(() => {
|
||
if (!isOpen) return;
|
||
|
||
const handleKeyDown = (event: KeyboardEvent) => {
|
||
if (event.key === "Escape") {
|
||
// 子组件(例如 Radix 的 Select/Dialog/Dropdown)如果已经消费了 ESC,就不要再关闭整个面板
|
||
if (event.defaultPrevented) {
|
||
return;
|
||
}
|
||
|
||
if (isTextEditableTarget(event.target)) {
|
||
return; // 让输入框自己处理 ESC(比如清空、失焦等)
|
||
}
|
||
|
||
event.stopPropagation(); // 阻止事件继续冒泡到 window,避免触发 App.tsx 的全局监听
|
||
onCloseRef.current();
|
||
}
|
||
};
|
||
|
||
// 使用冒泡阶段监听,让子组件(如 Radix UI)优先处理 ESC
|
||
window.addEventListener("keydown", handleKeyDown, false);
|
||
return () => {
|
||
window.removeEventListener("keydown", handleKeyDown, false);
|
||
};
|
||
}, [isOpen]);
|
||
|
||
return createPortal(
|
||
<AnimatePresence>
|
||
{isOpen && (
|
||
<motion.div
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
transition={{ duration: 0.2 }}
|
||
className="fixed inset-0 z-[60] flex flex-col"
|
||
style={{ backgroundColor: "hsl(var(--background))" }}
|
||
>
|
||
{/* Drag region - match App.tsx. Linux 上 DRAG_BAR_HEIGHT=0,
|
||
直接跳过整个元素;macOS 保留 28px 拖拽占位。 */}
|
||
{DRAG_BAR_HEIGHT > 0 && (
|
||
<div
|
||
data-tauri-drag-region
|
||
style={
|
||
{
|
||
WebkitAppRegion: "drag",
|
||
height: DRAG_BAR_HEIGHT,
|
||
} as React.CSSProperties
|
||
}
|
||
/>
|
||
)}
|
||
|
||
{/* Header - match App.tsx */}
|
||
<div
|
||
className="flex-shrink-0 flex items-center"
|
||
{...DRAG_REGION_ATTR}
|
||
style={
|
||
{
|
||
...DRAG_REGION_STYLE,
|
||
backgroundColor: "hsl(var(--background))",
|
||
height: HEADER_HEIGHT,
|
||
} as React.CSSProperties
|
||
}
|
||
>
|
||
<div
|
||
className="px-6 w-full flex items-center gap-4"
|
||
{...DRAG_REGION_ATTR}
|
||
style={{ ...DRAG_REGION_STYLE } as React.CSSProperties}
|
||
>
|
||
<Button
|
||
type="button"
|
||
variant="outline"
|
||
size="icon"
|
||
onClick={onClose}
|
||
className="rounded-lg select-none"
|
||
style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}
|
||
>
|
||
<ArrowLeft className="h-4 w-4" />
|
||
</Button>
|
||
<h2 className="text-lg font-semibold text-foreground select-none">
|
||
{title}
|
||
</h2>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Content */}
|
||
<div className="flex-1 overflow-y-auto scroll-overlay">
|
||
<div className="px-6 py-6 space-y-6 w-full">{children}</div>
|
||
</div>
|
||
|
||
{/* Footer */}
|
||
{footer && (
|
||
<div
|
||
className="flex-shrink-0 py-4 border-t border-border-default"
|
||
style={{ backgroundColor: "hsl(var(--background))" }}
|
||
>
|
||
<div className="px-6 flex items-center justify-end gap-3">
|
||
{footer}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>,
|
||
document.body,
|
||
);
|
||
};
|