import React, { useEffect, useMemo, useState } from "react"; import Tree, { mutateTree, moveItemOnTree, RenderItemParams, TreeItem, TreeData, ItemId, TreeSourcePosition, TreeDestinationPosition, } from "@atlaskit/tree"; import useCollectionStore from "@/store/collections"; import { Collection } from "@prisma/client"; import Link from "next/link"; import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; import { useRouter } from "next/router"; import useAccountStore from "@/store/account"; import toast from "react-hot-toast"; interface ExtendedTreeItem extends TreeItem { data: Collection; } const CollectionListing = () => { const { collections, updateCollection } = useCollectionStore(); const { account, updateAccount } = useAccountStore(); const router = useRouter(); const currentPath = router.asPath; const initialTree = useMemo(() => { if (collections.length > 0) { return buildTreeFromCollections( collections, router, account.collectionOrder ); } return undefined; }, [collections, router]); const [tree, setTree] = useState(initialTree); useEffect(() => { setTree(initialTree); }, [initialTree]); useEffect(() => { if (account.username) { if (!account.collectionOrder || account.collectionOrder.length === 0) updateAccount({ ...account, collectionOrder: collections .filter((e) => e.parentId === null) // Filter out collections with non-null parentId .map((e) => e.id as number), // Use "as number" to assert that e.id is a number }); else { // const collectionsIds = collections.map((c) => c.id); // const orderIds = [...account.collectionOrder]; // const missingInOrder = collectionsIds.filter( // (id) => !orderIds.includes(id) // ); // if (missingInOrder.length > 0) { // updateAccount({ // ...account, // collectionOrder: [...account.collectionOrder, ...missingInOrder], // }); // } } } }, [account, collections]); const onExpand = (movedCollectionId: ItemId) => { setTree((currentTree) => mutateTree(currentTree!, movedCollectionId, { isExpanded: true }) ); }; const onCollapse = (movedCollectionId: ItemId) => { setTree((currentTree) => mutateTree(currentTree as TreeData, movedCollectionId, { isExpanded: false, }) ); }; const onDragEnd = async ( source: TreeSourcePosition, destination: TreeDestinationPosition | undefined ) => { if (!destination || !tree) { return; } if ( source.index === destination.index && source.parentId === destination.parentId ) { return; } const movedCollectionId = Number( tree.items[source.parentId].children[source.index] ); const movedCollection = collections.find((c) => c.id === movedCollectionId); const destinationCollection = collections.find( (c) => c.id === Number(destination.parentId) ); console.log( "Moved:", movedCollection, "Destination:", destinationCollection ); if ( (movedCollection?.ownerId !== account.id && destination.parentId !== source.parentId) || (destinationCollection?.ownerId !== account.id && destination.parentId !== "root") ) { return toast.error( "You can't make change to a collection you don't own." ); } console.log("source:", source, "destination:", destination); setTree((currentTree) => moveItemOnTree(currentTree!, source, destination)); const updatedCollectionOrder = [...account.collectionOrder]; if (source.parentId !== destination.parentId) { await updateCollection({ ...movedCollection, parentId: destination.parentId && destination.parentId !== "root" ? Number(destination.parentId) : destination.parentId === "root" ? "root" : null, } as any); } if ( destination.index !== undefined && destination.parentId === source.parentId && source.parentId === "root" ) { updatedCollectionOrder.splice(source.index, 1); console.log("Order1", updatedCollectionOrder); updatedCollectionOrder.splice(destination.index, 0, movedCollectionId); console.log("Order2", updatedCollectionOrder); console.log("Moved id:", movedCollectionId); console.log("Order:", updatedCollectionOrder); await updateAccount({ ...account, collectionOrder: updatedCollectionOrder, }); } else if ( destination.index !== undefined && destination.parentId === "root" ) { console.log("Order1", updatedCollectionOrder); updatedCollectionOrder.splice(destination.index, 0, movedCollectionId); console.log("Order2", updatedCollectionOrder); console.log("Moved id:", movedCollectionId); console.log("Order:", updatedCollectionOrder); await updateAccount({ ...account, collectionOrder: updatedCollectionOrder, }); } else if ( source.parentId === "root" && destination.parentId && destination.parentId !== "root" ) { updatedCollectionOrder.splice(source.index, 1); console.log("Order", updatedCollectionOrder); await updateAccount({ ...account, collectionOrder: updatedCollectionOrder, }); } }; if (!tree) { return <>; } else return ( renderItem({ ...itemProps }, currentPath)} onExpand={onExpand} onCollapse={onCollapse} onDragEnd={onDragEnd} isDragEnabled isNestingEnabled /> ); }; export default CollectionListing; const renderItem = ( { item, onExpand, onCollapse, provided }: RenderItemParams, currentPath: string ) => { const collection = item.data; return (
{Icon(item as ExtendedTreeItem, onExpand, onCollapse)}

{collection.name}

{collection.isPublic ? ( ) : undefined}
{collection._count?.links}
); }; const Icon = ( item: ExtendedTreeItem, onExpand: (id: ItemId) => void, onCollapse: (id: ItemId) => void ) => { if (item.children && item.children.length > 0) { return item.isExpanded ? ( ) : ( ); } // return ; return
; }; const buildTreeFromCollections = ( collections: CollectionIncludingMembersAndLinkCount[], router: ReturnType, order?: number[] ): TreeData => { if (order) { collections.sort((a: any, b: any) => { return order.indexOf(a.id) - order.indexOf(b.id); }); } const items: { [key: string]: ExtendedTreeItem } = collections.reduce( (acc: any, collection) => { acc[collection.id as number] = { id: collection.id, children: [], hasChildren: false, isExpanded: false, data: { id: collection.id, parentId: collection.parentId, name: collection.name, description: collection.description, color: collection.color, isPublic: collection.isPublic, ownerId: collection.ownerId, createdAt: collection.createdAt, updatedAt: collection.updatedAt, _count: { links: collection._count?.links, }, }, }; return acc; }, {} ); const activeCollectionId = Number(router.asPath.split("/collections/")[1]); if (activeCollectionId) { for (const item in items) { const collection = items[item]; if (Number(item) === activeCollectionId && collection.data.parentId) { // get all the parents of the active collection recursively until root and set isExpanded to true let parentId = collection.data.parentId || null; while (parentId) { items[parentId].isExpanded = true; parentId = items[parentId].data.parentId; } } } } collections.forEach((collection) => { const parentId = collection.parentId; if (parentId && items[parentId] && collection.id) { items[parentId].children.push(collection.id); items[parentId].hasChildren = true; } }); const rootId = "root"; items[rootId] = { id: rootId, children: (collections .filter((c) => c.parentId === null) .map((c) => c.id) || "") as unknown as string[], hasChildren: true, isExpanded: true, data: { name: "Root" } as Collection, }; return { rootId, items }; };