Sidbar more elegant

This commit is contained in:
2026-02-28 02:33:50 +01:00
parent 9200e1cfad
commit 7d4a087c10
+66 -16
View File
@@ -12,6 +12,16 @@ import {
SidebarProvider, SidebarProvider,
useSidebar, useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogDescription,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import EntitySwitch from "./Entity"; import EntitySwitch from "./Entity";
import { import {
@@ -25,6 +35,7 @@ import {
import { ChevronDown } from "lucide-react"; import { ChevronDown } from "lucide-react";
import { Route, SetState, Workspace } from "@/lib/types"; import { Route, SetState, Workspace } from "@/lib/types";
import { listWorkspaces, saveWorkspaceToLocalStorage } from "@/lib/workspace"; import { listWorkspaces, saveWorkspaceToLocalStorage } from "@/lib/workspace";
import { openFileDialog } from "@/lib/import";
// Simple ID generator // Simple ID generator
const id = (prefix = "t") => const id = (prefix = "t") =>
@@ -48,6 +59,13 @@ export default function AppSidebar({
Workspace[] Workspace[]
>([]); >([]);
const [isRenameOpen, setIsRenameOpen] = React.useState(false);
const [proposedName, setProposedName] = React.useState(workspace.name);
React.useEffect(() => {
setProposedName(workspace.name);
}, [workspace.name]);
// reload list when component mounts or whenever the current workspace changes // reload list when component mounts or whenever the current workspace changes
React.useEffect(() => { React.useEffect(() => {
setAvailableWorkspaces(listWorkspaces()); setAvailableWorkspaces(listWorkspaces());
@@ -57,14 +75,10 @@ export default function AppSidebar({
setRoute({ workspace: id }); setRoute({ workspace: id });
}; };
const importWorkspace = () => { const importWorkspace = async () => {
const json = window.prompt(
"Paste a workspace JSON to import (generated by export)",
);
if (!json) return;
try { try {
const parsed = JSON.parse(json); const { content } = await openFileDialog(".json");
const parsed = JSON.parse(content);
if (parsed && typeof parsed.id === "string") { if (parsed && typeof parsed.id === "string") {
saveWorkspaceToLocalStorage(parsed); saveWorkspaceToLocalStorage(parsed);
setAvailableWorkspaces(listWorkspaces()); setAvailableWorkspaces(listWorkspaces());
@@ -74,28 +88,34 @@ export default function AppSidebar({
} }
} catch (err) { } catch (err) {
console.error("Failed to import workspace:", err); console.error("Failed to import workspace:", err);
window.alert("Import failed: invalid JSON"); window.alert("Import failed: invalid file");
} }
}; };
const exportWorkspace = () => { const exportWorkspace = () => {
try { try {
const text = JSON.stringify(workspace, null, 2); const text = JSON.stringify(workspace, null, 2);
navigator.clipboard.writeText(text); const blob = new Blob([text], { type: "application/json" });
window.alert("Workspace JSON copied to clipboard"); const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `workspace-${workspace.name}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (err) { } catch (err) {
console.error("Export failed", err); console.error("Export failed", err);
window.alert("Failed to copy workspace JSON"); window.alert("Failed to download workspace");
} }
}; };
const renameWorkspace = () => { const renameWorkspace = (name: string) => {
const newName = window.prompt("New workspace name", workspace.name); if (!name || name === workspace.name) return;
if (!newName || newName === workspace.name) return;
const updated: Workspace = { const updated: Workspace = {
...workspace, ...workspace,
name: newName, name,
updatedAt: Date.now(), updatedAt: Date.now(),
}; };
saveWorkspaceToLocalStorage(updated); saveWorkspaceToLocalStorage(updated);
@@ -142,7 +162,7 @@ export default function AppSidebar({
Import Workspace Import Workspace
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem onSelect={renameWorkspace}> <DropdownMenuItem onSelect={() => setIsRenameOpen(true)}>
Rename Workspace Rename Workspace
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onSelect={exportWorkspace}> <DropdownMenuItem onSelect={exportWorkspace}>
@@ -172,6 +192,36 @@ export default function AppSidebar({
</Sidebar> </Sidebar>
<main className="w-svw flex-1 flex flex-col min-h-0">{children}</main> <main className="w-svw flex-1 flex flex-col min-h-0">{children}</main>
{/* rename dialog */}
<Dialog open={isRenameOpen} onOpenChange={setIsRenameOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename workspace</DialogTitle>
<DialogDescription>
Provide a new name for the workspace.
</DialogDescription>
</DialogHeader>
<Input
value={proposedName}
onChange={(e) => setProposedName(e.target.value)}
className="w-full"
/>
<DialogFooter>
<Button variant="outline" onClick={() => setIsRenameOpen(false)}>
Cancel
</Button>
<Button
onClick={() => {
renameWorkspace(proposedName);
setIsRenameOpen(false);
}}
>
Save
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</SidebarProvider> </SidebarProvider>
); );
} }