Custom window controls (#46)
This commit is contained in:
@@ -11,6 +11,8 @@ import { cn } from "@/lib/utils";
|
|||||||
import { useDeepLink } from "@/contexts/DeepLinkContext";
|
import { useDeepLink } from "@/contexts/DeepLinkContext";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { DyadProSuccessDialog } from "@/components/DyadProSuccessDialog";
|
import { DyadProSuccessDialog } from "@/components/DyadProSuccessDialog";
|
||||||
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
import { IpcClient } from "@/ipc/ipc_client";
|
||||||
|
|
||||||
export const TitleBar = () => {
|
export const TitleBar = () => {
|
||||||
const [selectedAppId] = useAtom(selectedAppIdAtom);
|
const [selectedAppId] = useAtom(selectedAppIdAtom);
|
||||||
@@ -18,6 +20,21 @@ export const TitleBar = () => {
|
|||||||
const { navigate } = useRouter();
|
const { navigate } = useRouter();
|
||||||
const { settings, refreshSettings } = useSettings();
|
const { settings, refreshSettings } = useSettings();
|
||||||
const [isSuccessDialogOpen, setIsSuccessDialogOpen] = useState(false);
|
const [isSuccessDialogOpen, setIsSuccessDialogOpen] = useState(false);
|
||||||
|
const [showWindowControls, setShowWindowControls] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if we're running on Windows
|
||||||
|
const checkPlatform = async () => {
|
||||||
|
try {
|
||||||
|
const platform = await IpcClient.getInstance().getSystemPlatform();
|
||||||
|
setShowWindowControls(platform !== "darwin");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get platform info:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkPlatform();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const showDyadProSuccessDialog = () => {
|
const showDyadProSuccessDialog = () => {
|
||||||
setIsSuccessDialogOpen(true);
|
setIsSuccessDialogOpen(true);
|
||||||
@@ -82,6 +99,7 @@ export const TitleBar = () => {
|
|||||||
{isDyadProEnabled ? "Dyad Pro" : "Dyad Pro (disabled)"}
|
{isDyadProEnabled ? "Dyad Pro" : "Dyad Pro (disabled)"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{showWindowControls && <WindowsControls />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DyadProSuccessDialog
|
<DyadProSuccessDialog
|
||||||
@@ -91,3 +109,84 @@ export const TitleBar = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function WindowsControls() {
|
||||||
|
const { isDarkMode } = useTheme();
|
||||||
|
const ipcClient = IpcClient.getInstance();
|
||||||
|
|
||||||
|
const minimizeWindow = () => {
|
||||||
|
ipcClient.minimizeWindow();
|
||||||
|
};
|
||||||
|
|
||||||
|
const maximizeWindow = () => {
|
||||||
|
ipcClient.maximizeWindow();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeWindow = () => {
|
||||||
|
ipcClient.closeWindow();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ml-auto flex no-app-region-drag">
|
||||||
|
<button
|
||||||
|
className="w-12 h-11 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
||||||
|
onClick={minimizeWindow}
|
||||||
|
aria-label="Minimize"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="12"
|
||||||
|
height="1"
|
||||||
|
viewBox="0 0 12 1"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
width="12"
|
||||||
|
height="1"
|
||||||
|
fill={isDarkMode ? "#ffffff" : "#000000"}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="w-12 h-11 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
||||||
|
onClick={maximizeWindow}
|
||||||
|
aria-label="Maximize"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
x="0.5"
|
||||||
|
y="0.5"
|
||||||
|
width="11"
|
||||||
|
height="11"
|
||||||
|
stroke={isDarkMode ? "#ffffff" : "#000000"}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="w-12 h-11 flex items-center justify-center hover:bg-red-500 transition-colors"
|
||||||
|
onClick={closeWindow}
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M1 1L11 11M1 11L11 1"
|
||||||
|
stroke={isDarkMode ? "#ffffff" : "#000000"}
|
||||||
|
strokeWidth="1.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
53
src/ipc/handlers/window_handlers.ts
Normal file
53
src/ipc/handlers/window_handlers.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { BrowserWindow, ipcMain } from "electron";
|
||||||
|
import log from "electron-log";
|
||||||
|
import { platform } from "os";
|
||||||
|
|
||||||
|
const logger = log.scope("window-handlers");
|
||||||
|
|
||||||
|
// Handler for minimizing the window
|
||||||
|
const handleMinimize = (event: Electron.IpcMainInvokeEvent) => {
|
||||||
|
const window = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
if (!window) {
|
||||||
|
logger.error("Failed to get BrowserWindow instance for minimize command");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.minimize();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handler for maximizing/restoring the window
|
||||||
|
const handleMaximize = (event: Electron.IpcMainInvokeEvent) => {
|
||||||
|
const window = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
if (!window) {
|
||||||
|
logger.error("Failed to get BrowserWindow instance for maximize command");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.isMaximized()) {
|
||||||
|
window.restore();
|
||||||
|
} else {
|
||||||
|
window.maximize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handler for closing the window
|
||||||
|
const handleClose = (event: Electron.IpcMainInvokeEvent) => {
|
||||||
|
const window = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
if (!window) {
|
||||||
|
logger.error("Failed to get BrowserWindow instance for close command");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handler to get the current system platform
|
||||||
|
const handleGetSystemPlatform = () => {
|
||||||
|
return platform();
|
||||||
|
};
|
||||||
|
|
||||||
|
export function registerWindowHandlers() {
|
||||||
|
logger.debug("Registering window control handlers");
|
||||||
|
ipcMain.handle("window:minimize", handleMinimize);
|
||||||
|
ipcMain.handle("window:maximize", handleMaximize);
|
||||||
|
ipcMain.handle("window:close", handleClose);
|
||||||
|
ipcMain.handle("window:get-platform", handleGetSystemPlatform);
|
||||||
|
}
|
||||||
@@ -776,4 +776,45 @@ export class IpcClient {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Window control methods
|
||||||
|
public async minimizeWindow(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.ipcRenderer.invoke("window:minimize");
|
||||||
|
} catch (error) {
|
||||||
|
showError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async maximizeWindow(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.ipcRenderer.invoke("window:maximize");
|
||||||
|
} catch (error) {
|
||||||
|
showError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async closeWindow(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.ipcRenderer.invoke("window:close");
|
||||||
|
} catch (error) {
|
||||||
|
showError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get system platform (win32, darwin, linux)
|
||||||
|
public async getSystemPlatform(): Promise<string> {
|
||||||
|
try {
|
||||||
|
const platform = await this.ipcRenderer.invoke("window:get-platform");
|
||||||
|
return platform;
|
||||||
|
} catch (error) {
|
||||||
|
showError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- End window control methods ---
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { registerDebugHandlers } from "./handlers/debug_handlers";
|
|||||||
import { registerSupabaseHandlers } from "./handlers/supabase_handlers";
|
import { registerSupabaseHandlers } from "./handlers/supabase_handlers";
|
||||||
import { registerLocalModelHandlers } from "./handlers/local_model_handlers";
|
import { registerLocalModelHandlers } from "./handlers/local_model_handlers";
|
||||||
import { registerTokenCountHandlers } from "./handlers/token_count_handlers";
|
import { registerTokenCountHandlers } from "./handlers/token_count_handlers";
|
||||||
|
import { registerWindowHandlers } from "./handlers/window_handlers";
|
||||||
|
|
||||||
export function registerIpcHandlers() {
|
export function registerIpcHandlers() {
|
||||||
// Register all IPC handlers by category
|
// Register all IPC handlers by category
|
||||||
@@ -27,4 +28,5 @@ export function registerIpcHandlers() {
|
|||||||
registerSupabaseHandlers();
|
registerSupabaseHandlers();
|
||||||
registerLocalModelHandlers();
|
registerLocalModelHandlers();
|
||||||
registerTokenCountHandlers();
|
registerTokenCountHandlers();
|
||||||
|
registerWindowHandlers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const createWindow = () => {
|
|||||||
width: process.env.NODE_ENV === "development" ? 1280 : 960,
|
width: process.env.NODE_ENV === "development" ? 1280 : 960,
|
||||||
height: 700,
|
height: 700,
|
||||||
titleBarStyle: "hidden",
|
titleBarStyle: "hidden",
|
||||||
titleBarOverlay: true,
|
titleBarOverlay: false,
|
||||||
trafficLightPosition: {
|
trafficLightPosition: {
|
||||||
x: 10,
|
x: 10,
|
||||||
y: 8,
|
y: 8,
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ const validInvokeChannels = [
|
|||||||
"supabase:set-app-project",
|
"supabase:set-app-project",
|
||||||
"supabase:unset-app-project",
|
"supabase:unset-app-project",
|
||||||
"local-models:list",
|
"local-models:list",
|
||||||
|
"window:minimize",
|
||||||
|
"window:maximize",
|
||||||
|
"window:close",
|
||||||
|
"window:get-platform",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// Add valid receive channels
|
// Add valid receive channels
|
||||||
|
|||||||
Reference in New Issue
Block a user