Compare commits

...
Author SHA1 Message Date
Bombay 8997666ea0 for mac use regular titlebar 2025-08-19 11:03:31 +07:00
bflatastic c3680a8e25 custom tauri title bar 2025-08-18 14:48:03 +07:00
20 changed files with 280 additions and 37 deletions
@@ -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"
]
}
}
@@ -3,3 +3,7 @@
frontendDist = "../../../frontend/apps/editor3d/dist"
beforeDevCommand = "npx nx run editor3d:dev"
devUrl = "http://localhost:5173"
[[app.windows]]
decorations = true
title = "ArtCraft"
@@ -4,5 +4,16 @@
"beforeDevCommand": "echo 'no dev'",
"devUrl": "http://localhost:5173",
"beforeBuildCommand": "npx nx run editor3d:build"
},
"app": {
"windows": [
{
"title": "ArtCraft",
"resizable": true,
"fullscreen": false,
"dragDropEnabled": false,
"decorations": false
}
]
}
}
@@ -4,3 +4,7 @@ frontendDist = "../../../frontend/apps/editor3d/dist"
#beforeDevCommand = "nx run editor3d:dev"
beforeDevCommand = "echo 'no dev'"
devUrl = "http://localhost:5173"
[[app.windows]]
decorations = true
title = "ArtCraft"
@@ -4,5 +4,16 @@
"beforeDevCommand": "echo 'no dev'",
"devUrl": "http://localhost:5173",
"beforeBuildCommand": "npx nx run editor3d:build"
},
"app": {
"windows": [
{
"title": "ArtCraft",
"resizable": true,
"fullscreen": false,
"dragDropEnabled": false,
"decorations": false
}
]
}
}
+1
View File
@@ -17,6 +17,7 @@
"height": 1300,
"resizable": true,
"fullscreen": false,
"decorations": false,
"dragDropEnabled": false
}
],
+2 -1
View File
@@ -16,7 +16,8 @@
"width": 2400,
"height": 1300,
"resizable": true,
"fullscreen": false
"fullscreen": false,
"decorations": false
}
],
"security": {
@@ -21,7 +21,7 @@ export const AuthButtons = ({
} else {
return (
<>
<div className="flex items-center gap-3.5">
<div className="flex items-center gap-2">
<span className="text-white/20">|</span>
<Button
className="h-[38px]"
@@ -7,8 +7,13 @@ import {
faPaintbrush,
faImage,
faPenNib,
// faGem,
} from "@fortawesome/pro-solid-svg-icons";
import {
faWindowRestore,
faSquare,
faXmark,
faDash,
} from "@fortawesome/pro-regular-svg-icons";
import { Button } from "@storyteller/ui-button";
import { AuthButtons } from "./AuthButtons";
import { SceneTitleInput } from "./SceneTitleInput";
@@ -39,6 +44,10 @@ import {
// import { usePricingModalStore } from "@storyteller/ui-pricing-modal"; - Uncomment for pricing modal - BFlat
import toast from "react-hot-toast";
import { gtagEvent } from "@storyteller/google-analytics";
import {
useTauriWindowControls,
useTauriPlatform,
} from "@storyteller/tauri-utils";
interface Props {
pageName: string;
@@ -78,7 +87,7 @@ const appMenuTabs: MenuIconItem[] = [
id: "EDIT",
label: "Edit Image",
icon: <FontAwesomeIcon icon={faPenNib} />,
}
},
];
export const topNavMediaId = signal<string>("");
@@ -89,10 +98,14 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
const { isDesktop, isMaximized, minimize, toggleMaximize, close } =
useTauriWindowControls();
const platform = useTauriPlatform();
const handleOpenGalleryModal = () => {
galleryModalVisibleViewMode.value = true;
galleryModalVisibleDuringDrag.value = true;
gtagEvent("open_gallery_modal", { "tab": tabStore.activeTabId });
gtagEvent("open_gallery_modal", { tab: tabStore.activeTabId });
};
const tabStore = useTabStore();
@@ -128,13 +141,17 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
return (
<>
<header className="fixed left-0 top-0 z-[60] w-full border-b border-white/5 bg-ui-background">
<header
className="fixed left-0 top-0 z-[60] w-full border-b border-white/5 bg-ui-background"
data-tauri-drag-region
>
<nav
className="mx-auto grid h-[56px] w-screen grid-cols-3 items-center justify-between px-3"
className="mx-auto grid h-[56px] w-screen grid-cols-3 items-center justify-between ps-3"
aria-label="navigation"
>
<div className="flex items-center gap-7">
<div>
<div className="flex items-center gap-3">
<div className="mr-2">
<span className="sr-only">ArtCraft</span>
<img
className="h-[24px] w-auto"
@@ -147,7 +164,7 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
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 +187,13 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
setDisableSwitcher(false);
}, SWITCHER_THROTTLE_TIME);
}}
className="w-fit"
className="no-drag w-fit"
/>
</div>
<div className="flex items-center justify-center gap-2 font-medium">
<div
className={`${tabStore.activeTabId === "3D" ? "no-drag" : ""} flex items-center justify-center gap-2 font-medium`}
>
{tabStore.activeTabId === "3D" ? (
<SceneTitleInput pageName={pageName} />
) : (
@@ -188,8 +207,8 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
)}
</div>
<div className="flex justify-end gap-3.5 pr-2">
<div className="flex gap-2">
<div className="flex justify-end gap-2">
<div className="no-drag flex gap-2">
{/* - Uncomment for pricing modal - BFlat */}
{/* <Button
variant="primary"
@@ -220,7 +239,37 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
<Activity />
</div>
<AuthButtons loginSignUpPressed={loginSignUpPressed} />
<div className="no-drag">
<AuthButtons loginSignUpPressed={loginSignUpPressed} />
</div>
{isDesktop && platform !== "macos" && (
<div className="no-drag flex items-center">
<Button
variant="secondary"
className="h-[32px] w-[44px] rounded-none bg-transparent text-white/70 hover:bg-white/10 hover:text-white"
onClick={minimize}
>
<FontAwesomeIcon icon={faDash} className="text-xs" />
</Button>
<Button
variant="secondary"
className="h-[32px] w-[44px] rounded-none bg-transparent text-white/70 hover:bg-white/10 hover:text-white"
onClick={toggleMaximize}
>
<FontAwesomeIcon
icon={isMaximized ? faWindowRestore : faSquare}
className="text-xs"
/>
</Button>
<Button
variant="secondary"
className="h-[32px] w-[44px] rounded-none bg-transparent text-white/70 hover:bg-red/40 hover:text-white"
onClick={close}
>
<FontAwesomeIcon icon={faXmark} className="text-lg" />
</Button>
</div>
)}
</div>
</nav>
</header>
@@ -37,7 +37,7 @@ const TextToImage = ({ imageMediaId, imageUrl }: TextToImageProps) => {
return (
<div
ref={containerRef}
className="flex h-[calc(100vh-56px)] w-full rounded-lg bg-[#121212]"
className="flex h-[calc(100vh-56px)] w-full bg-[#121212]"
>
<div className="h-full w-full p-4">
<div className="flex h-full w-full flex-col items-center justify-center rounded-md pb-12">
@@ -37,7 +37,7 @@ const ImageToVideo = ({ imageMediaId, imageUrl }: ImageToVideoProps) => {
return (
<div
ref={containerRef}
className="flex h-[calc(100vh-56px)] w-full rounded-lg bg-[#121212]"
className="flex h-[calc(100vh-56px)] w-full bg-[#121212]"
>
<div className="h-full w-full p-4">
<div className="flex h-full w-full flex-col items-center justify-center rounded-md pb-12">
@@ -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;
@@ -84,7 +84,7 @@ export const MenuIconSelector: React.FC<MenuIconSelectorProps> = ({
>
<div
ref={containerRef}
className="relative flex gap-1 bg-white/10 border-white/10 border rounded-xl px-1 py-1 min-w-max"
className="relative flex gap-1 bg-white/10 border-white/10 border rounded-xl px-1 py-0.5 min-w-max"
style={{ minWidth: 0 }}
onMouseLeave={() => setHoveredIndex(-1)}
>
@@ -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"
@@ -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"
}
]
}
+12
View File
@@ -2,6 +2,18 @@
"files": [],
"include": [],
"references": [
{
"path": "../components/toaster"
},
{
"path": "../tauri-api"
},
{
"path": "../soundboard"
},
{
"path": "../common"
},
{
"path": "./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"
}
]
}
@@ -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";
@@ -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<TauriPlatform | undefined>(
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;
}
@@ -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<boolean> {
const win = await getCurrentWindowSafe();
return win.isMaximized();
}