Sidbar more elegant
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user