diff --git a/src/app/workspace/page.tsx b/src/app/workspace/page.tsx index d9a4302..7615d84 100644 --- a/src/app/workspace/page.tsx +++ b/src/app/workspace/page.tsx @@ -14,8 +14,18 @@ import { } from "@/lib/workspace"; export default function Workspace() { - const { workspace, loadWorkspace, updateEntity, setWorkspace } = - useWorkspace(); + const { + workspace, + loadWorkspace, + updateEntity, + setWorkspace, + addEntity, + addGroup, + renameEntity, + deleteEntity, + removeNode, + renameGroup, + } = useWorkspace(); const [route, setRoute] = useState({}); @@ -120,6 +130,12 @@ export default function Workspace() { workspace={workspace} setRoute={setRoute} setWorkspace={setWorkspace} + addEntity={addEntity} + addGroup={addGroup} + renameEntity={renameEntity} + deleteEntity={deleteEntity} + removeNode={removeNode} + renameGroup={renameGroup} >
diff --git a/src/components/sidebar/AppSidebar.tsx b/src/components/sidebar/AppSidebar.tsx index c22bb41..aecb03a 100644 --- a/src/components/sidebar/AppSidebar.tsx +++ b/src/components/sidebar/AppSidebar.tsx @@ -36,7 +36,6 @@ import { ChevronDown } from "lucide-react"; import { Route, SetState, Workspace } from "@/lib/types"; import { listWorkspaces, saveWorkspaceToLocalStorage } from "@/lib/workspace"; import { openFileDialog } from "@/lib/import"; -import { useWorkspace } from "@/hooks/workspace"; // Simple ID generator const id = (prefix = "t") => @@ -47,7 +46,17 @@ type AppSidebarProps = { workspace: Workspace; setRoute: SetState; /** optional setter for the current workspace, used for rename */ - setWorkspace?: React.Dispatch>; + setWorkspace: React.Dispatch>; + addEntity: ( + type: "todo" | "doc", + parentGroupId: string | null, + name?: string, + ) => string | undefined; + addGroup: (name: string | undefined, 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 AppSidebar({ @@ -55,8 +64,14 @@ export default function AppSidebar({ workspace, setRoute, setWorkspace, + addEntity, + addGroup, + renameEntity, + deleteEntity, + removeNode, + renameGroup, }: AppSidebarProps) { - const { addEntity, addGroup, renameEntity } = useWorkspace(); + // actions are passed from parent `useWorkspace` instance const [availableWorkspaces, setAvailableWorkspaces] = React.useState< Workspace[] @@ -64,6 +79,11 @@ export default function AppSidebar({ const [isRenameOpen, setIsRenameOpen] = React.useState(false); const [proposedName, setProposedName] = React.useState(workspace.name); + const [isCreateOpen, setIsCreateOpen] = React.useState(false); + const [createMode, setCreateMode] = React.useState<"list" | "doc" | "group">( + "list", + ); + const [createValue, setCreateValue] = React.useState(""); React.useEffect(() => { setProposedName(workspace.name); @@ -163,44 +183,27 @@ export default function AppSidebar({ { - const name = window.prompt( - "Name for new list", - "New list", - ); - const id = addEntity("todo", null); - if (id && name) { - // rename newly created entity - // obtain hook function - // import renameEntity above - renameEntity(id, name); - } - if (id) setRoute({ workspace: workspace.id, entity: id }); + setCreateMode("list"); + setCreateValue("New list"); + setIsCreateOpen(true); }} > New list (root) { - const name = window.prompt( - "Name for new document", - "New doc", - ); - const id = addEntity("doc", null); - if (id && name) { - renameEntity(id, name); - } - if (id) setRoute({ workspace: workspace.id, entity: id }); + setCreateMode("doc"); + setCreateValue("New doc"); + setIsCreateOpen(true); }} > New doc (root) { - const name = window.prompt( - "Name for new group", - "New group", - ); - addGroup(name || "New group", null); + setCreateMode("group"); + setCreateValue("New group"); + setIsCreateOpen(true); }} > New group (root) @@ -231,6 +234,12 @@ export default function AppSidebar({ node={node} workspace={workspace} setRoute={setRoute} + addEntity={addEntity} + addGroup={addGroup} + renameEntity={renameEntity} + deleteEntity={deleteEntity} + removeNode={removeNode} + renameGroup={renameGroup} /> ))} @@ -270,6 +279,55 @@ export default function AppSidebar({ + {/* create dialog */} + + + + + {createMode === "group" + ? "Create group" + : createMode === "doc" + ? "Create document" + : "Create list"} + + + {createMode === "group" && "Provide a name for the new group."} + {createMode === "doc" && "Provide a name for the new document."} + {createMode === "list" && "Provide a name for the new list."} + + + setCreateValue(e.target.value)} + className="w-full" + /> + + + + + + ); } diff --git a/src/components/sidebar/Entity.tsx b/src/components/sidebar/Entity.tsx index 2ea73e6..cfc1993 100644 --- a/src/components/sidebar/Entity.tsx +++ b/src/components/sidebar/Entity.tsx @@ -12,7 +12,6 @@ import { import { Collapsible, CollapsibleContent } from "../ui/collapsible"; import { ChevronDown, MoreHorizontal } from "lucide-react"; import { Route, Workspace, LayoutNode } from "@/lib/types"; -import { useWorkspace } from "@/hooks/workspace"; import { DropdownMenu, DropdownMenuContent, @@ -36,18 +35,29 @@ type Props = { node: LayoutNode; workspace: Workspace; setRoute: (route: Route) => void; + addEntity: ( + type: "todo" | "doc", + parentGroupId: string | null, + name?: string, + ) => string | undefined; + addGroup: (name: string | undefined, 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({ node, workspace, setRoute }: Props) { - const { - renameEntity, - deleteEntity, - addEntity, - addGroup, - removeNode, - renameGroup, - } = useWorkspace(); - +export default function EntitySwitch({ + node, + workspace, + setRoute, + addEntity, + addGroup, + renameEntity, + deleteEntity, + removeNode, + renameGroup, +}: Props) { const [menuOpen, setMenuOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); const [dialogValue, setDialogValue] = useState(""); @@ -74,11 +84,7 @@ export default function EntitySwitch({ node, workspace, setRoute }: Props) { renameEntity(node.entityId, dialogValue); } else if (dialogMode === "add-item") { const parentId = node.type === "group" ? node.id : null; - const id = addEntity("todo", parentId); - // optionally could rename newly created entity name - if (id && dialogValue) { - renameEntity(id, dialogValue); - } + addEntity("todo", parentId, dialogValue || "New list"); } else if (dialogMode === "add-group") { const parentId = node.type === "group" ? node.id : null; addGroup(dialogValue || "New group", parentId); @@ -144,6 +150,12 @@ export default function EntitySwitch({ node, workspace, setRoute }: Props) { node={child} workspace={workspace} setRoute={setRoute} + addEntity={addEntity} + addGroup={addGroup} + renameEntity={renameEntity} + deleteEntity={deleteEntity} + removeNode={removeNode} + renameGroup={renameGroup} /> ))} diff --git a/src/hooks/workspace.ts b/src/hooks/workspace.ts index 72a182f..2551d45 100644 --- a/src/hooks/workspace.ts +++ b/src/hooks/workspace.ts @@ -13,18 +13,19 @@ export function useWorkspace() { const [workspace, setWorkspace] = useState(); const updateEntity = (entityId: EntityId, updater: (e: Entity) => Entity) => { - setWorkspace((prev) => - prev - ? { - ...prev, - entities: { - ...prev.entities, - [entityId]: updater(prev.entities[entityId]), - }, - updatedAt: Date.now(), - } - : undefined, - ); + setWorkspace((prev) => { + if (!prev) return prev; + const current = prev.entities[entityId]; + if (!current) return prev; + return { + ...prev, + entities: { + ...prev.entities, + [entityId]: updater(current), + }, + updatedAt: Date.now(), + }; + }); }; const renameEntity = (entityId: EntityId, newName: string) => { @@ -46,8 +47,11 @@ export function useWorkspace() { }); }; - const addEntity = (type: "todo" | "doc", parentGroupId: string | null) => { - if (!workspace) return; + const addEntity = ( + type: "todo" | "doc", + parentGroupId: string | null, + name?: string, + ) => { const now = Date.now(); const id = crypto.randomUUID(); let entity: Entity; @@ -55,7 +59,7 @@ export function useWorkspace() { entity = { id, type: "todo", - name: "New list", + name: name || "New list", config: { statuses: [] }, tags: {}, tasks: {}, @@ -66,7 +70,7 @@ export function useWorkspace() { entity = { id, type: "doc", - name: "New doc", + name: name || "New doc", content: { version: "2.28.0", time: now, blocks: [] }, createdAt: now, updatedAt: now,