fully added subcollection tree functionality
This commit is contained in:
parent
51cf8172ff
commit
837241186f
|
@ -14,21 +14,27 @@ import { Collection } from "@prisma/client";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import useAccountStore from "@/store/account";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
interface ExtendedTreeItem extends TreeItem {
|
interface ExtendedTreeItem extends TreeItem {
|
||||||
data: Collection;
|
data: Collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollectionListing = () => {
|
const CollectionListing = () => {
|
||||||
const { collections } = useCollectionStore();
|
const { collections, updateCollection } = useCollectionStore();
|
||||||
|
const { account, updateAccount } = useAccountStore();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const currentPath = router.asPath;
|
const currentPath = router.asPath;
|
||||||
|
|
||||||
const initialTree = useMemo(() => {
|
const initialTree = useMemo(() => {
|
||||||
if (collections.length > 0) {
|
if (collections.length > 0) {
|
||||||
return buildTreeFromCollections(collections, router);
|
return buildTreeFromCollections(
|
||||||
|
collections,
|
||||||
|
router,
|
||||||
|
account.collectionOrder
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [collections, router]);
|
}, [collections, router]);
|
||||||
|
@ -39,26 +45,155 @@ const CollectionListing = () => {
|
||||||
setTree(initialTree);
|
setTree(initialTree);
|
||||||
}, [initialTree]);
|
}, [initialTree]);
|
||||||
|
|
||||||
const onExpand = (itemId: ItemId) => {
|
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) =>
|
setTree((currentTree) =>
|
||||||
mutateTree(currentTree!, itemId, { isExpanded: true })
|
mutateTree(currentTree!, movedCollectionId, { isExpanded: true })
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCollapse = (itemId: ItemId) => {
|
const onCollapse = (movedCollectionId: ItemId) => {
|
||||||
setTree((currentTree) =>
|
setTree((currentTree) =>
|
||||||
mutateTree(currentTree as TreeData, itemId, { isExpanded: false })
|
mutateTree(currentTree as TreeData, movedCollectionId, {
|
||||||
|
isExpanded: false,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragEnd = (
|
const onDragEnd = async (
|
||||||
source: TreeSourcePosition,
|
source: TreeSourcePosition,
|
||||||
destination: TreeDestinationPosition | undefined
|
destination: TreeDestinationPosition | undefined
|
||||||
) => {
|
) => {
|
||||||
if (!destination || !tree) {
|
if (!destination || !tree) {
|
||||||
return;
|
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));
|
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) {
|
if (!tree) {
|
||||||
|
@ -148,8 +283,15 @@ const Icon = (
|
||||||
|
|
||||||
const buildTreeFromCollections = (
|
const buildTreeFromCollections = (
|
||||||
collections: CollectionIncludingMembersAndLinkCount[],
|
collections: CollectionIncludingMembersAndLinkCount[],
|
||||||
router: ReturnType<typeof useRouter>
|
router: ReturnType<typeof useRouter>,
|
||||||
|
order?: number[]
|
||||||
): TreeData => {
|
): 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(
|
const items: { [key: string]: ExtendedTreeItem } = collections.reduce(
|
||||||
(acc: any, collection) => {
|
(acc: any, collection) => {
|
||||||
acc[collection.id as number] = {
|
acc[collection.id as number] = {
|
||||||
|
@ -177,8 +319,6 @@ const buildTreeFromCollections = (
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("items:", items);
|
|
||||||
|
|
||||||
const activeCollectionId = Number(router.asPath.split("/collections/")[1]);
|
const activeCollectionId = Number(router.asPath.split("/collections/")[1]);
|
||||||
|
|
||||||
if (activeCollectionId) {
|
if (activeCollectionId) {
|
||||||
|
|
|
@ -65,7 +65,7 @@ export default function Navbar() {
|
||||||
<ToggleDarkMode className="hidden sm:inline-grid" />
|
<ToggleDarkMode className="hidden sm:inline-grid" />
|
||||||
|
|
||||||
<div className="dropdown dropdown-end sm:inline-block hidden">
|
<div className="dropdown dropdown-end sm:inline-block hidden">
|
||||||
<div className="tooltip tooltip-bottom" data-tip="Create New...">
|
<div className="tooltip tooltip-bottom z-10" data-tip="Create New...">
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
|
|
|
@ -18,24 +18,30 @@ export default async function updateCollection(
|
||||||
if (!(collectionIsAccessible?.ownerId === userId))
|
if (!(collectionIsAccessible?.ownerId === userId))
|
||||||
return { response: "Collection is not accessible.", status: 401 };
|
return { response: "Collection is not accessible.", status: 401 };
|
||||||
|
|
||||||
if (data.parentId) {
|
console.log(data);
|
||||||
const findParentCollection = await prisma.collection.findUnique({
|
|
||||||
where: {
|
|
||||||
id: data.parentId,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
ownerId: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
if (data.parentId) {
|
||||||
findParentCollection?.ownerId !== userId ||
|
if (data.parentId !== ("root" as any)) {
|
||||||
typeof data.parentId !== "number"
|
const findParentCollection = await prisma.collection.findUnique({
|
||||||
)
|
where: {
|
||||||
return {
|
id: data.parentId,
|
||||||
response: "You are not authorized to create a sub-collection here.",
|
},
|
||||||
status: 403,
|
select: {
|
||||||
};
|
ownerId: true,
|
||||||
|
parentId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
findParentCollection?.ownerId !== userId ||
|
||||||
|
typeof data.parentId !== "number" ||
|
||||||
|
findParentCollection?.parentId === data.parentId
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
response: "You are not authorized to create a sub-collection here.",
|
||||||
|
status: 403,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedCollection = await prisma.$transaction(async () => {
|
const updatedCollection = await prisma.$transaction(async () => {
|
||||||
|
@ -47,17 +53,6 @@ export default async function updateCollection(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.user.update({
|
|
||||||
where: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
collectionOrder: {
|
|
||||||
push: collectionId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await prisma.collection.update({
|
return await prisma.collection.update({
|
||||||
where: {
|
where: {
|
||||||
id: collectionId,
|
id: collectionId,
|
||||||
|
@ -67,13 +62,18 @@ export default async function updateCollection(
|
||||||
description: data.description,
|
description: data.description,
|
||||||
color: data.color,
|
color: data.color,
|
||||||
isPublic: data.isPublic,
|
isPublic: data.isPublic,
|
||||||
parent: data.parentId
|
parent:
|
||||||
? {
|
data.parentId && data.parentId !== ("root" as any)
|
||||||
connect: {
|
? {
|
||||||
id: data.parentId,
|
connect: {
|
||||||
},
|
id: data.parentId,
|
||||||
}
|
},
|
||||||
: undefined,
|
}
|
||||||
|
: data.parentId === ("root" as any)
|
||||||
|
? {
|
||||||
|
disconnect: true,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
members: {
|
members: {
|
||||||
create: data.members.map((e) => ({
|
create: data.members.map((e) => ({
|
||||||
user: { connect: { id: e.user.id || e.userId } },
|
user: { connect: { id: e.user.id || e.userId } },
|
||||||
|
|
|
@ -183,7 +183,9 @@ export default async function updateUserById(
|
||||||
email: data.email?.toLowerCase().trim(),
|
email: data.email?.toLowerCase().trim(),
|
||||||
isPrivate: data.isPrivate,
|
isPrivate: data.isPrivate,
|
||||||
image: data.image ? `uploads/avatar/${userId}.jpg` : "",
|
image: data.image ? `uploads/avatar/${userId}.jpg` : "",
|
||||||
collectionOrder: data.collectionOrder,
|
collectionOrder: data.collectionOrder.filter(
|
||||||
|
(value, index, self) => self.indexOf(value) === index
|
||||||
|
),
|
||||||
archiveAsScreenshot: data.archiveAsScreenshot,
|
archiveAsScreenshot: data.archiveAsScreenshot,
|
||||||
archiveAsPDF: data.archiveAsPDF,
|
archiveAsPDF: data.archiveAsPDF,
|
||||||
archiveAsWaybackMachine: data.archiveAsWaybackMachine,
|
archiveAsWaybackMachine: data.archiveAsWaybackMachine,
|
||||||
|
|
Ŝarĝante…
Reference in New Issue