2024-02-22 01:51:51 -06:00
|
|
|
import useAccountStore from "@/store/account";
|
2024-02-05 01:42:54 -06:00
|
|
|
import useCollectionStore from "@/store/collections";
|
|
|
|
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
|
|
|
import Link from "next/link";
|
|
|
|
import { useRouter } from "next/router";
|
2024-02-22 02:24:10 -06:00
|
|
|
import React, { useEffect, useState } from "react";
|
2024-02-22 02:04:01 -06:00
|
|
|
import { DragDropContext, Draggable, DraggableProvided, Droppable } from "react-beautiful-dnd";
|
2024-02-05 01:42:54 -06:00
|
|
|
|
|
|
|
type Props = {
|
|
|
|
links: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
const CollectionSelection = ({ links }: Props) => {
|
|
|
|
const { collections } = useCollectionStore();
|
2024-02-22 01:51:51 -06:00
|
|
|
const { account, updateAccount } = useAccountStore();
|
2024-02-22 02:04:01 -06:00
|
|
|
// Use local state to store the collection order so we don't have to wait for a response from the API to update the UI
|
2024-02-22 02:24:10 -06:00
|
|
|
const [localCollectionOrder, setLocalCollectionOrder] = useState<number[] | []>([]);
|
2024-02-05 01:42:54 -06:00
|
|
|
const [active, setActive] = useState("");
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setActive(router.asPath);
|
2024-02-22 02:24:10 -06:00
|
|
|
setLocalCollectionOrder(account.collectionOrder || []);
|
2024-02-22 01:51:51 -06:00
|
|
|
|
2024-02-22 02:24:10 -06:00
|
|
|
if (!account.collectionOrder || account.collectionOrder.length === 0) {
|
2024-02-22 01:51:51 -06:00
|
|
|
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
|
|
|
|
});
|
|
|
|
}
|
2024-02-22 02:24:10 -06:00
|
|
|
}, [router, collections, account]);
|
2024-02-05 01:42:54 -06:00
|
|
|
|
|
|
|
return (
|
2024-02-22 01:51:51 -06:00
|
|
|
<DragDropContext
|
|
|
|
onDragEnd={(result) => {
|
|
|
|
if (!result.destination) {
|
|
|
|
return; // Dragged outside the droppable area, do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
const updatedCollectionOrder = [...account.collectionOrder];
|
|
|
|
const [movedCollectionId] = updatedCollectionOrder.splice(result.source.index, 1);
|
|
|
|
updatedCollectionOrder.splice(result.destination.index, 0, movedCollectionId);
|
|
|
|
|
2024-02-22 02:04:01 -06:00
|
|
|
// Update local state with the new collection order
|
|
|
|
setLocalCollectionOrder(updatedCollectionOrder);
|
|
|
|
|
2024-02-22 01:51:51 -06:00
|
|
|
// Update account with the new collection order
|
|
|
|
updateAccount({
|
|
|
|
...account,
|
|
|
|
collectionOrder: updatedCollectionOrder,
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Droppable droppableId="collections">
|
|
|
|
{(provided) => (
|
|
|
|
<div ref={provided.innerRef} {...provided.droppableProps}>
|
2024-02-22 02:04:01 -06:00
|
|
|
{localCollectionOrder?.map((collectionId, index) => {
|
2024-02-22 01:51:51 -06:00
|
|
|
const collection = collections.find((c) => c.id === collectionId);
|
|
|
|
|
|
|
|
if (collection) {
|
|
|
|
return (
|
|
|
|
<Draggable
|
|
|
|
key={collection.id}
|
|
|
|
draggableId={`collection-${collection.id}`}
|
|
|
|
index={index}
|
|
|
|
>
|
|
|
|
{(provided) => (
|
|
|
|
<CollectionItem
|
|
|
|
innerRef={provided.innerRef}
|
|
|
|
{...provided.draggableProps}
|
|
|
|
{...provided.dragHandleProps}
|
|
|
|
key={index}
|
|
|
|
collection={collection}
|
|
|
|
active={active}
|
|
|
|
collections={collections}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</Draggable>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
})}
|
|
|
|
{provided.placeholder}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</Droppable>
|
|
|
|
</DragDropContext>
|
2024-02-05 01:42:54 -06:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default CollectionSelection;
|
|
|
|
|
|
|
|
const CollectionItem = ({
|
|
|
|
collection,
|
|
|
|
active,
|
|
|
|
collections,
|
2024-02-22 01:51:51 -06:00
|
|
|
innerRef,
|
|
|
|
...props
|
2024-02-05 01:42:54 -06:00
|
|
|
}: {
|
|
|
|
collection: CollectionIncludingMembersAndLinkCount;
|
|
|
|
active: string;
|
|
|
|
collections: CollectionIncludingMembersAndLinkCount[];
|
2024-02-22 02:04:01 -06:00
|
|
|
innerRef?: DraggableProvided["innerRef"];
|
2024-02-05 01:42:54 -06:00
|
|
|
}) => {
|
2024-02-06 06:46:57 -06:00
|
|
|
const hasChildren = collections.some((e) => e.parentId === collection.id);
|
2024-02-05 01:42:54 -06:00
|
|
|
|
2024-02-06 06:44:08 -06:00
|
|
|
// Check if the current collection or any of its subcollections is active
|
|
|
|
const isActiveOrParentOfActive = React.useMemo(() => {
|
|
|
|
const isActive = active === `/collections/${collection.id}`;
|
|
|
|
if (isActive) return true;
|
|
|
|
|
|
|
|
const checkIfParentOfActive = (parentId: number): boolean => {
|
|
|
|
return collections.some((e) => {
|
|
|
|
if (e.id === parseInt(active.split("/collections/")[1])) {
|
|
|
|
if (e.parentId === parentId) return true;
|
|
|
|
if (e.parentId) return checkIfParentOfActive(e.parentId);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return checkIfParentOfActive(collection.id as number);
|
|
|
|
}, [active, collection.id, collections]);
|
|
|
|
|
|
|
|
const [isOpen, setIsOpen] = useState(isActiveOrParentOfActive);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setIsOpen(isActiveOrParentOfActive);
|
|
|
|
}, [isActiveOrParentOfActive]);
|
|
|
|
|
2024-02-05 01:42:54 -06:00
|
|
|
return hasChildren ? (
|
2024-02-19 15:16:53 -06:00
|
|
|
<>
|
|
|
|
<div
|
2024-02-22 01:51:51 -06:00
|
|
|
ref={innerRef}
|
|
|
|
{...props}
|
|
|
|
className={`${active === `/collections/${collection.id}`
|
|
|
|
? "bg-primary/20"
|
|
|
|
: "hover:bg-neutral/20"
|
|
|
|
} duration-100 rounded-md flex w-full items-center cursor-pointer mb-1 px-2 gap-1`}
|
2024-02-05 01:42:54 -06:00
|
|
|
>
|
2024-02-19 15:16:53 -06:00
|
|
|
<button
|
|
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
|
|
className="flex items-center"
|
|
|
|
>
|
|
|
|
<i
|
2024-02-22 01:51:51 -06:00
|
|
|
className={`bi-chevron-down ${isOpen ? "rotate-reverse" : "rotate"
|
|
|
|
}`}
|
2024-02-19 15:16:53 -06:00
|
|
|
></i>
|
|
|
|
</button>
|
2024-02-05 01:42:54 -06:00
|
|
|
<Link href={`/collections/${collection.id}`} className="w-full">
|
|
|
|
<div
|
|
|
|
className={`py-1 cursor-pointer flex items-center gap-2 w-full h-8 capitalize`}
|
|
|
|
>
|
|
|
|
<i
|
|
|
|
className="bi-folder-fill text-2xl drop-shadow"
|
|
|
|
style={{ color: collection.color }}
|
|
|
|
></i>
|
|
|
|
<p className="truncate w-full">{collection.name}</p>
|
|
|
|
|
|
|
|
{collection.isPublic ? (
|
|
|
|
<i
|
|
|
|
className="bi-globe2 text-sm text-black/50 dark:text-white/50 drop-shadow"
|
|
|
|
title="This collection is being shared publicly."
|
|
|
|
></i>
|
|
|
|
) : undefined}
|
|
|
|
<div className="drop-shadow text-neutral text-xs">
|
|
|
|
{collection._count?.links}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Link>
|
2024-02-19 15:16:53 -06:00
|
|
|
</div>
|
|
|
|
{isOpen && hasChildren && (
|
|
|
|
<div className="ml-4 pl-1 border-l border-neutral-content">
|
2024-02-05 01:42:54 -06:00
|
|
|
{collections
|
|
|
|
.filter((e) => e.parentId === collection.id)
|
|
|
|
.map((subCollection) => (
|
|
|
|
<CollectionItem
|
|
|
|
key={subCollection.id}
|
|
|
|
collection={subCollection}
|
|
|
|
active={active}
|
|
|
|
collections={collections}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
)}
|
2024-02-19 15:16:53 -06:00
|
|
|
</>
|
2024-02-05 01:42:54 -06:00
|
|
|
) : (
|
2024-02-22 01:51:51 -06:00
|
|
|
<Link ref={innerRef} {...props} href={`/collections/${collection.id}`} className="w-full">
|
2024-02-05 01:42:54 -06:00
|
|
|
<div
|
2024-02-22 01:51:51 -06:00
|
|
|
className={`${active === `/collections/${collection.id}`
|
|
|
|
? "bg-primary/20"
|
|
|
|
: "hover:bg-neutral/20"
|
|
|
|
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize mb-1`}
|
2024-02-05 01:42:54 -06:00
|
|
|
>
|
|
|
|
<i
|
|
|
|
className="bi-folder-fill text-2xl drop-shadow"
|
|
|
|
style={{ color: collection.color }}
|
|
|
|
></i>
|
|
|
|
<p className="truncate w-full">{collection.name}</p>
|
|
|
|
|
|
|
|
{collection.isPublic ? (
|
|
|
|
<i
|
|
|
|
className="bi-globe2 text-sm text-black/50 dark:text-white/50 drop-shadow"
|
|
|
|
title="This collection is being shared publicly."
|
|
|
|
></i>
|
|
|
|
) : undefined}
|
|
|
|
<div className="drop-shadow text-neutral text-xs">
|
|
|
|
{collection._count?.links}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Link>
|
|
|
|
);
|
|
|
|
};
|