diff --git a/components/CollectionListing.tsx b/components/CollectionListing.tsx index a8d580b..01d81d0 100644 --- a/components/CollectionListing.tsx +++ b/components/CollectionListing.tsx @@ -1,110 +1,243 @@ +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 { CollectionIncludingMembersAndLinkCount } from "@/types/global"; +import { Collection } from "@prisma/client"; import Link from "next/link"; +import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; import { useRouter } from "next/router"; -import React, { useEffect, useState } from "react"; +import useAccountStore from "@/store/account"; +import toast from "react-hot-toast"; -type Props = { - links: boolean; -}; +interface ExtendedTreeItem extends TreeItem { + data: Collection; +} + +const CollectionListing = () => { + const { collections, updateCollection } = useCollectionStore(); + const { account, updateAccount } = useAccountStore(); -const CollectionSelection = ({ links }: Props) => { - const { collections } = useCollectionStore(); - const [active, setActive] = useState(""); 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(() => { - setActive(router.asPath); - }, [router, collections]); + 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 ( -
- {collections[0] ? ( - collections - .sort((a, b) => a.name.localeCompare(b.name)) - .filter((e) => e.parentId === null) - .map((e, i) => ( - - )) - ) : ( -
-

- You Have No Collections... -

-
- )} -
- ); -}; - -export default CollectionSelection; - -const CollectionItem = ({ - collection, - active, - collections, -}: { - collection: CollectionIncludingMembersAndLinkCount; - active: string; - collections: CollectionIncludingMembersAndLinkCount[]; -}) => { - const hasChildren = collections.some((e) => e.parentId === collection.id); - - const router = useRouter(); - - // 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]); - - return hasChildren ? ( - <> +
- -
- {isOpen && hasChildren && ( -
- {collections - .filter((e) => e.parentId === collection.id) - .map((subCollection) => ( - - ))} -
- )} - - ) : ( - -
- -

{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 }; +}; diff --git a/components/ModalContent/NewCollectionModal.tsx b/components/ModalContent/NewCollectionModal.tsx index 7c73500..80bf7c3 100644 --- a/components/ModalContent/NewCollectionModal.tsx +++ b/components/ModalContent/NewCollectionModal.tsx @@ -6,6 +6,8 @@ import { HexColorPicker } from "react-colorful"; import { Collection } from "@prisma/client"; import Modal from "../Modal"; import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; +import useAccountStore from "@/store/account"; +import { useSession } from "next-auth/react"; type Props = { onClose: Function; @@ -21,6 +23,8 @@ export default function NewCollectionModal({ onClose, parent }: Props) { } as Partial; const [collection, setCollection] = useState>(initial); + const { setAccount } = useAccountStore(); + const { data } = useSession(); useEffect(() => { setCollection(initial); @@ -42,7 +46,11 @@ export default function NewCollectionModal({ onClose, parent }: Props) { if (response.ok) { toast.success("Created!"); - onClose(); + if (response.data) { + // If the collection was created successfully, we need to get the new collection order + setAccount(data?.user.id as number); + onClose(); + } } else toast.error(response.data as string); setSubmitLoader(false); diff --git a/components/Navbar.tsx b/components/Navbar.tsx index eee0208..04a5d76 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -56,7 +56,7 @@ export default function Navbar() { setSidebar(true); document.body.style.overflow = "hidden"; }} - className="text-neutral btn btn-square btn-sm btn-ghost lg:hidden hidden sm:inline-flex" + className="text-neutral btn btn-square btn-sm btn-ghost lg:hidden sm:inline-flex" >
@@ -65,7 +65,7 @@ export default function Navbar() {
-
+
{ localStorage.setItem("tagDisclosure", tagDisclosure ? "true" : "false"); }, [tagDisclosure]); @@ -99,7 +98,7 @@ export default function Sidebar({ className }: { className?: string }) { leaveTo="transform opacity-0 -translate-y-3" > - + diff --git a/components/StrictModeDroppable.tsx b/components/StrictModeDroppable.tsx new file mode 100644 index 0000000..a6ac7eb --- /dev/null +++ b/components/StrictModeDroppable.tsx @@ -0,0 +1,19 @@ +// StrictModeDroppable.tsx + +import { useEffect, useState } from "react"; +import { Droppable, DroppableProps } from "react-beautiful-dnd"; + +export const StrictModeDroppable = ({ children, ...props }: DroppableProps) => { + const [enabled, setEnabled] = useState(false); + useEffect(() => { + const animation = requestAnimationFrame(() => setEnabled(true)); + return () => { + cancelAnimationFrame(animation); + setEnabled(false); + }; + }, []); + if (!enabled) { + return null; + } + return {children}; +}; diff --git a/lib/api/controllers/collections/collectionId/deleteCollectionById.ts b/lib/api/controllers/collections/collectionId/deleteCollectionById.ts index 98b1e17..e8fe51a 100644 --- a/lib/api/controllers/collections/collectionId/deleteCollectionById.ts +++ b/lib/api/controllers/collections/collectionId/deleteCollectionById.ts @@ -31,6 +31,8 @@ export default async function deleteCollection( }, }); + await removeFromOrders(userId, collectionId); + return { response: deletedUsersAndCollectionsRelation, status: 200 }; } else if (collectionIsAccessible?.ownerId !== userId) { return { response: "Collection is not accessible.", status: 401 }; @@ -57,6 +59,8 @@ export default async function deleteCollection( await removeFolder({ filePath: `archives/${collectionId}` }); + await removeFromOrders(userId, collectionId); + return await prisma.collection.delete({ where: { id: collectionId, @@ -98,3 +102,28 @@ async function deleteSubCollections(collectionId: number) { await removeFolder({ filePath: `archives/${subCollection.id}` }); } } + +async function removeFromOrders(userId: number, collectionId: number) { + const userCollectionOrder = await prisma.user.findUnique({ + where: { + id: userId, + }, + select: { + collectionOrder: true, + }, + }); + + if (userCollectionOrder) + await prisma.user.update({ + where: { + id: userId, + }, + data: { + collectionOrder: { + set: userCollectionOrder.collectionOrder.filter( + (e: number) => e !== collectionId + ), + }, + }, + }); +} diff --git a/lib/api/controllers/collections/collectionId/updateCollectionById.ts b/lib/api/controllers/collections/collectionId/updateCollectionById.ts index ca9b789..4bb4d2f 100644 --- a/lib/api/controllers/collections/collectionId/updateCollectionById.ts +++ b/lib/api/controllers/collections/collectionId/updateCollectionById.ts @@ -18,24 +18,30 @@ export default async function updateCollection( if (!(collectionIsAccessible?.ownerId === userId)) return { response: "Collection is not accessible.", status: 401 }; - if (data.parentId) { - const findParentCollection = await prisma.collection.findUnique({ - where: { - id: data.parentId, - }, - select: { - ownerId: true, - }, - }); + console.log(data); - if ( - findParentCollection?.ownerId !== userId || - typeof data.parentId !== "number" - ) - return { - response: "You are not authorized to create a sub-collection here.", - status: 403, - }; + if (data.parentId) { + if (data.parentId !== ("root" as any)) { + const findParentCollection = await prisma.collection.findUnique({ + where: { + id: data.parentId, + }, + 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 () => { @@ -56,13 +62,18 @@ export default async function updateCollection( description: data.description, color: data.color, isPublic: data.isPublic, - parent: data.parentId - ? { - connect: { - id: data.parentId, - }, - } - : undefined, + parent: + data.parentId && data.parentId !== ("root" as any) + ? { + connect: { + id: data.parentId, + }, + } + : data.parentId === ("root" as any) + ? { + disconnect: true, + } + : undefined, members: { create: data.members.map((e) => ({ user: { connect: { id: e.user.id || e.userId } }, diff --git a/lib/api/controllers/collections/postCollection.ts b/lib/api/controllers/collections/postCollection.ts index 94f4763..0969f1c 100644 --- a/lib/api/controllers/collections/postCollection.ts +++ b/lib/api/controllers/collections/postCollection.ts @@ -44,10 +44,10 @@ export default async function postCollection( color: collection.color, parent: collection.parentId ? { - connect: { - id: collection.parentId, - }, - } + connect: { + id: collection.parentId, + }, + } : undefined, }, include: { @@ -67,6 +67,17 @@ export default async function postCollection( }, }); + await prisma.user.update({ + where: { + id: userId, + }, + data: { + collectionOrder: { + push: newCollection.id, + }, + }, + }); + createFolder({ filePath: `archives/${newCollection.id}` }); return { response: newCollection, status: 200 }; diff --git a/lib/api/controllers/users/userId/updateUserById.ts b/lib/api/controllers/users/userId/updateUserById.ts index 6bb7480..e4e5726 100644 --- a/lib/api/controllers/users/userId/updateUserById.ts +++ b/lib/api/controllers/users/userId/updateUserById.ts @@ -183,6 +183,9 @@ export default async function updateUserById( email: data.email?.toLowerCase().trim(), isPrivate: data.isPrivate, image: data.image ? `uploads/avatar/${userId}.jpg` : "", + collectionOrder: data.collectionOrder.filter( + (value, index, self) => self.indexOf(value) === index + ), archiveAsScreenshot: data.archiveAsScreenshot, archiveAsPDF: data.archiveAsPDF, archiveAsWaybackMachine: data.archiveAsWaybackMachine, diff --git a/package.json b/package.json index 8e5b900..9193b5b 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "format": "prettier --write \"**/*.{ts,tsx,js,json,md}\"" }, "dependencies": { + "@atlaskit/tree": "^8.8.7", "@auth/prisma-adapter": "^1.0.1", "@aws-sdk/client-s3": "^3.379.1", "@headlessui/react": "^1.7.15", @@ -30,6 +31,7 @@ "@types/node": "^20.10.4", "@types/nodemailer": "^6.4.8", "@types/react": "18.2.14", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "18.2.7", "axios": "^1.5.1", "bcrypt": "^5.1.0", @@ -55,6 +57,7 @@ "nodemailer": "^6.9.3", "playwright": "^1.35.1", "react": "18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-colorful": "^5.6.1", "react-dom": "18.2.0", "react-hot-toast": "^2.4.1", diff --git a/prisma/migrations/20240222050805_collection_order/migration.sql b/prisma/migrations/20240222050805_collection_order/migration.sql new file mode 100644 index 0000000..ae5687f --- /dev/null +++ b/prisma/migrations/20240222050805_collection_order/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "collectionOrder" INTEGER[] DEFAULT ARRAY[]::INTEGER[]; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4d9ccec..6bdc8de 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -38,6 +38,7 @@ model User { tags Tag[] pinnedLinks Link[] collectionsJoined UsersAndCollections[] + collectionOrder Int[] @default([]) whitelistedUsers WhitelistedUser[] accessTokens AccessToken[] subscriptions Subscription? diff --git a/types/global.ts b/types/global.ts index 3c8de79..e77454d 100644 --- a/types/global.ts +++ b/types/global.ts @@ -33,6 +33,7 @@ export interface CollectionIncludingMembersAndLinkCount id?: number; ownerId?: number; createdAt?: string; + updatedAt?: string; _count?: { links: number }; members: Member[]; } diff --git a/yarn.lock b/yarn.lock index 1b4d92a..1f6899a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,15 @@ resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== +"@atlaskit/tree@^8.8.7": + version "8.8.7" + resolved "https://registry.yarnpkg.com/@atlaskit/tree/-/tree-8.8.7.tgz#f895137b063f676a490abb0b5deb939a96f51fd7" + integrity sha512-ftbFCzZoa5tZh35EdwMEP9lPuBfw19vtB1CcBmDDMP0AnyEXLjUVfVo8kIls6oI4wivYfIWkZgrUlgN+Jk1b0Q== + dependencies: + "@babel/runtime" "^7.0.0" + css-box-model "^1.2.0" + react-beautiful-dnd-next "11.0.5" + "@auth/core@0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.9.0.tgz#7a5d66eea0bc059cef072734698547ae2a0c86a6" @@ -614,6 +623,21 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/runtime-corejs2@^7.4.5": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.24.0.tgz#23c12d76ac8a7a0ec223c4b0c3b937f9c203fa33" + integrity sha512-RZVGq1it0GA1K8rb+z7v7NzecP6VYCMedN7yHsCCIQUMmRXFCPJD8GISdf6uIGj7NDDihg7ieQEzpdpQbUL75Q== + dependencies: + core-js "^2.6.12" + regenerator-runtime "^0.14.0" + +"@babel/runtime@^7.0.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" + integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" @@ -628,6 +652,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.15.4", "@babel/runtime@^7.9.2": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" + integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.21.0": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d" @@ -1925,6 +1956,14 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" + integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/jsdom@^21.1.3": version "21.1.3" resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-21.1.3.tgz#a88c5dc65703e1b10b2a7839c12db49662b43ff0" @@ -1986,6 +2025,13 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== +"@types/react-beautiful-dnd@^13.1.8": + version "13.1.8" + resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.8.tgz#f52d3ea07e1e19159d6c3c4a48c8da3d855e60b4" + integrity sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ== + dependencies: + "@types/react" "*" + "@types/react-dom@18.2.7": version "18.2.7" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63" @@ -1993,6 +2039,16 @@ dependencies: "@types/react" "*" +"@types/react-redux@^7.1.20": + version "7.1.33" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.33.tgz#53c5564f03f1ded90904e3c90f77e4bd4dc20b15" + integrity sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react-transition-group@^4.4.0": version "4.4.5" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" @@ -2608,6 +2664,11 @@ cookie@0.5.0, cookie@^0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +core-js@^2.6.12: + version "2.6.12" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2643,6 +2704,13 @@ crypto-js@^4.2.0: resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== +css-box-model@^1.1.2, css-box-model@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" + integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== + dependencies: + tiny-invariant "^1.0.6" + css-selector-tokenizer@^0.8: version "0.8.0" resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz#88267ef6238e64f2215ea2764b3e2cf498b845dd" @@ -3725,7 +3793,7 @@ himalaya@^1.1.0: resolved "https://registry.yarnpkg.com/himalaya/-/himalaya-1.1.0.tgz#31724ae9d35714cd7c6f4be94888953f3604606a" integrity sha512-LLase1dHCRMel68/HZTFft0N0wti0epHr3nNY7ynpLbyZpmrKMQ8YIpiOV77TM97cNpC8Wb2n6f66IRggwdWPw== -hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -4312,6 +4380,11 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +memoize-one@^5.0.4, memoize-one@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + memoize-one@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" @@ -4984,7 +5057,7 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -5035,6 +5108,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +raf-schd@^4.0.0, raf-schd@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" + integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== + raw-body@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" @@ -5045,6 +5123,33 @@ raw-body@2.4.1: iconv-lite "0.4.24" unpipe "1.0.0" +react-beautiful-dnd-next@11.0.5: + version "11.0.5" + resolved "https://registry.yarnpkg.com/react-beautiful-dnd-next/-/react-beautiful-dnd-next-11.0.5.tgz#41e693733bbdeb6269b9e4b923a36de2e99ed761" + integrity sha512-kM5Mob41HkA3ShS9uXqeMkW51L5bVsfttxfrwwHucu7I6SdnRKCyN78t6QiLH/UJQQ8T4ukI6NeQAQQpGwolkg== + dependencies: + "@babel/runtime-corejs2" "^7.4.5" + css-box-model "^1.1.2" + memoize-one "^5.0.4" + raf-schd "^4.0.0" + react-redux "^7.0.3" + redux "^4.0.1" + tiny-invariant "^1.0.4" + use-memo-one "^1.1.0" + +react-beautiful-dnd@^13.1.1: + version "13.1.1" + resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2" + integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ== + dependencies: + "@babel/runtime" "^7.9.2" + css-box-model "^1.2.0" + memoize-one "^5.1.1" + raf-schd "^4.0.2" + react-redux "^7.2.0" + redux "^4.0.4" + use-memo-one "^1.1.1" + react-colorful@^5.6.1: version "5.6.1" resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b" @@ -5075,6 +5180,23 @@ react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-redux@^7.0.3, react-redux@^7.2.0: + version "7.2.9" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" + integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/react-redux" "^7.1.20" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^17.0.2" + react-remove-scroll-bar@^2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9" @@ -5165,6 +5287,13 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +redux@^4.0.0, redux@^4.0.1, redux@^4.0.4: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.3: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" @@ -5688,6 +5817,16 @@ tiny-glob@^0.2.9: globalyzer "0.1.0" globrex "^0.1.2" +tiny-invariant@^1.0.4: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +tiny-invariant@^1.0.6: + version "1.3.1" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" + integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== + tinycolor2@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" @@ -5924,6 +6063,11 @@ use-isomorphic-layout-effect@^1.1.2: resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== +use-memo-one@^1.1.0, use-memo-one@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" + integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ== + use-sidecar@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"