import { useState, FC, useRef, useEffect } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { ChevronDown, ChevronRight, Plus } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; // types import { IProject } from "@plane/types"; // ui import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { CreateProjectModal, ProjectSidebarListItem } from "@/components/project"; // constants import { EUserWorkspaceRoles } from "@/constants/workspace"; // helpers import { cn } from "@/helpers/common.helper"; import { orderJoinedProjects } from "@/helpers/project.helper"; import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useAppTheme, useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; export const ProjectSidebarList: FC = observer(() => { // get local storage data for isFavoriteProjectsListOpen and isAllProjectsListOpen const isFavProjectsListOpenInLocalStorage = localStorage.getItem("isFavoriteProjectsListOpen"); const isAllProjectsListOpenInLocalStorage = localStorage.getItem("isAllProjectsListOpen"); // states const [isFavoriteProjectsListOpen, setIsFavoriteProjectsListOpen] = useState( isFavProjectsListOpenInLocalStorage === "true" ); const [isAllProjectsListOpen, setIsAllProjectsListOpen] = useState(isAllProjectsListOpenInLocalStorage === "true"); const [isFavoriteProjectCreate, setIsFavoriteProjectCreate] = useState(false); const [isProjectModalOpen, setIsProjectModalOpen] = useState(false); const [isScrolled, setIsScrolled] = useState(false); // scroll animation state // refs const containerRef = useRef(null); // store hooks const { toggleCreateProjectModal } = useCommandPalette(); const { sidebarCollapsed } = useAppTheme(); const { setTrackElement } = useEventTracker(); const { membership: { currentWorkspaceRole }, } = useUser(); const { getProjectById, joinedProjectIds: joinedProjects, favoriteProjectIds: favoriteProjects, updateProjectView, } = useProject(); // router params const { workspaceSlug } = useParams(); const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; const handleCopyText = (projectId: string) => { copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/issues`).then(() => { setToast({ type: TOAST_TYPE.SUCCESS, title: "Link Copied!", message: "Project link copied to clipboard.", }); }); }; const handleOnProjectDrop = ( sourceId: string | undefined, destinationId: string | undefined, shouldDropAtEnd: boolean ) => { if (!sourceId || !destinationId || !workspaceSlug) return; if (sourceId === destinationId) return; const joinedProjectsList: IProject[] = []; joinedProjects.map((projectId) => { const projectDetails = getProjectById(projectId); if (projectDetails) joinedProjectsList.push(projectDetails); }); const sourceIndex = joinedProjects.indexOf(sourceId); const destinationIndex = shouldDropAtEnd ? joinedProjects.length : joinedProjects.indexOf(destinationId); if (joinedProjectsList.length <= 0) return; const updatedSortOrder = orderJoinedProjects(sourceIndex, destinationIndex, sourceId, joinedProjectsList); if (updatedSortOrder != undefined) updateProjectView(workspaceSlug.toString(), sourceId, { sort_order: updatedSortOrder }).catch(() => { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong. Please try again.", }); }); }; const isCollapsed = sidebarCollapsed || false; /** * Implementing scroll animation styles based on the scroll length of the container */ useEffect(() => { const handleScroll = () => { if (containerRef.current) { const scrollTop = containerRef.current.scrollTop; setIsScrolled(scrollTop > 0); } }; const currentContainerRef = containerRef.current; if (currentContainerRef) { currentContainerRef.addEventListener("scroll", handleScroll); } return () => { if (currentContainerRef) { currentContainerRef.removeEventListener("scroll", handleScroll); } }; }, [containerRef]); useEffect(() => { const element = containerRef.current; if (!element) return; return combine( autoScrollForElements({ element, canScroll: ({ source }) => source?.data?.dragInstanceId === "PROJECTS", getAllowedAxis: () => "vertical", }) ); }, [containerRef]); const toggleListDisclosure = (isOpen: boolean, type: "all" | "favorite") => { if (type === "all") { setIsAllProjectsListOpen(isOpen); localStorage.setItem("isAllProjectsListOpen", isOpen.toString()); } else { setIsFavoriteProjectsListOpen(isOpen); localStorage.setItem("isFavoriteProjectsListOpen", isOpen.toString()); } }; return ( <> {workspaceSlug && ( { setIsProjectModalOpen(false); }} setToFavorite={isFavoriteProjectCreate} workspaceSlug={workspaceSlug.toString()} /> )}
{favoriteProjects && favoriteProjects.length > 0 && ( <> {!isCollapsed && (
toggleListDisclosure(!isFavoriteProjectsListOpen, "favorite")} > Favorites {isFavoriteProjectsListOpen ? ( ) : ( )} {isAuthorizedUser && ( )}
)} {isFavoriteProjectsListOpen && ( {favoriteProjects.map((projectId, index) => ( handleCopyText(projectId)} projectListType="FAVORITES" disableDrag disableDrop isLastChild={index === favoriteProjects.length - 1} /> ))} )}
)}
{joinedProjects && joinedProjects.length > 0 && ( <> {!isCollapsed && (
toggleListDisclosure(!isAllProjectsListOpen, "all")} > Your projects {isAllProjectsListOpen ? ( ) : ( )} {isAuthorizedUser && ( )}
)} {isAllProjectsListOpen && ( {joinedProjects.map((projectId, index) => ( handleCopyText(projectId)} isLastChild={index === joinedProjects.length - 1} handleOnProjectDrop={handleOnProjectDrop} /> ))} )}
)}
{isAuthorizedUser && joinedProjects && joinedProjects.length === 0 && ( )}
); });