Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-1907] Fix: favorites #5292

Merged
merged 22 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
790e16e
chore: workspace user favorites
NarayanBavisetti Jul 23, 2024
9d1ce25
chore: added project id in entity type
NarayanBavisetti Jul 25, 2024
827e11f
chore: removed the extra key
NarayanBavisetti Jul 25, 2024
9ee459f
chore: removed the project member filter
NarayanBavisetti Jul 26, 2024
1054263
chore: updated the project permission layer
NarayanBavisetti Jul 26, 2024
d8f13b3
chore: updated the workspace group favorite filter
NarayanBavisetti Jul 26, 2024
9454e4d
fix: project favorite toggle
NarayanBavisetti Jul 29, 2024
4077618
chore: Fav feature
gakshita Jul 29, 2024
4e80865
Merge branch 'preview' of https://github.com/makeplane/plane into cho…
gakshita Jul 29, 2024
db9be42
fix: build errors + added navigation
gakshita Jul 29, 2024
44bcec7
fix: added remove entity icon
gakshita Jul 30, 2024
11dcce2
fix: nomenclature
gakshita Jul 30, 2024
049b96e
chore: hard delete favorites
NarayanBavisetti Jul 31, 2024
d51953f
fix: review changes
gakshita Aug 1, 2024
e1400da
Merge branch 'chore/soft-delete-favorite' of https://github.com/makep…
gakshita Aug 1, 2024
eff77c3
fix: added optimistic addition to the store
gakshita Aug 1, 2024
c4f3f9a
chore: user favorite hard delete
NarayanBavisetti Aug 1, 2024
e700071
fix: linting fixed
gakshita Aug 1, 2024
c26c028
Merge branch 'chore/user-favorites' of https://github.com/makeplane/p…
gakshita Aug 1, 2024
477aeb9
fix: favorite bugs
gakshita Aug 3, 2024
fd12f54
fix: ts bugs
gakshita Aug 3, 2024
e55adca
Merge branch 'preview' of https://github.com/makeplane/plane into fix…
gakshita Aug 3, 2024
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
2 changes: 1 addition & 1 deletion apiserver/plane/app/views/cycle/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,7 @@ def destroy(self, request, slug, project_id, cycle_id):
workspace__slug=slug,
entity_identifier=cycle_id,
)
cycle_favorite.delete()
cycle_favorite.delete(soft=False)
return Response(status=status.HTTP_204_NO_CONTENT)


Expand Down
2 changes: 1 addition & 1 deletion apiserver/plane/app/views/module/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ def destroy(self, request, slug, project_id, module_id):
entity_type="module",
entity_identifier=module_id,
)
module_favorite.delete()
module_favorite.delete(soft=False)
return Response(status=status.HTTP_204_NO_CONTENT)


Expand Down
2 changes: 1 addition & 1 deletion apiserver/plane/app/views/page/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ def destroy(self, request, slug, project_id, pk):
entity_identifier=pk,
entity_type="page",
)
page_favorite.delete()
page_favorite.delete(soft=False)
return Response(status=status.HTTP_204_NO_CONTENT)


Expand Down
2 changes: 1 addition & 1 deletion apiserver/plane/app/views/project/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ def destroy(self, request, slug, project_id):
user=request.user,
workspace__slug=slug,
)
project_favorite.delete()
project_favorite.delete(soft=False)
return Response(status=status.HTTP_204_NO_CONTENT)


Expand Down
2 changes: 1 addition & 1 deletion apiserver/plane/app/views/view/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,5 +474,5 @@ def destroy(self, request, slug, project_id, view_id):
entity_type="view",
entity_identifier=view_id,
)
view_favorite.delete()
view_favorite.delete(soft=False)
return Response(status=status.HTTP_204_NO_CONTENT)
25 changes: 13 additions & 12 deletions packages/types/src/favorite/favorite.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
export type IFavorite = {
id: string;
name: string;
entity_type: string;
entity_data: {
name: string;
};
is_folder: boolean;
sort_order: number;
parent: string | null;
entity_identifier?: string | null;
children: IFavorite[];
project_id: string | null;
id: string;
name: string;
entity_type: string;
entity_data: {
name: string;
};
is_folder: boolean;
sort_order: number;
parent: string | null;
entity_identifier?: string | null;
children: IFavorite[];
project_id: string | null;
sequence: number;
};
34 changes: 16 additions & 18 deletions web/app/[workspaceSlug]/(projects)/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,23 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
"opacity-0": !sidebarCollapsed,
})}
/>
<SidebarUserMenu />
<hr
className={cn("flex-shrink-0 border-custom-sidebar-border-300 h-[0.5px] w-3/5 mx-auto my-1", {
"opacity-0": !sidebarCollapsed,
})}
/>
<SidebarWorkspaceMenu />
<hr
className={cn("flex-shrink-0 border-custom-sidebar-border-300 h-[0.5px] w-3/5 mx-auto my-1", {
"opacity-0": !sidebarCollapsed,
<div
className={cn("overflow-x-hidden scrollbar-sm h-full w-full overflow-y-auto px-2.5", {
"vertical-scrollbar": !sidebarCollapsed,
})}
/>
<SidebarFavoritesMenu />
<hr
className={cn("flex-shrink-0 border-custom-sidebar-border-300 h-[0.5px] w-3/5 mx-auto my-1", {
"opacity-0": !sidebarCollapsed,
})}
/>
<SidebarProjectsList />
>
<SidebarUserMenu />

<SidebarWorkspaceMenu />
<hr
className={cn("flex-shrink-0 border-custom-sidebar-border-300 h-[0.5px] w-3/5 mx-auto my-1", {
"opacity-0": !sidebarCollapsed,
})}
/>
<SidebarFavoritesMenu />

<SidebarProjectsList />
</div>
<SidebarHelpSection />
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions web/core/components/pages/list/block-item-action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ export const BlockItemAction: FC<Props> = observer((props) => {
const page = usePage(pageId);
const { getUserDetails } = useMember();
// derived values
const { access, created_at, is_favorite, owned_by, addToFavorites, removeFromFavorites } = page;
const { access, created_at, is_favorite, owned_by, addToFavorites, removePageFromFavorites } = page;

// derived values
const ownerDetails = owned_by ? getUserDetails(owned_by) : undefined;

// handlers
const handleFavorites = () => {
if (is_favorite)
removeFromFavorites().then(() =>
removePageFromFavorites().then(() =>
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
Expand Down
112 changes: 96 additions & 16 deletions web/core/components/workspace/sidebar/favorites/favorite-folder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

import { useEffect, useRef, useState } from "react";
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";

import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
import { useParams } from "next/navigation";
import { PenSquare, Star, MoreHorizontal, ChevronRight } from "lucide-react";
import { PenSquare, Star, MoreHorizontal, ChevronRight, GripVertical } from "lucide-react";
import { Disclosure, Transition } from "@headlessui/react";
// ui
import { IFavorite } from "@plane/types";
import { CustomMenu, Tooltip, DropIndicator, setToast, TOAST_TYPE, FavoriteFolderIcon } from "@plane/ui";
import { CustomMenu, Tooltip, DropIndicator, setToast, TOAST_TYPE, FavoriteFolderIcon, DragHandle } from "@plane/ui";

// helpers
import { cn } from "@/helpers/common.helper";
Expand All @@ -20,6 +21,7 @@ import useOutsideClickDetector from "@/hooks/use-outside-click-detector";
import { usePlatformOS } from "@/hooks/use-platform-os";
// constants
import { FavoriteItem } from "./favorite-item";
import { getDestinationStateSequence } from "./favorites.helpers";
import { NewFavoriteFolder } from "./new-fav-folder";

type Props = {
Expand All @@ -29,18 +31,20 @@ type Props = {
};

export const FavoriteFolder: React.FC<Props> = (props) => {
const { isLastChild, favorite, handleRemoveFromFavorites } = props;
const { favorite, handleRemoveFromFavorites } = props;
// store hooks
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();

const { isMobile } = usePlatformOS();
const { moveFavorite, getGroupedFavorites } = useFavorite();
const { moveFavorite, getGroupedFavorites, favoriteMap, moveFavoriteFolder } = useFavorite();
const { workspaceSlug } = useParams();
// states
const [isMenuActive, setIsMenuActive] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [instruction, setInstruction] = useState<"DRAG_OVER" | "DRAG_BELOW" | undefined>(undefined);
const [folderToRename, setFolderToRename] = useState<string | boolean | null>(null);
const [isDraggedOver, setIsDraggedOver] = useState(false);
const [closestEdge, setClosestEdge] = useState<string | null>(null);

// refs
const actionSectionRef = useRef<HTMLDivElement | null>(null);
const elementRef = useRef<HTMLDivElement | null>(null);
Expand All @@ -51,16 +55,14 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
moveFavorite(workspaceSlug.toString(), source, {
parent: destination,
})
.then((res) => {
console.log(res, "res");
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Favorite moved successfully.",
});
})
.catch((err) => {
console.log(err, "err");
.catch(() => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
Expand All @@ -69,33 +71,81 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
});
};

const handleOnDropFolder = (payload: Partial<IFavorite>) => {
moveFavoriteFolder(workspaceSlug.toString(), favorite.id, payload)
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Folder moved successfully.",
});
})
.catch(() => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Failed to move folder.",
});
});
};

useEffect(() => {
const element = elementRef.current;

if (!element) return;
const initialData = { type: "PARENT", id: favorite.id };

return combine(
draggable({
element,
// getInitialData: () => initialData,
onDragStart: () => setIsDragging(true),
onDrop: (data) => {
setIsDraggedOver(false);
if (!data.location.current.dropTargets[0]) return;
const destinationData = data.location.current.dropTargets[0].data;

if (favorite.id && destinationData) {
const edge = extractClosestEdge(destinationData) || undefined;
const payload = {
id: favorite.id,
sequence: getDestinationStateSequence(favoriteMap, destinationData.id as string, edge),
};

handleOnDropFolder(payload);
}
}, // canDrag: () => isDraggable,
}),
dropTargetForElements({
element,
getData: () => ({ type: "PARENT", id: favorite.id }),
onDragEnter: () => {
getData: ({ input, element }) =>
attachClosestEdge(initialData, {
input,
element,
allowedEdges: ["top", "bottom"],
}),
onDragEnter: (args) => {
setIsDragging(true);
setIsDraggedOver(true);
setClosestEdge(extractClosestEdge(args.self.data));
},
onDragLeave: () => {
setIsDragging(false);
setIsDraggedOver(false);
setClosestEdge(null);
},
onDragStart: () => {
setIsDragging(true);
},
onDrop: ({ self, source }) => {
setInstruction(undefined);
setIsDragging(false);
setIsDraggedOver(false);
const sourceId = source?.data?.id as string | undefined;
const destinationId = self?.data?.id as string | undefined;

if (sourceId === destinationId) return;
if (!sourceId || !destinationId) return;

if (favoriteMap[sourceId].parent === destinationId) return;
handleOnDrop(sourceId, destinationId);
},
})
Expand All @@ -122,7 +172,8 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
"bg-custom-sidebar-background-80 opacity-60": isDragging,
})}
>
<DropIndicator classNames="absolute top-0" isVisible={instruction === "DRAG_OVER"} />
{/* draggable drop top indicator */}
<DropIndicator isVisible={isDraggedOver && closestEdge === "top"} />
<div
className={cn(
"group/project-item relative w-full px-2 py-1.5 flex items-center rounded-md text-custom-sidebar-text-100 hover:bg-custom-sidebar-background-90",
Expand All @@ -132,6 +183,12 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
}
)}
>
{/* draggable indicator */}

<div className="flex-shrink-0 w-3 h-3 rounded-sm absolute left-0 hidden group-hover:flex justify-center items-center transition-colors bg-custom-background-90 cursor-pointer text-custom-text-200 hover:text-custom-text-100">
<GripVertical className="w-3 h-3" />
</div>

{isSidebarCollapsed ? (
<div
className={cn("flex-grow flex items-center gap-1.5 truncate text-left select-none", {
Expand Down Expand Up @@ -160,6 +217,28 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
"justify-center": isSidebarCollapsed,
})}
>
<Tooltip
isMobile={isMobile}
tooltipContent={
favorite.sort_order === null ? "Join the project to rearrange" : "Drag to rearrange"
}
position="top-right"
disabled={isDragging}
>
<button
type="button"
className={cn(
"hidden group-hover/project-item:flex items-center justify-center absolute top-1/2 -left-3 -translate-y-1/2 rounded text-custom-sidebar-text-400 cursor-grab",
{
"cursor-not-allowed opacity-60": favorite.sort_order === null,
"cursor-grabbing": isDragging,
"!hidden": isSidebarCollapsed,
}
)}
>
<DragHandle className="bg-transparent" />
</button>
</Tooltip>
<div className="size-4 grid place-items-center flex-shrink-0">
<FavoriteFolderIcon />
</div>
Expand Down Expand Up @@ -238,7 +317,8 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
</Disclosure.Panel>
</Transition>
)}
{isLastChild && <DropIndicator isVisible={instruction === "DRAG_BELOW"} />}
{/* draggable drop bottom indicator */}
<DropIndicator isVisible={isDraggedOver && closestEdge === "bottom"} />{" "}
</div>
)}
</Disclosure>
Expand Down
Loading
Loading