Sidbar more elegant
This commit is contained in:
@@ -12,6 +12,16 @@ import {
|
||||
SidebarProvider,
|
||||
useSidebar,
|
||||
} 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 {
|
||||
@@ -25,6 +35,7 @@ import {
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { Route, SetState, Workspace } from "@/lib/types";
|
||||
import { listWorkspaces, saveWorkspaceToLocalStorage } from "@/lib/workspace";
|
||||
import { openFileDialog } from "@/lib/import";
|
||||
|
||||
// Simple ID generator
|
||||
const id = (prefix = "t") =>
|
||||
@@ -48,6 +59,13 @@ export default function AppSidebar({
|
||||
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
|
||||
React.useEffect(() => {
|
||||
setAvailableWorkspaces(listWorkspaces());
|
||||
@@ -57,14 +75,10 @@ export default function AppSidebar({
|
||||
setRoute({ workspace: id });
|
||||
};
|
||||
|
||||
const importWorkspace = () => {
|
||||
const json = window.prompt(
|
||||
"Paste a workspace JSON to import (generated by export)",
|
||||
);
|
||||
if (!json) return;
|
||||
|
||||
const importWorkspace = async () => {
|
||||
try {
|
||||
const parsed = JSON.parse(json);
|
||||
const { content } = await openFileDialog(".json");
|
||||
const parsed = JSON.parse(content);
|
||||
if (parsed && typeof parsed.id === "string") {
|
||||
saveWorkspaceToLocalStorage(parsed);
|
||||
setAvailableWorkspaces(listWorkspaces());
|
||||
@@ -74,28 +88,34 @@ export default function AppSidebar({
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to import workspace:", err);
|
||||
window.alert("Import failed: invalid JSON");
|
||||
window.alert("Import failed: invalid file");
|
||||
}
|
||||
};
|
||||
|
||||
const exportWorkspace = () => {
|
||||
try {
|
||||
const text = JSON.stringify(workspace, null, 2);
|
||||
navigator.clipboard.writeText(text);
|
||||
window.alert("Workspace JSON copied to clipboard");
|
||||
const blob = new Blob([text], { type: "application/json" });
|
||||
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) {
|
||||
console.error("Export failed", err);
|
||||
window.alert("Failed to copy workspace JSON");
|
||||
window.alert("Failed to download workspace");
|
||||
}
|
||||
};
|
||||
|
||||
const renameWorkspace = () => {
|
||||
const newName = window.prompt("New workspace name", workspace.name);
|
||||
if (!newName || newName === workspace.name) return;
|
||||
const renameWorkspace = (name: string) => {
|
||||
if (!name || name === workspace.name) return;
|
||||
|
||||
const updated: Workspace = {
|
||||
...workspace,
|
||||
name: newName,
|
||||
name,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
saveWorkspaceToLocalStorage(updated);
|
||||
@@ -142,7 +162,7 @@ export default function AppSidebar({
|
||||
Import Workspace
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onSelect={renameWorkspace}>
|
||||
<DropdownMenuItem onSelect={() => setIsRenameOpen(true)}>
|
||||
Rename Workspace
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={exportWorkspace}>
|
||||
@@ -172,6 +192,36 @@ export default function AppSidebar({
|
||||
</Sidebar>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user