265 lines
6.8 KiB
TypeScript
265 lines
6.8 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { ChevronDown } from "lucide-react";
|
|
|
|
import {
|
|
SidebarGroup,
|
|
SidebarGroupContent,
|
|
SidebarGroupLabel,
|
|
SidebarMenu,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
} from "../ui/sidebar";
|
|
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from "../ui/collapsible";
|
|
|
|
import {
|
|
ContextMenu,
|
|
ContextMenuContent,
|
|
ContextMenuItem,
|
|
ContextMenuSeparator,
|
|
ContextMenuTrigger,
|
|
} from "../ui/context-menu";
|
|
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
} from "../ui/dialog";
|
|
|
|
import { Input } from "../ui/input";
|
|
import { Button } from "../ui/button";
|
|
|
|
import { Route, Workspace, LayoutNode } from "@/lib/types";
|
|
|
|
type DialogMode = "rename" | "add-item" | "add-group";
|
|
|
|
type Props = {
|
|
node: LayoutNode;
|
|
workspace: Workspace;
|
|
setRoute: (route: Route) => void;
|
|
addEntity: (
|
|
type: "todo" | "doc",
|
|
parentGroupId: string | null,
|
|
name?: string,
|
|
) => string | undefined;
|
|
addGroup: (name: string, parentGroupId: string | null) => string;
|
|
renameEntity: (entityId: string, newName: string) => void;
|
|
deleteEntity: (entityId: string) => void;
|
|
removeNode: (id: string) => void;
|
|
renameGroup: (id: string, name: string) => void;
|
|
};
|
|
|
|
export default function EntitySwitch(props: Props) {
|
|
const {
|
|
node,
|
|
workspace,
|
|
setRoute,
|
|
addEntity,
|
|
addGroup,
|
|
renameEntity,
|
|
deleteEntity,
|
|
removeNode,
|
|
renameGroup,
|
|
} = props;
|
|
|
|
const [dialogValue, setDialogValue] = useState("");
|
|
const [dialogMode, setDialogMode] = useState<DialogMode | undefined>();
|
|
|
|
const parentId = node.type === "group" ? node.id : null;
|
|
|
|
const openDialog = (mode: DialogMode, initial = "") => {
|
|
setDialogMode(mode);
|
|
setDialogValue(
|
|
initial ||
|
|
(mode === "add-item"
|
|
? "New list"
|
|
: mode === "add-group"
|
|
? "New group"
|
|
: ""),
|
|
);
|
|
};
|
|
|
|
const closeDialog = () => setDialogMode(undefined);
|
|
|
|
const handleSubmit = () => {
|
|
const value = dialogValue.trim();
|
|
if (!value) return;
|
|
|
|
switch (dialogMode) {
|
|
case "rename":
|
|
if (node.type === "group") renameGroup(node.id, value);
|
|
else if (node.type === "item") renameEntity(node.entityId, value);
|
|
break;
|
|
|
|
case "add-item":
|
|
addEntity("todo", parentId, value);
|
|
break;
|
|
|
|
case "add-group":
|
|
addGroup(value, parentId);
|
|
break;
|
|
}
|
|
|
|
closeDialog();
|
|
};
|
|
|
|
const confirmDelete = (message: string, action: () => void) => {
|
|
if (window.confirm(message)) action();
|
|
};
|
|
|
|
const dialogTitle = dialogMode?.startsWith("rename")
|
|
? "Rename"
|
|
: dialogMode === "add-item"
|
|
? "Add new list"
|
|
: "Add new group";
|
|
|
|
const dialog = (
|
|
<Dialog open={!!dialogMode} onOpenChange={(o) => !o && closeDialog()}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>{dialogTitle}</DialogTitle>
|
|
|
|
{(dialogMode === "add-item" || dialogMode === "add-group") && (
|
|
<DialogDescription>Enter a name below.</DialogDescription>
|
|
)}
|
|
</DialogHeader>
|
|
|
|
<Input
|
|
autoFocus
|
|
value={dialogValue}
|
|
onChange={(e) => setDialogValue(e.target.value)}
|
|
/>
|
|
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={closeDialog}>
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={handleSubmit}>Save</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
|
|
/* ================= GROUP ================= */
|
|
|
|
if (node.type === "group") {
|
|
return (
|
|
<>
|
|
<Collapsible defaultOpen className="group/collapsible">
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel asChild>
|
|
<ContextMenu>
|
|
<ContextMenuTrigger asChild>
|
|
<CollapsibleTrigger asChild>
|
|
<SidebarMenuButton className="w-full">
|
|
{node.name}
|
|
<ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
|
</SidebarMenuButton>
|
|
</CollapsibleTrigger>
|
|
</ContextMenuTrigger>
|
|
|
|
<ContextMenuContent>
|
|
<ContextMenuItem onSelect={() => openDialog("add-item")}>
|
|
Add list
|
|
</ContextMenuItem>
|
|
|
|
<ContextMenuItem onSelect={() => openDialog("add-group")}>
|
|
Add group
|
|
</ContextMenuItem>
|
|
|
|
<ContextMenuSeparator />
|
|
|
|
<ContextMenuItem
|
|
onSelect={() => openDialog("rename", node.name)}
|
|
>
|
|
Rename
|
|
</ContextMenuItem>
|
|
|
|
<ContextMenuItem
|
|
variant="destructive"
|
|
onSelect={() =>
|
|
confirmDelete("Delete this group and its children?", () =>
|
|
removeNode(node.id),
|
|
)
|
|
}
|
|
>
|
|
Delete
|
|
</ContextMenuItem>
|
|
</ContextMenuContent>
|
|
</ContextMenu>
|
|
</SidebarGroupLabel>
|
|
|
|
<CollapsibleContent>
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
{node.children.map((child) => (
|
|
<EntitySwitch
|
|
key={child.type === "group" ? child.id : child.entityId}
|
|
{...props}
|
|
node={child}
|
|
/>
|
|
))}
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</CollapsibleContent>
|
|
</SidebarGroup>
|
|
</Collapsible>
|
|
{dialog}
|
|
</>
|
|
);
|
|
}
|
|
|
|
/* ================= ITEM ================= */
|
|
|
|
const entity = workspace.entities[node.entityId];
|
|
if (!entity) return null;
|
|
|
|
return (
|
|
<>
|
|
<SidebarMenuItem>
|
|
<ContextMenu>
|
|
<ContextMenuTrigger asChild>
|
|
<SidebarMenuButton
|
|
onClick={() =>
|
|
setRoute({
|
|
workspace: workspace.id,
|
|
entity: node.entityId,
|
|
})
|
|
}
|
|
>
|
|
{entity.name}
|
|
</SidebarMenuButton>
|
|
</ContextMenuTrigger>
|
|
|
|
<ContextMenuContent>
|
|
<ContextMenuItem onSelect={() => openDialog("rename", entity.name)}>
|
|
Rename
|
|
</ContextMenuItem>
|
|
|
|
<ContextMenuItem
|
|
variant="destructive"
|
|
onSelect={() =>
|
|
confirmDelete("Delete this list?", () =>
|
|
deleteEntity(node.entityId),
|
|
)
|
|
}
|
|
>
|
|
Delete
|
|
</ContextMenuItem>
|
|
</ContextMenuContent>
|
|
</ContextMenu>
|
|
</SidebarMenuItem>
|
|
{dialog}
|
|
</>
|
|
);
|
|
}
|