mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2026-03-22 08:05:48 +05:30
feat: implemented custom titlebar for windows and linux
This commit is contained in:
@@ -10,6 +10,10 @@
|
||||
"core:window:allow-hide",
|
||||
"core:window:allow-show",
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-unmaximize",
|
||||
"core:window:allow-start-dragging",
|
||||
"opener:default",
|
||||
"shell:default",
|
||||
"fs:default",
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
"windows": [
|
||||
{
|
||||
"title": "NeoDLP",
|
||||
"width": 1067,
|
||||
"height": 605,
|
||||
"width": 1080,
|
||||
"height": 680,
|
||||
"decorations": false,
|
||||
"visible": false
|
||||
}
|
||||
],
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
"windows": [
|
||||
{
|
||||
"title": "NeoDLP",
|
||||
"width": 1067,
|
||||
"height": 605,
|
||||
"width": 1080,
|
||||
"height": 680,
|
||||
"decorations": false,
|
||||
"visible": false
|
||||
}
|
||||
],
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
"windows": [
|
||||
{
|
||||
"title": "NeoDLP",
|
||||
"width": 1067,
|
||||
"height": 605,
|
||||
"width": 1080,
|
||||
"height": 680,
|
||||
"decorations": false,
|
||||
"visible": false
|
||||
}
|
||||
],
|
||||
|
||||
12
src/components/icons/close.tsx
Normal file
12
src/components/icons/close.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
src/components/icons/maximize.tsx
Normal file
7
src/components/icons/maximize.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
src/components/icons/minimize.tsx
Normal file
7
src/components/icons/minimize.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
10
src/components/icons/unmaximize.tsx
Normal file
10
src/components/icons/unmaximize.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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 { useLogger } from "@/helpers/use-logger";
|
||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||
import TitleBar from "@/components/titlebar";
|
||||
import { platform } from "@tauri-apps/plugin-os";
|
||||
|
||||
export default function Navbar() {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const location = useLocation();
|
||||
const currentPlatform = platform();
|
||||
const logger = useLogger();
|
||||
const logs = logger.getLogs();
|
||||
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 (
|
||||
<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="flex justify-center">
|
||||
<SidebarTrigger />
|
||||
<h1 className="text-lg font-semibold ml-4">{getRouteName(location.pathname)}</h1>
|
||||
</div>
|
||||
<div className="flex justify-center items-center">
|
||||
<Dialog>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Terminal />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Logs</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<DialogContent className="sm:max-w-150">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Log Viewer</DialogTitle>
|
||||
<DialogDescription>Monitor real-time app session logs (latest on top)</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-2 p-2 max-h-75 overflow-y-scroll overflow-x-hidden bg-muted">
|
||||
{logs.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">NO LOGS TO SHOW!</p>
|
||||
) : (
|
||||
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>
|
||||
))
|
||||
)}
|
||||
</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" />
|
||||
<div className="sticky top-0 z-50">
|
||||
{currentPlatform === "windows" || currentPlatform === "linux" ? (
|
||||
<TitleBar />
|
||||
) : (
|
||||
null
|
||||
)}
|
||||
<nav className="flex justify-between items-center py-3 px-4 backdrop-blur supports-backdrop-filter:bg-background/60 border-b">
|
||||
<div className="flex justify-center">
|
||||
<SidebarTrigger />
|
||||
<h1 className="text-lg font-semibold ml-4">{getRouteName(location.pathname)}</h1>
|
||||
</div>
|
||||
<div className="flex justify-center items-center">
|
||||
<Dialog>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Terminal />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Logs</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<DialogContent className="sm:max-w-150">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Log Viewer</DialogTitle>
|
||||
<DialogDescription>Monitor real-time app session logs (latest on top)</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-2 p-2 max-h-75 overflow-y-scroll overflow-x-hidden bg-muted">
|
||||
{logs.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">NO LOGS TO SHOW!</p>
|
||||
) : (
|
||||
<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
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</nav>
|
||||
</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" />
|
||||
)}
|
||||
Copy Logs
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ export function AppSidebar() {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<CircleArrowUp className="size-4" />
|
||||
<CircleArrowUp className="size-4 stroke-primary" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
|
||||
58
src/components/titlebar.tsx
Normal file
58
src/components/titlebar.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -7,16 +7,16 @@ import Footer from "@/components/footer";
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex min-h-screen">
|
||||
<SidebarProvider>
|
||||
<AppSidebar />
|
||||
<div className="w-full h-screen overflow-y-scroll relative">
|
||||
<Navbar/>
|
||||
<Outlet />
|
||||
<Footer/>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<SidebarProvider>
|
||||
<AppSidebar />
|
||||
<div className="w-full h-screen overflow-y-scroll no-scrollbar relative">
|
||||
<Navbar/>
|
||||
<Outlet />
|
||||
<Footer/>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user