From c3680a8e25ead4e3432819ec83c12837e08ded4d Mon Sep 17 00:00:00 2001 From: bflatastic Date: Mon, 18 Aug 2025 14:48:03 +0700 Subject: [PATCH] custom tauri title bar --- .../artcraft/capabilities/default.json | 9 +- crates/desktop/artcraft/tauri.conf.json | 1 + crates/desktop/artcraft/tauri2.conf.json | 3 +- .../signaled/TopBar/AuthButtons.tsx | 2 +- .../src/components/signaled/TopBar/TopBar.tsx | 96 ++++++++++++++++--- .../app/src/pages/PageImage/TextToImage.tsx | 2 +- .../app/src/pages/PageVideo/ImageToVideo.tsx | 2 +- .../apps/editor3d/app/src/styles/base.css | 17 ++++ .../src/lib/menu-icon-selector.tsx | 2 +- .../provider-setup-modal/tsconfig.json | 9 +- .../provider-setup-modal/tsconfig.lib.json | 9 +- frontend/libs/tauri-events/tsconfig.json | 12 +++ frontend/libs/tauri-events/tsconfig.lib.json | 14 +++ .../libs/tauri-utils/src/lib/tauri-utils.tsx | 14 ++- .../libs/tauri-utils/src/lib/usePlatform.ts | 22 +++++ .../tauri-utils/src/lib/useWindowControls.ts | 94 ++++++++++++++++++ 16 files changed, 271 insertions(+), 37 deletions(-) create mode 100644 frontend/libs/tauri-utils/src/lib/usePlatform.ts create mode 100644 frontend/libs/tauri-utils/src/lib/useWindowControls.ts diff --git a/crates/desktop/artcraft/capabilities/default.json b/crates/desktop/artcraft/capabilities/default.json index 3192e556c2..3f400e29e9 100644 --- a/crates/desktop/artcraft/capabilities/default.json +++ b/crates/desktop/artcraft/capabilities/default.json @@ -2,12 +2,13 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "enables the default permissions", - "windows": [ - "main" - ], + "windows": ["main"], "permissions": [ "core:default", "log:default", + "core:window:allow-minimize", + "core:window:allow-toggle-maximize", + "core:window:allow-close", "upload:allow-download", "upload:default", "http:allow-fetch", @@ -24,4 +25,4 @@ }, "dialog:default" ] -} \ No newline at end of file +} diff --git a/crates/desktop/artcraft/tauri.conf.json b/crates/desktop/artcraft/tauri.conf.json index 38784a89ca..cf8026231e 100644 --- a/crates/desktop/artcraft/tauri.conf.json +++ b/crates/desktop/artcraft/tauri.conf.json @@ -17,6 +17,7 @@ "height": 1300, "resizable": true, "fullscreen": false, + "decorations": false, "dragDropEnabled": false } ], diff --git a/crates/desktop/artcraft/tauri2.conf.json b/crates/desktop/artcraft/tauri2.conf.json index dd20e2f5fa..ead7bdabe4 100644 --- a/crates/desktop/artcraft/tauri2.conf.json +++ b/crates/desktop/artcraft/tauri2.conf.json @@ -16,7 +16,8 @@ "width": 2400, "height": 1300, "resizable": true, - "fullscreen": false + "fullscreen": false, + "decorations": false } ], "security": { diff --git a/frontend/apps/editor3d/app/src/components/signaled/TopBar/AuthButtons.tsx b/frontend/apps/editor3d/app/src/components/signaled/TopBar/AuthButtons.tsx index eeb653d268..68e7b00472 100644 --- a/frontend/apps/editor3d/app/src/components/signaled/TopBar/AuthButtons.tsx +++ b/frontend/apps/editor3d/app/src/components/signaled/TopBar/AuthButtons.tsx @@ -21,7 +21,7 @@ export const AuthButtons = ({ } else { return ( <> -
+
|
+ )} +
ArtCraft { activeMenu={tabStore.activeTabId} disabled={disableTabSwitcher()} onMenuChange={(tabId) => { - gtagEvent("switch_tab", { "tab": tabId }); + gtagEvent("switch_tab", { tab: tabId }); // Prevent a second input if the switcher is throttled. if (switcherThrottle.current) { @@ -170,11 +208,13 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => { setDisableSwitcher(false); }, SWITCHER_THROTTLE_TIME); }} - className="w-fit" + className="no-drag w-fit" />
-
+
{tabStore.activeTabId === "3D" ? ( ) : ( @@ -188,8 +228,8 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => { )}
-
-
+
+
{/* - Uncomment for pricing modal - BFlat */} {/*
- +
+ +
+ {isDesktop && platform !== "macos" && ( +
+ + + +
+ )}
diff --git a/frontend/apps/editor3d/app/src/pages/PageImage/TextToImage.tsx b/frontend/apps/editor3d/app/src/pages/PageImage/TextToImage.tsx index 406b6cd798..4bca7285e4 100644 --- a/frontend/apps/editor3d/app/src/pages/PageImage/TextToImage.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageImage/TextToImage.tsx @@ -37,7 +37,7 @@ const TextToImage = ({ imageMediaId, imageUrl }: TextToImageProps) => { return (
diff --git a/frontend/apps/editor3d/app/src/pages/PageVideo/ImageToVideo.tsx b/frontend/apps/editor3d/app/src/pages/PageVideo/ImageToVideo.tsx index 7882567b1d..23595dfabf 100644 --- a/frontend/apps/editor3d/app/src/pages/PageVideo/ImageToVideo.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageVideo/ImageToVideo.tsx @@ -37,7 +37,7 @@ const ImageToVideo = ({ imageMediaId, imageUrl }: ImageToVideoProps) => { return (
diff --git a/frontend/apps/editor3d/app/src/styles/base.css b/frontend/apps/editor3d/app/src/styles/base.css index f30a6ccd2a..05d4cd48e8 100644 --- a/frontend/apps/editor3d/app/src/styles/base.css +++ b/frontend/apps/editor3d/app/src/styles/base.css @@ -46,6 +46,23 @@ body { user-select: none; } +/* Tauri custom titlebar drag regions */ +[data-tauri-drag-region] { + -webkit-app-region: drag; + app-region: drag; +} + +.tauri-no-drag, +[data-tauri-drag-region] .no-drag, +[data-tauri-drag-region] button, +[data-tauri-drag-region] a, +[data-tauri-drag-region] input, +[data-tauri-drag-region] textarea, +[data-tauri-drag-region] select { + -webkit-app-region: no-drag; + app-region: no-drag; +} + img { user-select: none; pointer-events: none; diff --git a/frontend/libs/components/menu-icon-selector/src/lib/menu-icon-selector.tsx b/frontend/libs/components/menu-icon-selector/src/lib/menu-icon-selector.tsx index b02d4f82a1..227013d0a1 100644 --- a/frontend/libs/components/menu-icon-selector/src/lib/menu-icon-selector.tsx +++ b/frontend/libs/components/menu-icon-selector/src/lib/menu-icon-selector.tsx @@ -84,7 +84,7 @@ export const MenuIconSelector: React.FC = ({ >
setHoveredIndex(-1)} > diff --git a/frontend/libs/components/provider-setup-modal/tsconfig.json b/frontend/libs/components/provider-setup-modal/tsconfig.json index 12b1e10ef8..b2d0f690d2 100644 --- a/frontend/libs/components/provider-setup-modal/tsconfig.json +++ b/frontend/libs/components/provider-setup-modal/tsconfig.json @@ -3,19 +3,16 @@ "include": [], "references": [ { - "path": "../button" - }, - { - "path": "../modal" + "path": "../../common" }, { "path": "../../tauri-events" }, { - "path": "../../tauri-api" + "path": "../button" }, { - "path": "../../common" + "path": "../modal" }, { "path": "./tsconfig.lib.json" diff --git a/frontend/libs/components/provider-setup-modal/tsconfig.lib.json b/frontend/libs/components/provider-setup-modal/tsconfig.lib.json index 4b429f449b..f03ada54f3 100644 --- a/frontend/libs/components/provider-setup-modal/tsconfig.lib.json +++ b/frontend/libs/components/provider-setup-modal/tsconfig.lib.json @@ -44,19 +44,16 @@ ], "references": [ { - "path": "../button/tsconfig.lib.json" - }, - { - "path": "../modal/tsconfig.lib.json" + "path": "../../common/tsconfig.lib.json" }, { "path": "../../tauri-events/tsconfig.lib.json" }, { - "path": "../../tauri-api/tsconfig.lib.json" + "path": "../button/tsconfig.lib.json" }, { - "path": "../../common/tsconfig.lib.json" + "path": "../modal/tsconfig.lib.json" } ] } diff --git a/frontend/libs/tauri-events/tsconfig.json b/frontend/libs/tauri-events/tsconfig.json index 8c5fc025ff..11c74cf946 100644 --- a/frontend/libs/tauri-events/tsconfig.json +++ b/frontend/libs/tauri-events/tsconfig.json @@ -2,6 +2,18 @@ "files": [], "include": [], "references": [ + { + "path": "../components/toaster" + }, + { + "path": "../tauri-api" + }, + { + "path": "../soundboard" + }, + { + "path": "../common" + }, { "path": "./tsconfig.lib.json" }, diff --git a/frontend/libs/tauri-events/tsconfig.lib.json b/frontend/libs/tauri-events/tsconfig.lib.json index ea31c66850..cdcccc7004 100644 --- a/frontend/libs/tauri-events/tsconfig.lib.json +++ b/frontend/libs/tauri-events/tsconfig.lib.json @@ -41,5 +41,19 @@ "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx" + ], + "references": [ + { + "path": "../components/toaster/tsconfig.lib.json" + }, + { + "path": "../tauri-api/tsconfig.lib.json" + }, + { + "path": "../soundboard/tsconfig.lib.json" + }, + { + "path": "../common/tsconfig.lib.json" + } ] } diff --git a/frontend/libs/tauri-utils/src/lib/tauri-utils.tsx b/frontend/libs/tauri-utils/src/lib/tauri-utils.tsx index 8354df73dd..bf139b8d4b 100644 --- a/frontend/libs/tauri-utils/src/lib/tauri-utils.tsx +++ b/frontend/libs/tauri-utils/src/lib/tauri-utils.tsx @@ -1,3 +1,11 @@ -export * from './api'; -export { FetchProxy } from './FetchProxy'; -export { IsDesktopApp } from './IsDesktopApp'; +export * from "./api"; +export { FetchProxy } from "./FetchProxy"; +export { IsDesktopApp } from "./IsDesktopApp"; +export { + useTauriWindowControls, + minimizeCurrentWindow, + toggleMaximizeCurrentWindow, + closeCurrentWindow, + isCurrentWindowMaximized, +} from "./useWindowControls"; +export { useTauriPlatform } from "./usePlatform"; diff --git a/frontend/libs/tauri-utils/src/lib/usePlatform.ts b/frontend/libs/tauri-utils/src/lib/usePlatform.ts new file mode 100644 index 0000000000..d6716e6e51 --- /dev/null +++ b/frontend/libs/tauri-utils/src/lib/usePlatform.ts @@ -0,0 +1,22 @@ +import { useEffect, useState } from "react"; +import { IsDesktopApp } from "./IsDesktopApp"; + +export type TauriPlatform = "windows" | "macos" | "linux"; + +export function useTauriPlatform(): TauriPlatform | undefined { + const [platform, setPlatform] = useState( + undefined + ); + + useEffect(() => { + if (!IsDesktopApp()) return; + if (typeof navigator !== "undefined") { + const ua = navigator.userAgent || ""; + if (/Macintosh|Mac OS X/i.test(ua)) setPlatform("macos"); + else if (/Windows/i.test(ua)) setPlatform("windows"); + else setPlatform("linux"); + } + }, []); + + return platform; +} diff --git a/frontend/libs/tauri-utils/src/lib/useWindowControls.ts b/frontend/libs/tauri-utils/src/lib/useWindowControls.ts new file mode 100644 index 0000000000..8c2da336d9 --- /dev/null +++ b/frontend/libs/tauri-utils/src/lib/useWindowControls.ts @@ -0,0 +1,94 @@ +import { useEffect, useState, useCallback } from "react"; +import { IsDesktopApp } from "./IsDesktopApp"; + +async function getCurrentWindowSafe() { + const { getCurrentWindow } = await import("@tauri-apps/api/window"); + return getCurrentWindow(); +} + +export function useTauriWindowControls() { + const isDesktop = IsDesktopApp(); + const [isMaximized, setIsMaximized] = useState(false); + + useEffect(() => { + if (!isDesktop) return; + let unlistenMax: (() => void) | undefined; + let unlistenUnmax: (() => void) | undefined; + let unlistenResize: (() => void) | undefined; + + (async () => { + try { + const win = await getCurrentWindowSafe(); + setIsMaximized(await win.isMaximized()); + } catch (err) { + // ignore + } + + try { + const { listen } = await import("@tauri-apps/api/event"); + unlistenMax = await listen("tauri://maximize", () => + setIsMaximized(true) + ); + unlistenUnmax = await listen("tauri://unmaximize", () => + setIsMaximized(false) + ); + unlistenResize = await listen("tauri://resize", async () => { + try { + const win = await getCurrentWindowSafe(); + setIsMaximized(await win.isMaximized()); + } catch (err) { + // ignore + } + }); + } catch (err) { + // ignore + } + })(); + + return () => { + if (unlistenMax) unlistenMax(); + if (unlistenUnmax) unlistenUnmax(); + if (unlistenResize) unlistenResize(); + }; + }, [isDesktop]); + + const minimize = useCallback(async () => { + if (!isDesktop) return; + const win = await getCurrentWindowSafe(); + await win.minimize(); + }, [isDesktop]); + + const toggleMaximize = useCallback(async () => { + if (!isDesktop) return; + const win = await getCurrentWindowSafe(); + await win.toggleMaximize(); + }, [isDesktop]); + + const close = useCallback(async () => { + if (!isDesktop) return; + const win = await getCurrentWindowSafe(); + await win.close(); + }, [isDesktop]); + + return { isDesktop, isMaximized, minimize, toggleMaximize, close }; +} + +export async function minimizeCurrentWindow() { + const win = await getCurrentWindowSafe(); + return win.minimize(); +} + +export async function toggleMaximizeCurrentWindow() { + const win = await getCurrentWindowSafe(); + return win.toggleMaximize(); +} + +export async function closeCurrentWindow() { + const win = await getCurrentWindowSafe(); + return win.close(); +} + +export async function isCurrentWindowMaximized(): Promise { + const win = await getCurrentWindowSafe(); + return win.isMaximized(); +}