1
1
mirror of https://github.com/neosubhamoy/neodlp.git synced 2026-03-22 11:25:49 +05:30

feat: implemented custom titlebar for windows and linux

This commit is contained in:
2026-02-11 19:19:17 +05:30
Verified
parent b1c176cba3
commit 24c4a640e1
12 changed files with 188 additions and 77 deletions

View File

@@ -10,6 +10,10 @@
"core:window:allow-hide", "core:window:allow-hide",
"core:window:allow-show", "core:window:allow-show",
"core:window:allow-set-focus", "core:window:allow-set-focus",
"core:window:allow-minimize",
"core:window:allow-maximize",
"core:window:allow-unmaximize",
"core:window:allow-start-dragging",
"opener:default", "opener:default",
"shell:default", "shell:default",
"fs:default", "fs:default",

View File

@@ -10,8 +10,9 @@
"windows": [ "windows": [
{ {
"title": "NeoDLP", "title": "NeoDLP",
"width": 1067, "width": 1080,
"height": 605, "height": 680,
"decorations": false,
"visible": false "visible": false
} }
], ],

View File

@@ -10,8 +10,9 @@
"windows": [ "windows": [
{ {
"title": "NeoDLP", "title": "NeoDLP",
"width": 1067, "width": 1080,
"height": 605, "height": 680,
"decorations": false,
"visible": false "visible": false
} }
], ],

View File

@@ -10,8 +10,9 @@
"windows": [ "windows": [
{ {
"title": "NeoDLP", "title": "NeoDLP",
"width": 1067, "width": 1080,
"height": 605, "height": 680,
"decorations": false,
"visible": false "visible": false
} }
], ],

View File

@@ -0,0 +1,12 @@
export function CloseIcon({ className }: { className?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" className={className}>
<path
fill="currentColor"
fillRule="evenodd"
d="m7.116 8l-4.558 4.558l.884.884L8 8.884l4.558 4.558l.884-.884L8.884 8l4.558-4.558l-.884-.884L8 7.116L3.442 2.558l-.884.884z"
clipRule="evenodd"
/>
</svg>
);
}

View File

@@ -0,0 +1,7 @@
export function MaximizeIcon({ className }: { className?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" className={className}>
<path fill="currentColor" d="M3 3v10h10V3zm9 9H4V4h8z" />
</svg>
);
}

View File

@@ -0,0 +1,7 @@
export function MinimizeIcon({ className }: { className?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" className={className}>
<path fill="currentColor" d="M14 8v1H3V8z" />
</svg>
);
}

View File

@@ -0,0 +1,10 @@
export function UnmaximizeIcon({ className }: { className?: string }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" className={className}>
<g fill="currentColor">
<path d="M3 5v9h9V5zm8 8H4V6h7z" />
<path fillRule="evenodd" d="M5 5h1V4h7v7h-1v1h2V3H5z" clipRule="evenodd" />
</g>
</svg>
);
}

View File

@@ -8,10 +8,13 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { useLogger } from "@/helpers/use-logger"; import { useLogger } from "@/helpers/use-logger";
import { writeText } from "@tauri-apps/plugin-clipboard-manager"; import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import TitleBar from "@/components/titlebar";
import { platform } from "@tauri-apps/plugin-os";
export default function Navbar() { export default function Navbar() {
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const location = useLocation(); const location = useLocation();
const currentPlatform = platform();
const logger = useLogger(); const logger = useLogger();
const logs = logger.getLogs(); const logs = logger.getLogs();
const logText = logs.map(log => `${new Date(log.timestamp).toLocaleTimeString()} [${log.level.toUpperCase()}] ${log.context}: ${log.message}`).join('\n'); const logText = logs.map(log => `${new Date(log.timestamp).toLocaleTimeString()} [${log.level.toUpperCase()}] ${log.context}: ${log.message}`).join('\n');
@@ -23,67 +26,74 @@ export default function Navbar() {
} }
return ( return (
<nav className="flex justify-between items-center py-3 px-4 sticky top-0 backdrop-blur supports-backdrop-filter:bg-background/60 border-b z-50"> <div className="sticky top-0 z-50">
<div className="flex justify-center"> {currentPlatform === "windows" || currentPlatform === "linux" ? (
<SidebarTrigger /> <TitleBar />
<h1 className="text-lg font-semibold ml-4">{getRouteName(location.pathname)}</h1> ) : (
</div> null
<div className="flex justify-center items-center"> )}
<Dialog> <nav className="flex justify-between items-center py-3 px-4 backdrop-blur supports-backdrop-filter:bg-background/60 border-b">
<Tooltip> <div className="flex justify-center">
<TooltipTrigger asChild> <SidebarTrigger />
<DialogTrigger asChild> <h1 className="text-lg font-semibold ml-4">{getRouteName(location.pathname)}</h1>
<Button variant="outline" size="icon"> </div>
<Terminal /> <div className="flex justify-center items-center">
</Button> <Dialog>
</DialogTrigger> <Tooltip>
</TooltipTrigger> <TooltipTrigger asChild>
<TooltipContent> <DialogTrigger asChild>
<p>Logs</p> <Button variant="outline" size="icon">
</TooltipContent> <Terminal />
</Tooltip> </Button>
<DialogContent className="sm:max-w-150"> </DialogTrigger>
<DialogHeader> </TooltipTrigger>
<DialogTitle>Log Viewer</DialogTitle> <TooltipContent>
<DialogDescription>Monitor real-time app session logs (latest on top)</DialogDescription> <p>Logs</p>
</DialogHeader> </TooltipContent>
<div className="flex flex-col gap-2 p-2 max-h-75 overflow-y-scroll overflow-x-hidden bg-muted"> </Tooltip>
{logs.length === 0 ? ( <DialogContent className="sm:max-w-150">
<p className="text-sm text-muted-foreground">NO LOGS TO SHOW!</p> <DialogHeader>
) : ( <DialogTitle>Log Viewer</DialogTitle>
logs.slice().reverse().map((log, index) => ( <DialogDescription>Monitor real-time app session logs (latest on top)</DialogDescription>
<div key={index} className={`flex flex-col ${log.level === 'error' ? 'text-red-500' : log.level === 'warning' ? 'text-amber-500' : log.level === 'debug' ? 'text-sky-500' : log.level === 'progress' ? 'text-emerald-500' : 'text-foreground'}`}> </DialogHeader>
<p className="text-xs"><strong>{new Date(log.timestamp).toLocaleTimeString()}</strong> [{log.level.toUpperCase()}] <em>{log.context}</em> :</p> <div className="flex flex-col gap-2 p-2 max-h-75 overflow-y-scroll overflow-x-hidden bg-muted">
<p className="text-xs font-mono break-all">{log.message}</p> {logs.length === 0 ? (
</div> <p className="text-sm text-muted-foreground">NO LOGS TO SHOW!</p>
))
)}
</div>
<DialogFooter>
<Button
variant="destructive"
disabled={logs.length === 0}
onClick={() => logger.clearLogs()}
>
<BrushCleaning className="size-4" />
Clear Logs
</Button>
<Button
className="transition-all duration-300"
disabled={logs.length === 0}
onClick={() => handleCopyLogs()}
>
{copied ? (
<Check className="size-4" />
) : ( ) : (
<Copy className="size-4" /> logs.slice().reverse().map((log, index) => (
<div key={index} className={`flex flex-col ${log.level === 'error' ? 'text-red-500' : log.level === 'warning' ? 'text-amber-500' : log.level === 'debug' ? 'text-sky-500' : log.level === 'progress' ? 'text-emerald-500' : 'text-foreground'}`}>
<p className="text-xs"><strong>{new Date(log.timestamp).toLocaleTimeString()}</strong> [{log.level.toUpperCase()}] <em>{log.context}</em> :</p>
<p className="text-xs font-mono break-all">{log.message}</p>
</div>
))
)} )}
Copy Logs </div>
</Button> <DialogFooter>
</DialogFooter> <Button
</DialogContent> variant="destructive"
</Dialog> disabled={logs.length === 0}
</div> onClick={() => logger.clearLogs()}
</nav> >
<BrushCleaning className="size-4" />
Clear Logs
</Button>
<Button
className="transition-all duration-300"
disabled={logs.length === 0}
onClick={() => handleCopyLogs()}
>
{copied ? (
<Check className="size-4" />
) : (
<Copy className="size-4" />
)}
Copy Logs
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</nav>
</div>
) )
} }

View File

@@ -147,7 +147,7 @@ export function AppSidebar() {
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button variant="ghost"> <Button variant="ghost">
<CircleArrowUp className="size-4" /> <CircleArrowUp className="size-4 stroke-primary" />
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="right"> <TooltipContent side="right">

View File

@@ -0,0 +1,58 @@
import { useState } from "react";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { MaximizeIcon } from "@/components/icons/maximize";
import { MinimizeIcon } from "@/components/icons/minimize";
import { CloseIcon } from "@/components/icons/close";
import { UnmaximizeIcon } from "@/components/icons/unmaximize";
export default function TitleBar() {
const [maximized, setMaximized] = useState<boolean>(false);
const appWindow = getCurrentWebviewWindow();
return (
<div className="titlebar flex items-center justify-between border-b bg-background">
<div className="flex items-center justify-center grow px-4 py-2.5" data-tauri-drag-region>
<h1 className="text-sm text-primary font-semibold">NeoDLP</h1>
</div>
<div className="controls flex items-center justify-center">
<button
className="px-4 py-3 hover:bg-muted"
id="titlebar-minimize"
title="Minimize"
onClick={() => appWindow.minimize()}
>
<MinimizeIcon />
</button>
<button
className="px-4 py-3 hover:bg-muted"
id="titlebar-maximize"
title={maximized ? "Unmaximize" : "Maximize"}
onClick={async () => {
const isMaximized = await appWindow.isMaximized();
if (isMaximized) {
await appWindow.unmaximize();
setMaximized(false);
} else {
await appWindow.maximize();
setMaximized(true);
}
}}
>
{maximized ? (
<UnmaximizeIcon />
) : (
<MaximizeIcon />
)}
</button>
<button
className="px-4 py-3 hover:bg-destructive"
id="titlebar-close"
title="Close"
onClick={() => appWindow.hide()}
>
<CloseIcon />
</button>
</div>
</div>
);
}

View File

@@ -7,16 +7,16 @@ import Footer from "@/components/footer";
export default function RootLayout() { export default function RootLayout() {
return ( return (
<> <>
<div className="flex min-h-screen"> <div className="flex flex-col min-h-screen">
<SidebarProvider> <SidebarProvider>
<AppSidebar /> <AppSidebar />
<div className="w-full h-screen overflow-y-scroll relative"> <div className="w-full h-screen overflow-y-scroll no-scrollbar relative">
<Navbar/> <Navbar/>
<Outlet /> <Outlet />
<Footer/> <Footer/>
</div> </div>
</SidebarProvider> </SidebarProvider>
</div> </div>
</> </>
); );
} }