Add `packages/app/src/constants/platform.ts` with canonical gates (isWeb, isNative, getIsElectron) and refactor all ~100 ad-hoc Platform.OS checks across 69 files to use shared imports. Fix sidebar hover crash on native (onPointerEnter → onHoverIn), fix tooltip to use breakpoint instead of platform detection, make sidebar action buttons always visible on native where hover doesn't work, and document the platform gating decision matrix in CLAUDE.md.
128 lines
3.5 KiB
TypeScript
128 lines
3.5 KiB
TypeScript
import { useEffect, useMemo, useState } from "react";
|
|
import {
|
|
getIsElectronRuntimeMac,
|
|
getIsElectronRuntime,
|
|
DESKTOP_TRAFFIC_LIGHT_WIDTH,
|
|
DESKTOP_TRAFFIC_LIGHT_HEIGHT,
|
|
DESKTOP_WINDOW_CONTROLS_WIDTH,
|
|
DESKTOP_WINDOW_CONTROLS_HEIGHT,
|
|
} from "@/constants/layout";
|
|
import { getDesktopWindow } from "@/desktop/electron/window";
|
|
import { usePanelStore } from "@/stores/panel-store";
|
|
import { isNative } from "@/constants/platform";
|
|
|
|
type RawWindowControlsPadding = {
|
|
left: number;
|
|
right: number;
|
|
top: number;
|
|
};
|
|
|
|
type WindowControlsPaddingRole = "sidebar" | "header" | "tabRow" | "explorerSidebar";
|
|
|
|
function useRawWindowControlsPadding(): RawWindowControlsPadding {
|
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (isNative || !getIsElectronRuntime()) return;
|
|
|
|
let disposed = false;
|
|
let cleanup: (() => void) | undefined;
|
|
let didCleanup = false;
|
|
|
|
function runCleanup() {
|
|
if (!cleanup || didCleanup) return;
|
|
didCleanup = true;
|
|
try {
|
|
void Promise.resolve(cleanup()).catch((error) => {
|
|
console.warn("[DesktopWindow] Failed to remove resize listener", error);
|
|
});
|
|
} catch (error) {
|
|
console.warn("[DesktopWindow] Failed to remove resize listener", error);
|
|
}
|
|
}
|
|
|
|
async function setup() {
|
|
const win = getDesktopWindow();
|
|
if (!win) return;
|
|
|
|
const fullscreen = typeof win.isFullscreen === "function" ? await win.isFullscreen() : false;
|
|
if (disposed) return;
|
|
setIsFullscreen(fullscreen);
|
|
|
|
if (typeof win.onResized !== "function") {
|
|
return;
|
|
}
|
|
|
|
const unlisten = await win.onResized(async () => {
|
|
if (disposed) return;
|
|
const fs = typeof win.isFullscreen === "function" ? await win.isFullscreen() : false;
|
|
if (disposed) return;
|
|
setIsFullscreen(fs);
|
|
});
|
|
|
|
cleanup = unlisten;
|
|
if (disposed) {
|
|
runCleanup();
|
|
}
|
|
}
|
|
|
|
void setup();
|
|
|
|
return () => {
|
|
disposed = true;
|
|
runCleanup();
|
|
};
|
|
}, []);
|
|
|
|
return useMemo((): RawWindowControlsPadding => {
|
|
if (!getIsElectronRuntime() || isFullscreen) {
|
|
return { left: 0, right: 0, top: 0 };
|
|
}
|
|
|
|
if (getIsElectronRuntimeMac()) {
|
|
return {
|
|
left: DESKTOP_TRAFFIC_LIGHT_WIDTH,
|
|
right: 0,
|
|
top: DESKTOP_TRAFFIC_LIGHT_HEIGHT,
|
|
};
|
|
}
|
|
|
|
return {
|
|
left: 0,
|
|
right: DESKTOP_WINDOW_CONTROLS_WIDTH,
|
|
top: DESKTOP_WINDOW_CONTROLS_HEIGHT,
|
|
};
|
|
}, [isFullscreen]);
|
|
}
|
|
|
|
export function useWindowControlsPadding(role: WindowControlsPaddingRole): {
|
|
left: number;
|
|
right: number;
|
|
top: number;
|
|
} {
|
|
const sidebarOpen = usePanelStore((state) => state.desktop.agentListOpen);
|
|
const explorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
|
|
const focusModeEnabled = usePanelStore((state) => state.desktop.focusModeEnabled);
|
|
const rawPadding = useRawWindowControlsPadding();
|
|
const sidebarClosed = !sidebarOpen;
|
|
|
|
let left = 0;
|
|
let right = 0;
|
|
let top = 0;
|
|
|
|
if (role === "sidebar") {
|
|
left = rawPadding.left;
|
|
top = rawPadding.top;
|
|
} else if (role === "header") {
|
|
left = sidebarClosed ? rawPadding.left : 0;
|
|
right = explorerOpen ? 0 : rawPadding.right;
|
|
} else if (role === "tabRow") {
|
|
left = sidebarClosed && focusModeEnabled ? rawPadding.left : 0;
|
|
right = focusModeEnabled && !explorerOpen ? rawPadding.right : 0;
|
|
} else if (role === "explorerSidebar") {
|
|
right = rawPadding.right;
|
|
}
|
|
|
|
return useMemo(() => ({ left, right, top }), [left, right, top]);
|
|
}
|