Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 160 additions & 65 deletions apps/app/components/project/sidebar-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { mutate } from "swr";

// react-beautiful-dnd
import { DragDropContext, Draggable, DropResult, Droppable } from "react-beautiful-dnd";
// headless ui
import { Disclosure } from "@headlessui/react";
// hooks
import useToast from "hooks/use-toast";
import useTheme from "hooks/use-theme";
Expand All @@ -15,6 +17,7 @@ import { DeleteProjectModal, SingleSidebarProject } from "components/project";
// services
import projectService from "services/project.service";
// icons
import { Icon } from "components/ui";
import { PlusIcon } from "@heroicons/react/24/outline";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
Expand All @@ -30,23 +33,26 @@ export const ProjectSidebarList: FC = () => {

// router
const router = useRouter();
const { workspaceSlug } = router.query;
const { workspaceSlug, projectId } = router.query;

const { user } = useUserAuth();

const { collapsed: sidebarCollapse } = useTheme();
const { setToastAlert } = useToast();

const { projects: allProjects } = useProjects();

const joinedProjects = allProjects?.filter((p) => p.sort_order);
const favoriteProjects = allProjects?.filter((p) => p.is_favorite);
const otherProjects = allProjects?.filter((p) => p.sort_order === null);

const orderedAllProjects = allProjects
? orderArrayBy(allProjects, "sort_order", "ascending")
: [];
const orderedJoinedProjects: IProject[] | undefined = joinedProjects
? orderArrayBy(joinedProjects, "sort_order", "ascending")
: undefined;

const orderedFavProjects = favoriteProjects
const orderedFavProjects: IProject[] | undefined = favoriteProjects
? orderArrayBy(favoriteProjects, "sort_order", "ascending")
: [];
: undefined;

const handleDeleteProject = (project: IProject) => {
setProjectToDelete(project);
Expand All @@ -69,22 +75,25 @@ export const ProjectSidebarList: FC = () => {
const { source, destination, draggableId } = result;

if (!destination || !workspaceSlug) return;

if (source.index === destination.index) return;

const projectList =
destination.droppableId === "all-projects" ? orderedAllProjects : orderedFavProjects;
const projectsList =
(destination.droppableId === "joined-projects"
? orderedJoinedProjects
: orderedFavProjects) ?? [];

let updatedSortOrder = projectList[source.index].sort_order;
if (destination.index === 0) {
updatedSortOrder = projectList[0].sort_order - 1000;
} else if (destination.index === projectList.length - 1) {
updatedSortOrder = projectList[projectList.length - 1].sort_order + 1000;
} else {
const destinationSortingOrder = projectList[destination.index].sort_order;
let updatedSortOrder = projectsList[source.index].sort_order;

if (destination.index === 0) updatedSortOrder = (projectsList[0].sort_order as number) - 1000;
else if (destination.index === projectsList.length - 1)
updatedSortOrder = (projectsList[projectsList.length - 1].sort_order as number) + 1000;
else {
const destinationSortingOrder = projectsList[destination.index].sort_order as number;
const relativeDestinationSortingOrder =
source.index < destination.index
? projectList[destination.index + 1].sort_order
: projectList[destination.index - 1].sort_order;
? (projectsList[destination.index + 1].sort_order as number)
: (projectsList[destination.index - 1].sort_order as number);

updatedSortOrder = Math.round(
(destinationSortingOrder + relativeDestinationSortingOrder) / 2
Expand Down Expand Up @@ -121,76 +130,162 @@ export const ProjectSidebarList: FC = () => {
data={projectToDelete}
user={user}
/>
<div className="h-full overflow-y-auto px-4">
<div className="h-full overflow-y-auto px-5 space-y-3 pt-3 border-t border-custom-sidebar-border-300">
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="favorite-projects">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{orderedFavProjects && orderedFavProjects.length > 0 && (
<div className="flex flex-col space-y-2 mt-5">
{!sidebarCollapse && (
<h5 className="text-sm font-medium text-custom-sidebar-text-200">
Favorites
</h5>
)}
{orderedFavProjects.map((project, index) => (
<Draggable key={project.id} draggableId={project.id} index={index}>
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps}>
<SingleSidebarProject
key={project.id}
project={project}
sidebarCollapse={sidebarCollapse}
provided={provided}
snapshot={snapshot}
handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)}
shortContextMenu
<Disclosure
as="div"
className="flex flex-col space-y-2"
defaultOpen={
projectId && orderedFavProjects.find((p) => p.id === projectId) ? true : false
}
>
{({ open }) => (
<>
{!sidebarCollapse && (
<Disclosure.Button
as="button"
type="button"
className="group flex items-center gap-1 px-1.5 text-xs font-semibold text-custom-sidebar-text-200 text-left hover:bg-custom-sidebar-background-80 rounded w-min whitespace-nowrap"
>
Favorites
<Icon
iconName={open ? "arrow_drop_down" : "arrow_right"}
className="group-hover:opacity-100 opacity-0 !text-lg"
/>
</div>
</Disclosure.Button>
)}
</Draggable>
))}
{provided.placeholder}
</div>
<Disclosure.Panel as="div" className="space-y-2">
{orderedFavProjects.map((project, index) => (
<Draggable
key={project.id}
draggableId={project.id}
index={index}
isDragDisabled={project.sort_order === null}
>
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps}>
<SingleSidebarProject
key={project.id}
project={project}
sidebarCollapse={sidebarCollapse}
provided={provided}
snapshot={snapshot}
handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)}
shortContextMenu
/>
</div>
)}
</Draggable>
))}
</Disclosure.Panel>
{provided.placeholder}
</>
)}
</Disclosure>
)}
</div>
)}
</Droppable>
</DragDropContext>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="all-projects">
<Droppable droppableId="joined-projects">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{orderedAllProjects && orderedAllProjects.length > 0 && (
<div className="flex flex-col space-y-2 mt-5">
{!sidebarCollapse && (
<h5 className="text-sm font-medium text-custom-sidebar-text-200">Projects</h5>
)}
{orderedAllProjects.map((project, index) => (
<Draggable key={project.id} draggableId={project.id} index={index}>
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps}>
<SingleSidebarProject
key={project.id}
project={project}
sidebarCollapse={sidebarCollapse}
provided={provided}
snapshot={snapshot}
handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)}
{orderedJoinedProjects && orderedJoinedProjects.length > 0 && (
<Disclosure
as="div"
className="flex flex-col space-y-2"
defaultOpen={
projectId && orderedJoinedProjects.find((p) => p.id === projectId)
? true
: false
}
>
{({ open }) => (
<>
{!sidebarCollapse && (
<Disclosure.Button
as="button"
type="button"
className="group flex items-center gap-1 px-1.5 text-xs font-semibold text-custom-sidebar-text-200 text-left hover:bg-custom-sidebar-background-80 rounded w-min whitespace-nowrap"
>
Projects
<Icon
iconName={open ? "arrow_drop_down" : "arrow_right"}
className="group-hover:opacity-100 opacity-0 !text-lg"
/>
</div>
</Disclosure.Button>
)}
</Draggable>
))}
{provided.placeholder}
</div>
<Disclosure.Panel as="div" className="space-y-2">
{orderedJoinedProjects.map((project, index) => (
<Draggable key={project.id} draggableId={project.id} index={index}>
{(provided, snapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps}>
<SingleSidebarProject
key={project.id}
project={project}
sidebarCollapse={sidebarCollapse}
provided={provided}
snapshot={snapshot}
handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)}
/>
</div>
)}
</Draggable>
))}
</Disclosure.Panel>
{provided.placeholder}
</>
)}
</Disclosure>
)}
</div>
)}
</Droppable>
</DragDropContext>
{otherProjects && otherProjects.length > 0 && (
<Disclosure
as="div"
className="flex flex-col space-y-2"
defaultOpen={projectId && otherProjects.find((p) => p.id === projectId) ? true : false}
>
{({ open }) => (
<>
{!sidebarCollapse && (
<Disclosure.Button
as="button"
type="button"
className="group flex items-center gap-1 px-1.5 text-xs font-semibold text-custom-sidebar-text-200 text-left hover:bg-custom-sidebar-background-80 rounded w-min whitespace-nowrap"
>
Other Projects
<Icon
iconName={open ? "arrow_drop_down" : "arrow_right"}
className="group-hover:opacity-100 opacity-0 !text-lg"
/>
</Disclosure.Button>
)}
<Disclosure.Panel as="div" className="space-y-2">
{otherProjects?.map((project, index) => (
<SingleSidebarProject
key={project.id}
project={project}
sidebarCollapse={sidebarCollapse}
handleDeleteProject={() => handleDeleteProject(project)}
handleCopyText={() => handleCopyText(project.id)}
shortContextMenu
/>
))}
</Disclosure.Panel>
</>
)}
</Disclosure>
)}
{allProjects && allProjects.length === 0 && (
<button
type="button"
Expand Down
46 changes: 25 additions & 21 deletions apps/app/components/project/single-sidebar-project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ import { PROJECTS_LIST } from "constants/fetch-keys";
type Props = {
project: IProject;
sidebarCollapse: boolean;
provided: DraggableProvided;
snapshot: DraggableStateSnapshot;
provided?: DraggableProvided;
snapshot?: DraggableStateSnapshot;
handleDeleteProject: () => void;
handleCopyText: () => void;
shortContextMenu?: boolean;
Expand Down Expand Up @@ -133,34 +133,36 @@ export const SingleSidebarProject: React.FC<Props> = ({
};

return (
<Disclosure key={project?.id} defaultOpen={projectId === project?.id}>
<Disclosure key={project.id} defaultOpen={projectId === project.id}>
{({ open }) => (
<>
<div
className={`group relative flex items-center gap-x-1 text-custom-sidebar-text-100 ${
snapshot.isDragging ? "opacity-60" : ""
className={`group relative text-custom-sidebar-text-10 px-2 py-1 w-full flex items-center hover:bg-custom-sidebar-background-80 rounded-md ${
snapshot?.isDragging ? "opacity-60" : ""
}`}
>
<button
type="button"
className={`absolute top-2 left-0 hidden rounded p-0.5 ${
sidebarCollapse ? "" : "group-hover:!flex"
}`}
{...provided.dragHandleProps}
>
<EllipsisVerticalIcon className="h-4" />
<EllipsisVerticalIcon className="-ml-5 h-4" />
</button>
{provided && (
<button
type="button"
className={`absolute top-1/2 -translate-y-1/2 -left-4 hidden rounded p-0.5 ${
sidebarCollapse ? "" : "group-hover:!flex"
}`}
{...provided?.dragHandleProps}
>
<EllipsisVerticalIcon className="h-4" />
<EllipsisVerticalIcon className="-ml-5 h-4" />
</button>
)}
<Tooltip
tooltipContent={`${project?.name}`}
tooltipContent={`${project.name}`}
position="right"
className="ml-2"
disabled={!sidebarCollapse}
>
<Disclosure.Button
as="div"
className={`flex w-full cursor-pointer select-none items-center rounded-sm py-1 text-left text-sm font-medium ${
sidebarCollapse ? "justify-center" : "justify-between ml-4"
className={`flex items-center w-full cursor-pointer select-none text-left text-sm font-medium ${
sidebarCollapse ? "justify-center" : `justify-between`
}`}
>
<div className="flex items-center gap-x-2">
Expand All @@ -184,21 +186,23 @@ export const SingleSidebarProject: React.FC<Props> = ({
open ? "" : "text-custom-sidebar-text-200"
}`}
>
{truncateText(project?.name, 14)}
{truncateText(project.name, 15)}
</p>
)}
</div>
{!sidebarCollapse && (
<ExpandMoreOutlined
fontSize="small"
className={`${open ? "rotate-180" : ""} text-custom-text-200 duration-300`}
className={`${
open ? "rotate-180" : ""
} !hidden group-hover:!block text-custom-sidebar-text-200 duration-300`}
/>
)}
</Disclosure.Button>
</Tooltip>

{!sidebarCollapse && (
<CustomMenu ellipsis>
<CustomMenu className="hidden group-hover:block" ellipsis>
{!shortContextMenu && (
<CustomMenu.MenuItem onClick={handleDeleteProject}>
<span className="flex items-center justify-start gap-2 ">
Expand Down
2 changes: 1 addition & 1 deletion apps/app/components/workspace/sidebar-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const WorkspaceSidebarMenu = () => {
const { collapsed: sidebarCollapse } = useTheme();

return (
<div className="w-full cursor-pointer space-y-2 px-4 mt-5">
<div className="w-full cursor-pointer space-y-2 px-4 mt-5 pb-5">
{workspaceLinks(workspaceSlug as string).map((link, index) => {
const isActive =
link.name === "Settings"
Expand Down
Loading