From 0f641ecbbac1621d63ab48f18c131dee20047e5f Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Wed, 20 Nov 2024 18:44:27 +0530 Subject: [PATCH 01/15] fixed re order for favorites --- .../sidebar/favorites/favorite-folder.tsx | 85 ++++++++++-------- .../sidebar/favorites/favorite-items/root.tsx | 90 ++++++++++++++++--- .../sidebar/favorites/favorites-menu.tsx | 24 ++++- web/core/store/favorite.store.ts | 12 +-- 4 files changed, 155 insertions(+), 56 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index 70dd006cb76..0d3b0892780 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -3,12 +3,16 @@ import { useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; +import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview"; import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; import uniqBy from "lodash/uniqBy"; import { useParams } from "next/navigation"; import { PenSquare, Star, MoreHorizontal, ChevronRight, GripVertical } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; +import { createRoot } from "react-dom/client"; + // plane helpers import { useOutsideClickDetector } from "@plane/helpers"; // ui @@ -24,21 +28,23 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; import { FavoriteRoot } from "./favorite-items"; import { getDestinationStateSequence } from "./favorites.helpers"; import { NewFavoriteFolder } from "./new-fav-folder"; +import { orderBy } from "lodash"; type Props = { isLastChild: boolean; favorite: IFavorite; handleRemoveFromFavorites: (favorite: IFavorite) => void; handleRemoveFromFavoritesFolder: (favoriteId: string) => void; + handleReorder: (favoriteId: string, sequence: number) => void; }; export const FavoriteFolder: React.FC = (props) => { - const { favorite, handleRemoveFromFavorites, handleRemoveFromFavoritesFolder } = props; + const { favorite, handleRemoveFromFavorites, handleRemoveFromFavoritesFolder, handleReorder } = props; // store hooks const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme(); const { isMobile } = usePlatformOS(); - const { moveFavorite, getGroupedFavorites, groupedFavorites, moveFavoriteFolder } = useFavorite(); + const { getGroupedFavorites, groupedFavorites, moveFavoriteToFolder } = useFavorite(); const { workspaceSlug } = useParams(); // states const [isMenuActive, setIsMenuActive] = useState(false); @@ -53,8 +59,8 @@ export const FavoriteFolder: React.FC = (props) => { !favorite.children && getGroupedFavorites(workspaceSlug.toString(), favorite.id); - const handleOnDrop = (source: string, destination: string) => { - moveFavorite(workspaceSlug.toString(), source, { + const handleMoveToFolder = (source: string, destination: string) => { + moveFavoriteToFolder(workspaceSlug.toString(), source, { parent: destination, }) .then(() => { @@ -73,24 +79,6 @@ export const FavoriteFolder: React.FC = (props) => { }); }; - const handleOnDropFolder = (payload: Partial) => { - 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; @@ -101,7 +89,25 @@ export const FavoriteFolder: React.FC = (props) => { draggable({ element, getInitialData: () => initialData, - onDragStart: () => setIsDragging(true), + // onDragStart: () => setIsDragging(true), + onGenerateDragPreview: ({ nativeSetDragImage }) =>{ + setCustomNativeDragPreview({ + getOffset: pointerOutsideOfPreview({ x: "0px", y: "0px" }), + render: ({ container }) => { + const root = createRoot(container); + root.render( +
+
+ +
+

{favorite.name}

+
+ ); + return () => root.unmount(); + }, + nativeSetDragImage, + }); + }, onDrop: (data) => { setIsDraggedOver(false); if (!data.location.current.dropTargets[0]) return; @@ -109,14 +115,10 @@ export const FavoriteFolder: React.FC = (props) => { if (favorite.id && destinationData) { const edge = extractClosestEdge(destinationData) || undefined; - const payload = { - id: favorite.id, - sequence: Math.round( - getDestinationStateSequence(groupedFavorites, destinationData.id as string, edge) || 0 - ), - }; - - handleOnDropFolder(payload); + const sequence = Math.round( + getDestinationStateSequence(groupedFavorites, destinationData.id as string, edge) || 0 + ); + handleReorder(favorite.id,sequence); } }, // canDrag: () => isDraggable, }), @@ -128,10 +130,14 @@ export const FavoriteFolder: React.FC = (props) => { element, allowedEdges: ["top", "bottom"], }), - onDragEnter: (args) => { - setIsDragging(true); - setIsDraggedOver(true); - args.source.data.is_folder && setClosestEdge(extractClosestEdge(args.self.data)); + onDragEnter: ({source,self}) => { + const sourceId = source?.data?.id as string; + const destinationId = self?.data?.id as string | undefined; + if (groupedFavorites[sourceId].parent !== destinationId) { + setIsDraggedOver(true); + setIsDragging(true); + }; + source.data.is_folder && setClosestEdge(extractClosestEdge(self.data)); }, onDragLeave: () => { setIsDragging(false); @@ -146,16 +152,18 @@ export const FavoriteFolder: React.FC = (props) => { setIsDraggedOver(false); const sourceId = source?.data?.id as string | undefined; const destinationId = self?.data?.id as string | undefined; + if (source.data.is_folder) return; if (sourceId === destinationId) return; if (!sourceId || !destinationId) return; if (groupedFavorites[sourceId].parent === destinationId) return; - handleOnDrop(sourceId, destinationId); + + handleMoveToFolder(sourceId, destinationId); }, }) ); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [elementRef.current, isDragging, favorite.id, handleOnDrop]); + }, [elementRef.current, isDragging, favorite.id, handleMoveToFolder]); useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false)); @@ -316,7 +324,7 @@ export const FavoriteFolder: React.FC = (props) => { "px-2": !isSidebarCollapsed, })} > - {uniqBy(favorite.children, "id").map((child) => ( + {orderBy(uniqBy(favorite.children, "id"),'sequence','desc').map((child) => ( = (props) => { handleRemoveFromFavorites={handleRemoveFromFavorites} handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder} favoriteMap={groupedFavorites} + handleReorder={handleReorder} /> ))} diff --git a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx index 5d880e3a6ce..9080fa13876 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx @@ -3,6 +3,9 @@ import React, { FC, useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; +import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; +import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview"; import { observer } from "mobx-react"; // plane helpers import { useOutsideClickDetector } from "@plane/helpers"; @@ -19,48 +22,94 @@ import { import { useAppTheme } from "@/hooks/store"; import { useFavoriteItemDetails } from "@/hooks/use-favorite-item-details"; +import { createRoot } from "react-dom/client"; +import { DropIndicator } from "@plane/ui"; +//constants +import { getDestinationStateSequence } from "../favorites.helpers"; + type Props = { workspaceSlug: string; favorite: IFavorite; favoriteMap: Record; handleRemoveFromFavorites: (favorite: IFavorite) => void; handleRemoveFromFavoritesFolder: (favoriteId: string) => void; + handleReorder: (favoriteId: string, sequence: number) => void; }; export const FavoriteRoot: FC = observer((props) => { // props - const { workspaceSlug, favorite, favoriteMap, handleRemoveFromFavorites, handleRemoveFromFavoritesFolder } = props; + const { + workspaceSlug, + favorite, + favoriteMap, + handleRemoveFromFavorites, + handleRemoveFromFavoritesFolder, + handleReorder, + } = props; // store hooks const { sidebarCollapsed } = useAppTheme(); - + const { itemLink, itemIcon, itemTitle } = useFavoriteItemDetails(workspaceSlug, favorite); //state const [isDragging, setIsDragging] = useState(false); const [isMenuActive, setIsMenuActive] = useState(false); + const [closestEdge, setClosestEdge] = useState(null); + const [isDraggedOver, setIsDraggedOver] = useState(false); + //ref const elementRef = useRef(null); const actionSectionRef = useRef(null); const handleQuickAction = (value: boolean) => setIsMenuActive(value); - const { itemLink, itemIcon, itemTitle } = useFavoriteItemDetails(workspaceSlug, favorite); // drag and drop useEffect(() => { const element = elementRef.current; if (!element) return; + const initialData = { id: favorite.id, type: favorite.parent ? 'CHILD' : 'NON_PARENT' }; return combine( draggable({ element, dragHandle: elementRef.current, - canDrag: () => true, - getInitialData: () => ({ id: favorite.id, type: "CHILD" }), + getInitialData: () => initialData, onDragStart: () => { setIsDragging(true); }, - onDrop: () => { + onDrop: (data) => { + setIsDraggedOver(false); setIsDragging(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 sequence = Math.round( + getDestinationStateSequence(favoriteMap, destinationData.id as string, edge) || 0 + ); + handleReorder(favorite.id, sequence); + } + }, + onGenerateDragPreview: ({ nativeSetDragImage }) => { + setCustomNativeDragPreview({ + getOffset: pointerOutsideOfPreview({ x: "0px", y: "0px" }), + render: ({ container }) => { + const root = createRoot(container); + root.render( +
+ +
+ ); + return () => root.unmount(); + }, + nativeSetDragImage, + }); }, }), dropTargetForElements({ @@ -68,17 +117,34 @@ export const FavoriteRoot: FC = observer((props) => { onDragStart: () => { setIsDragging(true); }, - 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); }, - onDrop: ({ source }) => { + onDrop: ({ self, source }) => { setIsDragging(false); - const sourceId = source?.data?.id as string | undefined; - if (!sourceId || !favoriteMap[sourceId].parent) return; - handleRemoveFromFavoritesFolder(sourceId); + setIsDraggedOver(false); + const sourceId = source.data?.id as string | undefined; + const destinationType = self.data?.type as string | undefined; + const sourceType = source.data?.type as string | undefined; + + if(!sourceId) return; + + if(destinationType === 'NON_PARENT'){ + handleRemoveFromFavoritesFolder(sourceId) + } }, }) ); @@ -90,6 +156,7 @@ export const FavoriteRoot: FC = observer((props) => { return ( <> + {!sidebarCollapsed && } {!sidebarCollapsed && ( @@ -101,6 +168,7 @@ export const FavoriteRoot: FC = observer((props) => { handleRemoveFromFavorites={handleRemoveFromFavorites} /> )} + ); diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index 89cd6f36f3a..2dc442e33e8 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -33,7 +33,7 @@ export const SidebarFavoritesMenu = observer(() => { // store hooks const { sidebarCollapsed } = useAppTheme(); - const { favoriteIds, groupedFavorites, deleteFavorite, removeFromFavoriteFolder } = useFavorite(); + const { favoriteIds, groupedFavorites, deleteFavorite, removeFromFavoriteFolder, reOrderFavorite } = useFavorite(); const { workspaceSlug } = useParams(); const { isMobile } = usePlatformOS(); @@ -83,6 +83,26 @@ export const SidebarFavoritesMenu = observer(() => { }); }); }; + + const handleReorder = (favoriteId: string, sequence: number) => { + reOrderFavorite(workspaceSlug.toString(), favoriteId, { + sequence: sequence + }) + .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(() => { if (sidebarCollapsed) toggleFavoriteMenu(true); }, [sidebarCollapsed, toggleFavoriteMenu]); @@ -194,6 +214,7 @@ export const SidebarFavoritesMenu = observer(() => { isLastChild={index === favoriteIds.length - 1} handleRemoveFromFavorites={handleRemoveFromFavorites} handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder} + handleReorder={handleReorder} /> ) : ( { handleRemoveFromFavorites={handleRemoveFromFavorites} handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder} favoriteMap={groupedFavorites} + handleReorder={handleReorder} /> )} diff --git a/web/core/store/favorite.store.ts b/web/core/store/favorite.store.ts index 9d04ed1303e..da2a5b4c92f 100644 --- a/web/core/store/favorite.store.ts +++ b/web/core/store/favorite.store.ts @@ -26,9 +26,9 @@ export interface IFavoriteStore { updateFavorite: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; deleteFavorite: (workspaceSlug: string, favoriteId: string) => Promise; getGroupedFavorites: (workspaceSlug: string, favoriteId: string) => Promise; - moveFavorite: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; + moveFavoriteToFolder: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; removeFavoriteEntity: (workspaceSlug: string, entityId: string) => Promise; - moveFavoriteFolder: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; + reOrderFavorite: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; removeFromFavoriteFolder: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; removeFavoriteFromStore: (entity_identifier: string) => void; } @@ -64,9 +64,9 @@ export class FavoriteStore implements IFavoriteStore { // CRUD actions addFavorite: action, getGroupedFavorites: action, - moveFavorite: action, + moveFavoriteToFolder: action, removeFavoriteEntity: action, - moveFavoriteFolder: action, + reOrderFavorite: action, removeFavoriteEntityFromStore: action, removeFromFavoriteFolder: action, }); @@ -168,7 +168,7 @@ export class FavoriteStore implements IFavoriteStore { * @param data * @returns Promise */ - moveFavorite = async (workspaceSlug: string, favoriteId: string, data: Partial) => { + moveFavoriteToFolder = async (workspaceSlug: string, favoriteId: string, data: Partial) => { const oldParent = this.favoriteMap[favoriteId].parent; try { runInAction(() => { @@ -190,7 +190,7 @@ export class FavoriteStore implements IFavoriteStore { } }; - moveFavoriteFolder = async (workspaceSlug: string, favoriteId: string, data: Partial) => { + reOrderFavorite = async (workspaceSlug: string, favoriteId: string, data: Partial) => { const initialSequence = this.favoriteMap[favoriteId].sequence; try { runInAction(() => { From 12743b5b046da958081d3f0d435f90774988c9fc Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Wed, 20 Nov 2024 19:32:08 +0530 Subject: [PATCH 02/15] fixed lint errors --- .../workspace/sidebar/favorites/favorite-folder.tsx | 12 ++++++------ .../sidebar/favorites/favorite-items/root.tsx | 13 ++++++------- .../workspace/sidebar/favorites/favorites-menu.tsx | 4 ++-- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index 0d3b0892780..51edaa556ad 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -3,15 +3,16 @@ import { useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; -import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview"; +import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; +import orderBy from "lodash/orderBy"; import uniqBy from "lodash/uniqBy"; import { useParams } from "next/navigation"; +import { createRoot } from "react-dom/client"; import { PenSquare, Star, MoreHorizontal, ChevronRight, GripVertical } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; -import { createRoot } from "react-dom/client"; // plane helpers import { useOutsideClickDetector } from "@plane/helpers"; @@ -28,7 +29,6 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; import { FavoriteRoot } from "./favorite-items"; import { getDestinationStateSequence } from "./favorites.helpers"; import { NewFavoriteFolder } from "./new-fav-folder"; -import { orderBy } from "lodash"; type Props = { isLastChild: boolean; @@ -57,7 +57,7 @@ export const FavoriteFolder: React.FC = (props) => { const actionSectionRef = useRef(null); const elementRef = useRef(null); - !favorite.children && getGroupedFavorites(workspaceSlug.toString(), favorite.id); + if(!favorite.children) getGroupedFavorites(workspaceSlug.toString(), favorite.id); const handleMoveToFolder = (source: string, destination: string) => { moveFavoriteToFolder(workspaceSlug.toString(), source, { @@ -137,7 +137,7 @@ export const FavoriteFolder: React.FC = (props) => { setIsDraggedOver(true); setIsDragging(true); }; - source.data.is_folder && setClosestEdge(extractClosestEdge(self.data)); + if(source.data.is_folder) setClosestEdge(extractClosestEdge(self.data)); }, onDragLeave: () => { setIsDragging(false); @@ -152,7 +152,7 @@ export const FavoriteFolder: React.FC = (props) => { setIsDraggedOver(false); const sourceId = source?.data?.id as string | undefined; const destinationId = self?.data?.id as string | undefined; - + if (source.data.is_folder) return; if (sourceId === destinationId) return; if (!sourceId || !destinationId) return; diff --git a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx index 9080fa13876..0fb8280b57f 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx @@ -3,15 +3,17 @@ import React, { FC, useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; -import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; -import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview"; +import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; +import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; import { observer } from "mobx-react"; // plane helpers +import { createRoot } from "react-dom/client"; import { useOutsideClickDetector } from "@plane/helpers"; // ui import { IFavorite } from "@plane/types"; // components +import { DropIndicator } from "@plane/ui"; import { FavoriteItemDragHandle, FavoriteItemQuickAction, @@ -22,8 +24,6 @@ import { import { useAppTheme } from "@/hooks/store"; import { useFavoriteItemDetails } from "@/hooks/use-favorite-item-details"; -import { createRoot } from "react-dom/client"; -import { DropIndicator } from "@plane/ui"; //constants import { getDestinationStateSequence } from "../favorites.helpers"; @@ -138,13 +138,12 @@ export const FavoriteRoot: FC = observer((props) => { setIsDraggedOver(false); const sourceId = source.data?.id as string | undefined; const destinationType = self.data?.type as string | undefined; - const sourceType = source.data?.type as string | undefined; - + if(!sourceId) return; if(destinationType === 'NON_PARENT'){ handleRemoveFromFavoritesFolder(sourceId) - } + } }, }) ); diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index 2dc442e33e8..fedaf24322b 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -102,7 +102,7 @@ export const SidebarFavoritesMenu = observer(() => { message: "Failed to move folder.", }); }); - } + } useEffect(() => { if (sidebarCollapsed) toggleFavoriteMenu(true); }, [sidebarCollapsed, toggleFavoriteMenu]); @@ -158,7 +158,7 @@ export const SidebarFavoritesMenu = observer(() => { { setCreateNewFolder(true); - !isFavoriteMenuOpen && toggleFavoriteMenu(!isFavoriteMenuOpen); + if(!isFavoriteMenuOpen) toggleFavoriteMenu(!isFavoriteMenuOpen); }} className={cn("size-4 flex-shrink-0 text-custom-sidebar-text-400 transition-transform")} /> From 23b30d047978ea42ec2c90c78e0e432f5e9f1e8a Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Fri, 22 Nov 2024 15:31:52 +0530 Subject: [PATCH 03/15] added reorder --- .../sidebar/favorites/favorite-folder.tsx | 136 +++++++++++------- .../sidebar/favorites/favorite-items/root.tsx | 95 ++++++------ .../sidebar/favorites/favorites-menu.tsx | 20 +-- .../sidebar/favorites/favorites.helpers.ts | 72 +++++++++- .../workspace/sidebar/projects-list-item.tsx | 3 +- 5 files changed, 220 insertions(+), 106 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index 51edaa556ad..6b49cd8aacb 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -5,10 +5,9 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview"; import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; +import { attachInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item"; -import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; import orderBy from "lodash/orderBy"; -import uniqBy from "lodash/uniqBy"; import { useParams } from "next/navigation"; import { createRoot } from "react-dom/client"; import { PenSquare, Star, MoreHorizontal, ChevronRight, GripVertical } from "lucide-react"; @@ -17,7 +16,7 @@ import { Disclosure, Transition } from "@headlessui/react"; // plane helpers import { useOutsideClickDetector } from "@plane/helpers"; // ui -import { IFavorite } from "@plane/types"; +import { IFavorite, InstructionType } from "@plane/types"; import { CustomMenu, Tooltip, DropIndicator, setToast, TOAST_TYPE, FavoriteFolderIcon, DragHandle } from "@plane/ui"; // helpers import { cn } from "@/helpers/common.helper"; @@ -27,7 +26,7 @@ import { useFavorite } from "@/hooks/store/use-favorite"; import { usePlatformOS } from "@/hooks/use-platform-os"; // constants import { FavoriteRoot } from "./favorite-items"; -import { getDestinationStateSequence } from "./favorites.helpers"; +import { getCanDrop, TargetData, getInstructionFromPayload, getDestinationStateSequence } from "./favorites.helpers"; import { NewFavoriteFolder } from "./new-fav-folder"; type Props = { @@ -39,7 +38,7 @@ type Props = { }; export const FavoriteFolder: React.FC = (props) => { - const { favorite, handleRemoveFromFavorites, handleRemoveFromFavoritesFolder, handleReorder } = props; + const { favorite, handleRemoveFromFavorites, handleRemoveFromFavoritesFolder, handleReorder, isLastChild } = props; // store hooks const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme(); @@ -50,8 +49,7 @@ export const FavoriteFolder: React.FC = (props) => { const [isMenuActive, setIsMenuActive] = useState(false); const [isDragging, setIsDragging] = useState(false); const [folderToRename, setFolderToRename] = useState(null); - const [isDraggedOver, setIsDraggedOver] = useState(false); - const [closestEdge, setClosestEdge] = useState(null); + const [instruction, setInstruction] = useState(undefined); // refs const actionSectionRef = useRef(null); @@ -79,17 +77,18 @@ export const FavoriteFolder: React.FC = (props) => { }); }; + useEffect(() => { const element = elementRef.current; if (!element) return; - const initialData = { type: "PARENT", id: favorite.id, is_folder: favorite.is_folder }; + const initialData = { id: favorite.id, isGroup: true, isChild: false }; return combine( draggable({ element, getInitialData: () => initialData, - // onDragStart: () => setIsDragging(true), + onDragStart: () => setIsDragging(true), onGenerateDragPreview: ({ nativeSetDragImage }) =>{ setCustomNativeDragPreview({ getOffset: pointerOutsideOfPreview({ x: "0px", y: "0px" }), @@ -108,63 +107,93 @@ export const FavoriteFolder: React.FC = (props) => { nativeSetDragImage, }); }, - 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 sequence = Math.round( - getDestinationStateSequence(groupedFavorites, destinationData.id as string, edge) || 0 - ); - handleReorder(favorite.id,sequence); - } + onDrop: () => { + setIsDragging(false) }, // canDrag: () => isDraggable, }), dropTargetForElements({ element, - getData: ({ input, element }) => - attachClosestEdge(initialData, { + canDrop: ({ source }) => getCanDrop(source, favorite, false), + getData: ({ input, element }) =>{ + + const blockedStates: InstructionType[] = []; + if(!isLastChild){ + blockedStates.push('reorder-below'); + } + + return attachInstruction(initialData,{ input, element, - allowedEdges: ["top", "bottom"], - }), - onDragEnter: ({source,self}) => { - const sourceId = source?.data?.id as string; - const destinationId = self?.data?.id as string | undefined; - if (groupedFavorites[sourceId].parent !== destinationId) { - setIsDraggedOver(true); - setIsDragging(true); - }; - if(source.data.is_folder) setClosestEdge(extractClosestEdge(self.data)); + currentLevel: 0, + indentPerLevel: 0, + mode: isLastChild ? 'last-in-group' : 'standard', + block: blockedStates + }) }, - onDragLeave: () => { - setIsDragging(false); - setIsDraggedOver(false); - setClosestEdge(null); + onDrag: ({source, self, location}) => { + const instruction = getInstructionFromPayload(self,source, location); + setInstruction(instruction); }, - onDragStart: () => { - setIsDragging(true); + onDragLeave: () => { + setInstruction(undefined); }, - onDrop: ({ self, source }) => { - setIsDragging(false); - setIsDraggedOver(false); - const sourceId = source?.data?.id as string | undefined; - const destinationId = self?.data?.id as string | undefined; + onDrop: ({ source, location }) => { + setInstruction(undefined); + + const dropTargets = location?.current?.dropTargets ?? [] + if(!dropTargets || dropTargets.length <= 0) return; + const dropTarget = dropTargets.length > 1 ? dropTargets.find(target=>target?.data?.isChild) : dropTargets[0]; - if (source.data.is_folder) return; - if (sourceId === destinationId) return; - if (!sourceId || !destinationId) return; - if (groupedFavorites[sourceId].parent === destinationId) return; + const dropTargetData = dropTarget?.data as TargetData; - handleMoveToFolder(sourceId, destinationId); + if(!dropTarget || !dropTargetData) return; + const instruction = getInstructionFromPayload(dropTarget, source, location); + const parentId = instruction === 'make-child' ? dropTargetData.id : dropTargetData.parentId; + const droppedFavId = instruction !== "make-child" ? dropTargetData.id : undefined; + const sourceData = source.data as TargetData; + + if(droppedFavId && sourceData.id){ + if(!!parentId) return; // skip if reorder is inside folder + const destinationSequence = getDestinationStateSequence(groupedFavorites,droppedFavId,instruction) + handleReorder(sourceData.id,destinationSequence || 0) + } + + if(!parentId && !droppedFavId) return + if(sourceData.isGroup) return + if(sourceData.parentId === parentId) return + + if(!parentId && sourceData.isChild){ + handleRemoveFromFavoritesFolder(sourceData.id) + return + } + + if(parentId){ + handleMoveToFolder(sourceData.id,parentId) + } + + + // if(parentId) + // handleMoveToFolder(sourceData.id,parentId); + // else + // handleRemoveFromFavoritesFolder(sourceData.id) + + // setIsDragging(false); + // const sourceId = source?.data?.id as string | undefined; + // const destinationId = self?.data?.id as string | undefined; + + // if (source.data.is_folder) return; + // if (sourceId === destinationId) return; + // if (!sourceId || !destinationId) return; + // if (groupedFavorites[sourceId].parent === destinationId) return; + + // handleMoveToFolder(sourceId, destinationId); }, }) ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [elementRef.current, isDragging, favorite.id, handleMoveToFolder]); + useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false)); return folderToRename ? ( @@ -182,10 +211,11 @@ export const FavoriteFolder: React.FC = (props) => { // id={`sidebar-${projectId}-${projectListType}`} className={cn("relative", { "bg-custom-sidebar-background-80 opacity-60": isDragging, + "border-[2px] border-custom-primary-100" : instruction === 'make-child' })} > {/* draggable drop top indicator */} - +
= (props) => { "px-2": !isSidebarCollapsed, })} > - {orderBy(uniqBy(favorite.children, "id"),'sequence','desc').map((child) => ( + {orderBy(favorite.children,'sequence','desc').map((child,index) => ( = (props) => { )} {/* draggable drop bottom indicator */} - {" "} + { isLastChild && }
)} diff --git a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx index 0fb8280b57f..2acc38139c5 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx @@ -5,13 +5,14 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview"; import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; -import { attachClosestEdge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; +import { attachInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item"; + import { observer } from "mobx-react"; // plane helpers import { createRoot } from "react-dom/client"; import { useOutsideClickDetector } from "@plane/helpers"; // ui -import { IFavorite } from "@plane/types"; +import { IFavorite, InstructionType } from "@plane/types"; // components import { DropIndicator } from "@plane/ui"; import { @@ -23,11 +24,13 @@ import { // hooks import { useAppTheme } from "@/hooks/store"; import { useFavoriteItemDetails } from "@/hooks/use-favorite-item-details"; +//helpers +import { getCanDrop, TargetData , getInstructionFromPayload, getDestinationStateSequence} from "../favorites.helpers"; -//constants -import { getDestinationStateSequence } from "../favorites.helpers"; type Props = { + isLastChild: boolean; + parentId: string | undefined; workspaceSlug: string; favorite: IFavorite; favoriteMap: Record; @@ -39,6 +42,8 @@ type Props = { export const FavoriteRoot: FC = observer((props) => { // props const { + isLastChild, + parentId, workspaceSlug, favorite, favoriteMap, @@ -52,8 +57,7 @@ export const FavoriteRoot: FC = observer((props) => { //state const [isDragging, setIsDragging] = useState(false); const [isMenuActive, setIsMenuActive] = useState(false); - const [closestEdge, setClosestEdge] = useState(null); - const [isDraggedOver, setIsDraggedOver] = useState(false); + const [instruction, setInstruction] = useState(undefined); //ref const elementRef = useRef(null); @@ -67,8 +71,7 @@ export const FavoriteRoot: FC = observer((props) => { const element = elementRef.current; if (!element) return; - const initialData = { id: favorite.id, type: favorite.parent ? 'CHILD' : 'NON_PARENT' }; - + const initialData = { id: favorite.id, isGroup: false, isChild: !!parentId, parentId }; return combine( draggable({ element, @@ -77,19 +80,8 @@ export const FavoriteRoot: FC = observer((props) => { onDragStart: () => { setIsDragging(true); }, - onDrop: (data) => { - setIsDraggedOver(false); + onDrop: () => { setIsDragging(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 sequence = Math.round( - getDestinationStateSequence(favoriteMap, destinationData.id as string, edge) || 0 - ); - handleReorder(favorite.id, sequence); - } }, onGenerateDragPreview: ({ nativeSetDragImage }) => { setCustomNativeDragPreview({ @@ -114,35 +106,56 @@ export const FavoriteRoot: FC = observer((props) => { }), dropTargetForElements({ element, + canDrop: ({ source }) => getCanDrop(source, favorite, !!parentId), onDragStart: () => { setIsDragging(true); }, - getData: ({ input, element }) => - attachClosestEdge(initialData, { + getData: ({ input, element }) =>{ + + const blockedStates: InstructionType[] = ['make-child']; + if(!isLastChild){ + blockedStates.push('reorder-below'); + } + + return attachInstruction(initialData,{ input, element, - allowedEdges: ["top", "bottom"], - }), - onDragEnter: (args) => { - setIsDragging(true); - setIsDraggedOver(true); - setClosestEdge(extractClosestEdge(args.self.data)); + currentLevel: 1, + indentPerLevel: 0, + mode: isLastChild ? 'last-in-group' : 'standard', + block: blockedStates + }) + }, + onDrag: ({ self, source, location }) => { + const instruction = getInstructionFromPayload(self, source, location); + setInstruction(instruction); }, onDragLeave: () => { - setIsDragging(false); - setIsDraggedOver(false); - setClosestEdge(null); + setInstruction(undefined); }, - onDrop: ({ self, source }) => { - setIsDragging(false); - setIsDraggedOver(false); - const sourceId = source.data?.id as string | undefined; - const destinationType = self.data?.type as string | undefined; + onDrop: ({ source, location }) => { + setInstruction(undefined); + const dropTargets = location?.current?.dropTargets ?? [] + if(!dropTargets || dropTargets.length <= 0) return; + + const dropTarget = dropTargets.length > 1 ? dropTargets.find(target=>target?.data?.isChild) : dropTargets[0]; - if(!sourceId) return; + const dropTargetData = dropTarget?.data as TargetData; - if(destinationType === 'NON_PARENT'){ - handleRemoveFromFavoritesFolder(sourceId) + if(!dropTarget || !dropTargetData) return; + + const instruction = getInstructionFromPayload(dropTarget, source, location); + const parentId = instruction === 'make-child' ? dropTargetData.id : dropTargetData.parentId; + const droppedFavId = instruction !== "make-child" ? dropTargetData.id : undefined; + const sourceData = source.data as TargetData; + + if(droppedFavId && sourceData.id){ + const destinationSequence = getDestinationStateSequence(favoriteMap,droppedFavId,instruction) + handleReorder(sourceData.id,destinationSequence || 0) + } + + if(!parentId && sourceData.isChild){ + handleRemoveFromFavoritesFolder(sourceData.id) } }, }) @@ -154,8 +167,8 @@ export const FavoriteRoot: FC = observer((props) => { return ( <> + - {!sidebarCollapsed && } {!sidebarCollapsed && ( @@ -167,8 +180,8 @@ export const FavoriteRoot: FC = observer((props) => { handleRemoveFromFavorites={handleRemoveFromFavorites} /> )} - + { isLastChild && } ); }); diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index fedaf24322b..14d0b7ee54e 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -89,17 +89,17 @@ export const SidebarFavoritesMenu = observer(() => { sequence: sequence }) .then(() => { - // setToast({ - // type: TOAST_TYPE.SUCCESS, - // title: "Success!", - // message: "Folder moved successfully.", - // }); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Success!", + message: "Reordered successfully.", + }); }) .catch(() => { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", - message: "Failed to move folder.", + message: "Failed reorder", }); }); } @@ -129,7 +129,7 @@ export const SidebarFavoritesMenu = observer(() => { const sourceId = source?.data?.id as string | undefined; console.log({ sourceId }); if (!sourceId || !groupedFavorites[sourceId].parent) return; - handleRemoveFromFavoritesFolder(sourceId); + // handleRemoveFromFavoritesFolder(sourceId); }, }) ); @@ -199,7 +199,7 @@ export const SidebarFavoritesMenu = observer(() => { ) : ( orderBy(Object.values(groupedFavorites), "sequence", "desc") .filter((fav) => !fav.parent) - .map((fav, index) => ( + .map((fav, index, {length}) => ( { {fav.is_folder ? ( { , @@ -17,14 +25,14 @@ export const getDestinationStateSequence = ( if (!destinationStateSequence) return defaultSequence; - if (edge === "top") { + if (edge === "reorder-above") { const prevStateSequence = favoriteMap[favoriteIds[destinationStateIndex - 1]]?.sequence || undefined; if (prevStateSequence === undefined) { return destinationStateSequence + defaultSequence; } return (destinationStateSequence + prevStateSequence) / 2; - } else if (edge === "bottom") { + } else if (edge === "reorder-below") { const nextStateSequence = favoriteMap[favoriteIds[destinationStateIndex + 1]]?.sequence || undefined; if (nextStateSequence === undefined) { @@ -33,3 +41,61 @@ export const getDestinationStateSequence = ( return (destinationStateSequence + nextStateSequence) / 2; } }; + +/** + * extracts the Payload and translates the instruction for the current dropTarget based on drag and drop payload + * @param dropTarget dropTarget for which the instruction is required + * @param source the dragging favorite data that is being dragged on the dropTarget + * @param location location includes the data of all the dropTargets the source is being dragged on + * @returns Instruction for dropTarget + */ +export const getInstructionFromPayload = ( + dropTarget: TDropTarget, + source: TDropTarget, + location: IPragmaticPayloadLocation +): InstructionType | undefined => { + const dropTargetData = dropTarget?.data as TargetData; + const sourceData = source?.data as TargetData; + const allDropTargets = location?.current?.dropTargets; + + // if all the dropTargets are greater than 1 meaning the source is being dragged on a group and its child at the same time + // and also if the dropTarget in question is also a group then, it should be a child of the current Droptarget + if (allDropTargets?.length > 1 && dropTargetData?.isGroup) return "make-child"; + + if (!dropTargetData || !sourceData) return undefined; + + let instruction = extractInstruction(dropTargetData)?.type; + + // If the instruction is blocked then set an instruction based on if dropTarget it is a child or not + if (instruction === "instruction-blocked") { + instruction = dropTargetData.isChild ? "reorder-above" : "make-child"; + } + + // if source that is being dragged is a group. A group cannon be a child of any other favorite, + // hence if current instruction is to be a child of dropTarget then reorder-above instead + if (instruction === "make-child" && sourceData.isGroup) instruction = "reorder-above"; + + return instruction; +}; + +/** + * This provides a boolean to indicate if the favorite can be dropped onto the droptarget + * @param source + * @param favorite + * @param isCurrentChild if the dropTarget is a child + * @returns + */ +export const getCanDrop = (source: TDropTarget, favorite: IFavorite | undefined, isCurrentChild: boolean) => { + const sourceData = source?.data; + + if (!sourceData) return false; + + // a favorite cannot be dropped on to itself + if (sourceData.id === favorite?.id ) return false; + + + // if current dropTarget is a child and the favorite being dropped is a group then don't enable drop + if (isCurrentChild && sourceData.isGroup) return false; + + return true; +}; \ No newline at end of file diff --git a/web/core/components/workspace/sidebar/projects-list-item.tsx b/web/core/components/workspace/sidebar/projects-list-item.tsx index faa85a82607..644994080f1 100644 --- a/web/core/components/workspace/sidebar/projects-list-item.tsx +++ b/web/core/components/workspace/sidebar/projects-list-item.tsx @@ -298,12 +298,13 @@ export const SidebarProjectsListItem: React.FC = observer((props) => { <> setPublishModal(false)} /> setLeaveProjectModal(false)} /> - +
Date: Fri, 22 Nov 2024 21:31:12 +0530 Subject: [PATCH 04/15] fixed reorder inside folder --- .../sidebar/favorites/favorite-folder.tsx | 39 +++++++++---------- .../sidebar/favorites/favorite-items/root.tsx | 2 +- .../sidebar/favorites/favorites-menu.tsx | 20 +++++----- .../sidebar/favorites/favorites.helpers.ts | 24 +++++++++--- 4 files changed, 47 insertions(+), 38 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index 6b49cd8aacb..55452fe7767 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -38,7 +38,7 @@ type Props = { }; export const FavoriteFolder: React.FC = (props) => { - const { favorite, handleRemoveFromFavorites, handleRemoveFromFavoritesFolder, handleReorder, isLastChild } = props; + const { favorite, handleRemoveFromFavorites, handleRemoveFromFavoritesFolder, handleReorder, isLastChild} = props; // store hooks const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme(); @@ -62,11 +62,11 @@ export const FavoriteFolder: React.FC = (props) => { parent: destination, }) .then(() => { - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Favorite moved successfully.", - }); + // setToast({ + // type: TOAST_TYPE.SUCCESS, + // title: "Success!", + // message: "Favorite moved successfully.", + // }); }) .catch(() => { setToast({ @@ -152,25 +152,22 @@ export const FavoriteFolder: React.FC = (props) => { const droppedFavId = instruction !== "make-child" ? dropTargetData.id : undefined; const sourceData = source.data as TargetData; - if(droppedFavId && sourceData.id){ - if(!!parentId) return; // skip if reorder is inside folder + if(!sourceData.id) return + if(parentId){ + if(parentId !== sourceData.parentId){ + handleMoveToFolder(sourceData.id,parentId) + } + } else { + if(sourceData.isChild){ + handleRemoveFromFavoritesFolder(sourceData.id) + } + } + if(droppedFavId){ + if(instruction === 'make-child') return; /** Reorder iniside the folder skipped here. It is handled in root element */ const destinationSequence = getDestinationStateSequence(groupedFavorites,droppedFavId,instruction) handleReorder(sourceData.id,destinationSequence || 0) } - - if(!parentId && !droppedFavId) return - if(sourceData.isGroup) return - if(sourceData.parentId === parentId) return - - if(!parentId && sourceData.isChild){ - handleRemoveFromFavoritesFolder(sourceData.id) - return - } - if(parentId){ - handleMoveToFolder(sourceData.id,parentId) - } - // if(parentId) // handleMoveToFolder(sourceData.id,parentId); diff --git a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx index 2acc38139c5..bfed7668203 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx @@ -148,7 +148,7 @@ export const FavoriteRoot: FC = observer((props) => { const parentId = instruction === 'make-child' ? dropTargetData.id : dropTargetData.parentId; const droppedFavId = instruction !== "make-child" ? dropTargetData.id : undefined; const sourceData = source.data as TargetData; - + if(droppedFavId && sourceData.id){ const destinationSequence = getDestinationStateSequence(favoriteMap,droppedFavId,instruction) handleReorder(sourceData.id,destinationSequence || 0) diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index 14d0b7ee54e..c9253a67708 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -69,11 +69,11 @@ export const SidebarFavoritesMenu = observer(() => { parent: null, }) .then(() => { - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Favorite moved successfully.", - }); + // setToast({ + // type: TOAST_TYPE.SUCCESS, + // title: "Success!", + // message: "Favorite moved successfully.", + // }); }) .catch(() => { setToast({ @@ -89,11 +89,11 @@ export const SidebarFavoritesMenu = observer(() => { sequence: sequence }) .then(() => { - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Reordered successfully.", - }); + // setToast({ + // type: TOAST_TYPE.SUCCESS, + // title: "Success!", + // message: "Reordered successfully.", + // }); }) .catch(() => { setToast({ diff --git a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts index cbf95fd02fe..cd1c379a2cc 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts +++ b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts @@ -1,6 +1,7 @@ import { extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item"; import orderBy from "lodash/orderBy"; import { IFavorite, InstructionType, IPragmaticPayloadLocation, TDropTarget } from "@plane/types"; +import { result } from "lodash"; export type TargetData = { id: string; @@ -17,6 +18,7 @@ export const getDestinationStateSequence = ( const defaultSequence = 65535; if (!edge) return defaultSequence; + const favoriteIds = orderBy(Object.values(favoriteMap), "sequence", "desc") .filter((fav: IFavorite) => !fav.parent) .map((fav: IFavorite) => fav.id); @@ -25,21 +27,31 @@ export const getDestinationStateSequence = ( if (!destinationStateSequence) return defaultSequence; + + let resultSequence = defaultSequence; if (edge === "reorder-above") { const prevStateSequence = favoriteMap[favoriteIds[destinationStateIndex - 1]]?.sequence || undefined; if (prevStateSequence === undefined) { - return destinationStateSequence + defaultSequence; - } - return (destinationStateSequence + prevStateSequence) / 2; - } else if (edge === "reorder-below") { + resultSequence = destinationStateSequence + defaultSequence; + }else { + resultSequence = (destinationStateSequence + prevStateSequence) / 2 + } + } else if (edge === "reorder-below") { const nextStateSequence = favoriteMap[favoriteIds[destinationStateIndex + 1]]?.sequence || undefined; if (nextStateSequence === undefined) { - return destinationStateSequence - defaultSequence; + resultSequence = destinationStateSequence - defaultSequence; + } else { + resultSequence = (destinationStateSequence + nextStateSequence) / 2; } - return (destinationStateSequence + nextStateSequence) / 2; } + + console.log({resultSequence}); + + resultSequence = Math.round(resultSequence) + + return resultSequence; }; /** From b2a58a382f6bfbf7f102082e3ff47c2bc4dfa6ca Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Sat, 23 Nov 2024 00:10:42 +0530 Subject: [PATCH 05/15] fixed lint issues --- .../workspace/sidebar/favorites/favorite-folder.tsx | 4 ++-- .../workspace/sidebar/favorites/favorite-items/root.tsx | 4 ++-- .../workspace/sidebar/favorites/favorites.helpers.ts | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index 55452fe7767..f50410db491 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -167,11 +167,11 @@ export const FavoriteFolder: React.FC = (props) => { const destinationSequence = getDestinationStateSequence(groupedFavorites,droppedFavId,instruction) handleReorder(sourceData.id,destinationSequence || 0) } - + // if(parentId) // handleMoveToFolder(sourceData.id,parentId); - // else + // else // handleRemoveFromFavoritesFolder(sourceData.id) // setIsDragging(false); diff --git a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx index bfed7668203..07083cde52c 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx @@ -148,12 +148,12 @@ export const FavoriteRoot: FC = observer((props) => { const parentId = instruction === 'make-child' ? dropTargetData.id : dropTargetData.parentId; const droppedFavId = instruction !== "make-child" ? dropTargetData.id : undefined; const sourceData = source.data as TargetData; - + if(droppedFavId && sourceData.id){ const destinationSequence = getDestinationStateSequence(favoriteMap,droppedFavId,instruction) handleReorder(sourceData.id,destinationSequence || 0) } - + if(!parentId && sourceData.isChild){ handleRemoveFromFavoritesFolder(sourceData.id) } diff --git a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts index cd1c379a2cc..06729f29872 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts +++ b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts @@ -1,7 +1,6 @@ import { extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item"; import orderBy from "lodash/orderBy"; import { IFavorite, InstructionType, IPragmaticPayloadLocation, TDropTarget } from "@plane/types"; -import { result } from "lodash"; export type TargetData = { id: string; @@ -36,8 +35,8 @@ export const getDestinationStateSequence = ( resultSequence = destinationStateSequence + defaultSequence; }else { resultSequence = (destinationStateSequence + prevStateSequence) / 2 - } - } else if (edge === "reorder-below") { + } + } else if (edge === "reorder-below") { const nextStateSequence = favoriteMap[favoriteIds[destinationStateIndex + 1]]?.sequence || undefined; if (nextStateSequence === undefined) { From 1b124c61e59d991f1bb4a2bcebbda4ee924cad35 Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Sat, 23 Nov 2024 00:43:32 +0530 Subject: [PATCH 06/15] memoized reorder --- .../sidebar/favorites/favorite-folder.tsx | 2 +- .../sidebar/favorites/favorites-menu.tsx | 34 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index f50410db491..2fc59e523fc 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -188,7 +188,7 @@ export const FavoriteFolder: React.FC = (props) => { }) ); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [elementRef.current, isDragging, favorite.id, handleMoveToFolder]); + }, [isDragging, favorite.id, handleMoveToFolder]); useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false)); diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index c9253a67708..c8e76b25482 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useCallback, 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 orderBy from "lodash/orderBy"; @@ -84,25 +84,23 @@ export const SidebarFavoritesMenu = observer(() => { }); }; - const handleReorder = (favoriteId: string, sequence: number) => { - reOrderFavorite(workspaceSlug.toString(), favoriteId, { - sequence: sequence - }) - .then(() => { - // setToast({ - // type: TOAST_TYPE.SUCCESS, - // title: "Success!", - // message: "Reordered successfully.", - // }); + const handleReorder = useCallback( + (favoriteId: string, sequence: number) => { + reOrderFavorite(workspaceSlug.toString(), favoriteId, { + sequence: sequence, }) - .catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Failed reorder", + .catch(() => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Failed reorder favorite", + }); }); - }); - } + }, + [workspaceSlug,reOrderFavorite] + ); + + useEffect(() => { if (sidebarCollapsed) toggleFavoriteMenu(true); }, [sidebarCollapsed, toggleFavoriteMenu]); From d1cc0e0f8e53ccf3e560617370f9ab4dfaf4031b Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Mon, 25 Nov 2024 16:02:21 +0530 Subject: [PATCH 07/15] removed unnecessary comments --- .../sidebar/favorites/favorite-folder.tsx | 24 ------------------- .../sidebar/favorites/favorites-menu.tsx | 8 ------- 2 files changed, 32 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index 2fc59e523fc..c5a3bc310f0 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -61,13 +61,6 @@ export const FavoriteFolder: React.FC = (props) => { moveFavoriteToFolder(workspaceSlug.toString(), source, { parent: destination, }) - .then(() => { - // setToast({ - // type: TOAST_TYPE.SUCCESS, - // title: "Success!", - // message: "Favorite moved successfully.", - // }); - }) .catch(() => { setToast({ type: TOAST_TYPE.ERROR, @@ -167,23 +160,6 @@ export const FavoriteFolder: React.FC = (props) => { const destinationSequence = getDestinationStateSequence(groupedFavorites,droppedFavId,instruction) handleReorder(sourceData.id,destinationSequence || 0) } - - - // if(parentId) - // handleMoveToFolder(sourceData.id,parentId); - // else - // handleRemoveFromFavoritesFolder(sourceData.id) - - // setIsDragging(false); - // const sourceId = source?.data?.id as string | undefined; - // const destinationId = self?.data?.id as string | undefined; - - // if (source.data.is_folder) return; - // if (sourceId === destinationId) return; - // if (!sourceId || !destinationId) return; - // if (groupedFavorites[sourceId].parent === destinationId) return; - - // handleMoveToFolder(sourceId, destinationId); }, }) ); diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index c8e76b25482..5a813693b33 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -68,13 +68,6 @@ export const SidebarFavoritesMenu = observer(() => { id: favoriteId, parent: null, }) - .then(() => { - // setToast({ - // type: TOAST_TYPE.SUCCESS, - // title: "Success!", - // message: "Favorite moved successfully.", - // }); - }) .catch(() => { setToast({ type: TOAST_TYPE.ERROR, @@ -127,7 +120,6 @@ export const SidebarFavoritesMenu = observer(() => { const sourceId = source?.data?.id as string | undefined; console.log({ sourceId }); if (!sourceId || !groupedFavorites[sourceId].parent) return; - // handleRemoveFromFavoritesFolder(sourceId); }, }) ); From 9535a4af3ec9b68020e635129a8567e6a483817b Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Tue, 26 Nov 2024 16:40:57 +0530 Subject: [PATCH 08/15] seprated duplicate logic to a common file --- .../sidebar/favorites/favorite-folder.tsx | 63 ++-------- .../sidebar/favorites/favorite-items/root.tsx | 38 ++---- .../sidebar/favorites/favorites-menu.tsx | 116 ++++++++++++++---- .../sidebar/favorites/favorites.helpers.ts | 3 - web/core/store/favorite.store.ts | 7 +- 5 files changed, 113 insertions(+), 114 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index c5a3bc310f0..ab18d869791 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; +import { DragLocationHistory, ElementDragPayload, DropTargetRecord } from "@atlaskit/pragmatic-drag-and-drop/dist/types/internal-types"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview"; import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; @@ -17,16 +18,15 @@ import { Disclosure, Transition } from "@headlessui/react"; import { useOutsideClickDetector } from "@plane/helpers"; // ui import { IFavorite, InstructionType } from "@plane/types"; -import { CustomMenu, Tooltip, DropIndicator, setToast, TOAST_TYPE, FavoriteFolderIcon, DragHandle } from "@plane/ui"; +import { CustomMenu, Tooltip, DropIndicator, FavoriteFolderIcon, DragHandle } from "@plane/ui"; // helpers import { cn } from "@/helpers/common.helper"; // hooks import { useAppTheme } from "@/hooks/store"; -import { useFavorite } from "@/hooks/store/use-favorite"; import { usePlatformOS } from "@/hooks/use-platform-os"; // constants import { FavoriteRoot } from "./favorite-items"; -import { getCanDrop, TargetData, getInstructionFromPayload, getDestinationStateSequence } from "./favorites.helpers"; +import { getCanDrop, getInstructionFromPayload } from "./favorites.helpers"; import { NewFavoriteFolder } from "./new-fav-folder"; type Props = { @@ -35,15 +35,15 @@ type Props = { handleRemoveFromFavorites: (favorite: IFavorite) => void; handleRemoveFromFavoritesFolder: (favoriteId: string) => void; handleReorder: (favoriteId: string, sequence: number) => void; + handleDrop: (self: DropTargetRecord,source: ElementDragPayload, location: DragLocationHistory) => void; }; export const FavoriteFolder: React.FC = (props) => { - const { favorite, handleRemoveFromFavorites, handleRemoveFromFavoritesFolder, handleReorder, isLastChild} = props; + const { favorite, handleRemoveFromFavorites, isLastChild, handleDrop } = props; // store hooks const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme(); const { isMobile } = usePlatformOS(); - const { getGroupedFavorites, groupedFavorites, moveFavoriteToFolder } = useFavorite(); const { workspaceSlug } = useParams(); // states const [isMenuActive, setIsMenuActive] = useState(false); @@ -55,20 +55,8 @@ export const FavoriteFolder: React.FC = (props) => { const actionSectionRef = useRef(null); const elementRef = useRef(null); - if(!favorite.children) getGroupedFavorites(workspaceSlug.toString(), favorite.id); + // if(!favorite.children) getGroupedFavorites(workspaceSlug.toString(), favorite.id); - const handleMoveToFolder = (source: string, destination: string) => { - moveFavoriteToFolder(workspaceSlug.toString(), source, { - parent: destination, - }) - .catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Failed to move favorite.", - }); - }); - }; useEffect(() => { @@ -130,41 +118,14 @@ export const FavoriteFolder: React.FC = (props) => { onDragLeave: () => { setInstruction(undefined); }, - onDrop: ({ source, location }) => { + onDrop: ({ self, source, location})=>{ setInstruction(undefined); - - const dropTargets = location?.current?.dropTargets ?? [] - if(!dropTargets || dropTargets.length <= 0) return; - const dropTarget = dropTargets.length > 1 ? dropTargets.find(target=>target?.data?.isChild) : dropTargets[0]; - - const dropTargetData = dropTarget?.data as TargetData; - - if(!dropTarget || !dropTargetData) return; - const instruction = getInstructionFromPayload(dropTarget, source, location); - const parentId = instruction === 'make-child' ? dropTargetData.id : dropTargetData.parentId; - const droppedFavId = instruction !== "make-child" ? dropTargetData.id : undefined; - const sourceData = source.data as TargetData; - - if(!sourceData.id) return - if(parentId){ - if(parentId !== sourceData.parentId){ - handleMoveToFolder(sourceData.id,parentId) - } - } else { - if(sourceData.isChild){ - handleRemoveFromFavoritesFolder(sourceData.id) - } - } - if(droppedFavId){ - if(instruction === 'make-child') return; /** Reorder iniside the folder skipped here. It is handled in root element */ - const destinationSequence = getDestinationStateSequence(groupedFavorites,droppedFavId,instruction) - handleReorder(sourceData.id,destinationSequence || 0) - } - }, + handleDrop(self, source,location); + } }) ); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isDragging, favorite.id, handleMoveToFolder]); + }, [isDragging, favorite.id ]); useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false)); @@ -335,9 +296,7 @@ export const FavoriteFolder: React.FC = (props) => { isLastChild={index === favorite.children.length - 1} parentId={favorite.id} handleRemoveFromFavorites={handleRemoveFromFavorites} - handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder} - favoriteMap={groupedFavorites} - handleReorder={handleReorder} + handleDrop={handleDrop} /> ))} diff --git a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx index 07083cde52c..5f0f4744d6e 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx @@ -2,7 +2,8 @@ import React, { FC, useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; -import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; +import { DropTargetRecord, DragLocationHistory } from "@atlaskit/pragmatic-drag-and-drop/dist/types/internal-types"; +import { draggable, dropTargetForElements, ElementDragPayload } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview"; import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview"; import { attachInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item"; @@ -25,7 +26,7 @@ import { import { useAppTheme } from "@/hooks/store"; import { useFavoriteItemDetails } from "@/hooks/use-favorite-item-details"; //helpers -import { getCanDrop, TargetData , getInstructionFromPayload, getDestinationStateSequence} from "../favorites.helpers"; +import { getCanDrop, getInstructionFromPayload} from "../favorites.helpers"; type Props = { @@ -33,10 +34,8 @@ type Props = { parentId: string | undefined; workspaceSlug: string; favorite: IFavorite; - favoriteMap: Record; handleRemoveFromFavorites: (favorite: IFavorite) => void; - handleRemoveFromFavoritesFolder: (favoriteId: string) => void; - handleReorder: (favoriteId: string, sequence: number) => void; + handleDrop: (self: DropTargetRecord,source: ElementDragPayload, location: DragLocationHistory) => void; }; export const FavoriteRoot: FC = observer((props) => { @@ -46,10 +45,8 @@ export const FavoriteRoot: FC = observer((props) => { parentId, workspaceSlug, favorite, - favoriteMap, handleRemoveFromFavorites, - handleRemoveFromFavoritesFolder, - handleReorder, + handleDrop, } = props; // store hooks const { sidebarCollapsed } = useAppTheme(); @@ -133,30 +130,9 @@ export const FavoriteRoot: FC = observer((props) => { onDragLeave: () => { setInstruction(undefined); }, - onDrop: ({ source, location }) => { + onDrop: ({ self, source, location }) => { setInstruction(undefined); - const dropTargets = location?.current?.dropTargets ?? [] - if(!dropTargets || dropTargets.length <= 0) return; - - const dropTarget = dropTargets.length > 1 ? dropTargets.find(target=>target?.data?.isChild) : dropTargets[0]; - - const dropTargetData = dropTarget?.data as TargetData; - - if(!dropTarget || !dropTargetData) return; - - const instruction = getInstructionFromPayload(dropTarget, source, location); - const parentId = instruction === 'make-child' ? dropTargetData.id : dropTargetData.parentId; - const droppedFavId = instruction !== "make-child" ? dropTargetData.id : undefined; - const sourceData = source.data as TargetData; - - if(droppedFavId && sourceData.id){ - const destinationSequence = getDestinationStateSequence(favoriteMap,droppedFavId,instruction) - handleReorder(sourceData.id,destinationSequence || 0) - } - - if(!parentId && sourceData.isChild){ - handleRemoveFromFavoritesFolder(sourceData.id) - } + handleDrop(self,source,location) }, }) ); diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index 5a813693b33..024f2cdf890 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -2,6 +2,11 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; +import { + DragLocationHistory, + DropTargetRecord, + ElementDragPayload, +} from "@atlaskit/pragmatic-drag-and-drop/dist/types/internal-types"; import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import orderBy from "lodash/orderBy"; import { observer } from "mobx-react"; @@ -23,6 +28,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components import { FavoriteFolder } from "./favorite-folder"; import { FavoriteRoot } from "./favorite-items"; +import { getDestinationStateSequence, getInstructionFromPayload, TargetData } from "./favorites.helpers"; import { NewFavoriteFolder } from "./new-fav-folder"; export const SidebarFavoritesMenu = observer(() => { @@ -33,7 +39,14 @@ export const SidebarFavoritesMenu = observer(() => { // store hooks const { sidebarCollapsed } = useAppTheme(); - const { favoriteIds, groupedFavorites, deleteFavorite, removeFromFavoriteFolder, reOrderFavorite } = useFavorite(); + const { + favoriteIds, + groupedFavorites, + deleteFavorite, + removeFromFavoriteFolder, + reOrderFavorite, + moveFavoriteToFolder, + } = useFavorite(); const { workspaceSlug } = useParams(); const { isMobile } = usePlatformOS(); @@ -46,6 +59,66 @@ export const SidebarFavoritesMenu = observer(() => { const containerRef = useRef(null); const elementRef = useRef(null); + const handleMoveToFolder = (sourceId: string, destinationId: string) => { + moveFavoriteToFolder(workspaceSlug.toString(), sourceId, { + parent: destinationId, + }).catch(() => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Failed to move favorite.", + }); + }); + }; + + const handleDrop = (self: DropTargetRecord, source: ElementDragPayload, location: DragLocationHistory) => { + const isFolder = self.data?.isGroup; + const dropTargets = location?.current?.dropTargets ?? []; + if (!dropTargets || dropTargets.length <= 0) return; + const dropTarget = + dropTargets.length > 1 ? dropTargets.find((target: DropTargetRecord) => target?.data?.isChild) : dropTargets[0]; + + const dropTargetData = dropTarget?.data as TargetData; + + if (!dropTarget || !dropTargetData) return; + const instruction = getInstructionFromPayload(dropTarget, source, location); + const parentId = instruction === "make-child" ? dropTargetData.id : dropTargetData.parentId; + const droppedFavId = instruction !== "make-child" ? dropTargetData.id : undefined; + const sourceData = source.data as TargetData; + + if (!sourceData.id) return; + + if (isFolder) { + // handle move to a new parent folder if dropped on a folder + if (parentId && parentId !== sourceData.parentId) { + handleMoveToFolder(sourceData.id, parentId); + } + //handle remove from folder if dropped outside of the folder + if (parentId && sourceData.isChild) { + handleRemoveFromFavoritesFolder(sourceData.id); + } + + // handle reordering at root level + if (droppedFavId) { + if (instruction != "make-child") { + const destinationSequence = getDestinationStateSequence(groupedFavorites, droppedFavId, instruction); + handleReorder(sourceData.id, destinationSequence || 0); + } + } + } else { + //handling reordering for favorites + if (droppedFavId) { + const destinationSequence = getDestinationStateSequence(groupedFavorites, droppedFavId, instruction); + handleReorder(sourceData.id, destinationSequence || 0); + } + + // handle removal from folder if dropped outside a folder + if (!parentId && sourceData.isChild) { + handleRemoveFromFavoritesFolder(sourceData.id); + } + } + }; + const handleRemoveFromFavorites = (favorite: IFavorite) => { deleteFavorite(workspaceSlug.toString(), favorite.id) .then(() => { @@ -64,36 +137,30 @@ export const SidebarFavoritesMenu = observer(() => { }); }; const handleRemoveFromFavoritesFolder = (favoriteId: string) => { - removeFromFavoriteFolder(workspaceSlug.toString(), favoriteId, { - id: favoriteId, - parent: null, - }) - .catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Failed to move favorite.", - }); + removeFromFavoriteFolder(workspaceSlug.toString(), favoriteId).catch(() => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Failed to move favorite.", }); + }); }; const handleReorder = useCallback( (favoriteId: string, sequence: number) => { reOrderFavorite(workspaceSlug.toString(), favoriteId, { sequence: sequence, - }) - .catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Failed reorder favorite", - }); + }).catch(() => { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Failed reorder favorite", }); + }); }, - [workspaceSlug,reOrderFavorite] + [workspaceSlug, reOrderFavorite] ); - useEffect(() => { if (sidebarCollapsed) toggleFavoriteMenu(true); }, [sidebarCollapsed, toggleFavoriteMenu]); @@ -148,7 +215,7 @@ export const SidebarFavoritesMenu = observer(() => { { setCreateNewFolder(true); - if(!isFavoriteMenuOpen) toggleFavoriteMenu(!isFavoriteMenuOpen); + if (!isFavoriteMenuOpen) toggleFavoriteMenu(!isFavoriteMenuOpen); }} className={cn("size-4 flex-shrink-0 text-custom-sidebar-text-400 transition-transform")} /> @@ -189,7 +256,7 @@ export const SidebarFavoritesMenu = observer(() => { ) : ( orderBy(Object.values(groupedFavorites), "sequence", "desc") .filter((fav) => !fav.parent) - .map((fav, index, {length}) => ( + .map((fav, index, { length }) => ( { handleRemoveFromFavorites={handleRemoveFromFavorites} handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder} handleReorder={handleReorder} + handleDrop={handleDrop} /> ) : ( { isLastChild={index === length - 1} parentId={undefined} handleRemoveFromFavorites={handleRemoveFromFavorites} - handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder} - favoriteMap={groupedFavorites} - handleReorder={handleReorder} + handleDrop={handleDrop} /> )} diff --git a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts index 06729f29872..94d54f89475 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts +++ b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts @@ -45,9 +45,6 @@ export const getDestinationStateSequence = ( resultSequence = (destinationStateSequence + nextStateSequence) / 2; } } - - console.log({resultSequence}); - resultSequence = Math.round(resultSequence) return resultSequence; diff --git a/web/core/store/favorite.store.ts b/web/core/store/favorite.store.ts index da2a5b4c92f..cc1ac1b2df2 100644 --- a/web/core/store/favorite.store.ts +++ b/web/core/store/favorite.store.ts @@ -29,7 +29,7 @@ export interface IFavoriteStore { moveFavoriteToFolder: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; removeFavoriteEntity: (workspaceSlug: string, entityId: string) => Promise; reOrderFavorite: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; - removeFromFavoriteFolder: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; + removeFromFavoriteFolder: (workspaceSlug: string, favoriteId: string) => Promise; removeFavoriteFromStore: (entity_identifier: string) => void; } @@ -207,14 +207,15 @@ export class FavoriteStore implements IFavoriteStore { } }; - removeFromFavoriteFolder = async (workspaceSlug: string, favoriteId: string, data: Partial) => { + removeFromFavoriteFolder = async (workspaceSlug: string, favoriteId: string) => { const parent = this.favoriteMap[favoriteId].parent; try { runInAction(() => { //remove parent set(this.favoriteMap, [favoriteId, "parent"], null); }); - await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, data); + await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { parent: null}); + } catch (error) { console.error("Failed to move favorite"); runInAction(() => { From 797a7567c946c0f93125119adfec9371bca8b4b5 Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Tue, 26 Nov 2024 16:51:55 +0530 Subject: [PATCH 09/15] removed code comments --- .../workspace/sidebar/favorites/favorite-folder.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index ab18d869791..5956c8d2e66 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -55,10 +55,6 @@ export const FavoriteFolder: React.FC = (props) => { const actionSectionRef = useRef(null); const elementRef = useRef(null); - // if(!favorite.children) getGroupedFavorites(workspaceSlug.toString(), favorite.id); - - - useEffect(() => { const element = elementRef.current; From 997ea5b6e461017630574944fd07e6ff03ed4697 Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Thu, 28 Nov 2024 20:14:39 +0530 Subject: [PATCH 10/15] fixed favorite remove while reorder inside folder --- .../components/workspace/sidebar/favorites/favorites-menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index 024f2cdf890..7b7fa6bbaac 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -94,7 +94,7 @@ export const SidebarFavoritesMenu = observer(() => { handleMoveToFolder(sourceData.id, parentId); } //handle remove from folder if dropped outside of the folder - if (parentId && sourceData.isChild) { + if (parentId && parentId !== sourceData.parentId && sourceData.isChild) { handleRemoveFromFavoritesFolder(sourceData.id); } From 073cc0670410b7e91ee7725946c134b0044a4b21 Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Fri, 29 Nov 2024 12:20:05 +0530 Subject: [PATCH 11/15] fixed folder remove while reorder inside folder --- .../components/workspace/sidebar/favorites/favorites.helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts index 94d54f89475..bf003e2c1a3 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts +++ b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts @@ -37,7 +37,7 @@ export const getDestinationStateSequence = ( resultSequence = (destinationStateSequence + prevStateSequence) / 2 } } else if (edge === "reorder-below") { - const nextStateSequence = favoriteMap[favoriteIds[destinationStateIndex + 1]]?.sequence || undefined; + const nextStateSequence = favoriteMap[favoriteIds.length - 1]?.sequence || undefined; if (nextStateSequence === undefined) { resultSequence = destinationStateSequence - defaultSequence; From 65248c7a4f3d04e15d62b8ea974ccde405370974 Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Fri, 29 Nov 2024 15:18:41 +0530 Subject: [PATCH 12/15] fixed-reorder issue --- .../sidebar/favorites/favorite-folder.tsx | 1 - .../sidebar/favorites/favorite-items/root.tsx | 2 +- .../sidebar/favorites/favorites-menu.tsx | 17 ++++---- .../sidebar/favorites/favorites.helpers.ts | 41 ------------------- web/core/store/favorite.store.ts | 41 +++++++++++++++---- 5 files changed, 42 insertions(+), 60 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx index 5956c8d2e66..f7bad0400a6 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-folder.tsx @@ -34,7 +34,6 @@ type Props = { favorite: IFavorite; handleRemoveFromFavorites: (favorite: IFavorite) => void; handleRemoveFromFavoritesFolder: (favoriteId: string) => void; - handleReorder: (favoriteId: string, sequence: number) => void; handleDrop: (self: DropTargetRecord,source: ElementDragPayload, location: DragLocationHistory) => void; }; diff --git a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx index 5f0f4744d6e..9a8dcf312de 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx @@ -157,7 +157,7 @@ export const FavoriteRoot: FC = observer((props) => { /> )} - { isLastChild && } + ); }); diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index 7b7fa6bbaac..cfde76ff666 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -28,7 +28,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components import { FavoriteFolder } from "./favorite-folder"; import { FavoriteRoot } from "./favorite-items"; -import { getDestinationStateSequence, getInstructionFromPayload, TargetData } from "./favorites.helpers"; +import { getInstructionFromPayload, TargetData } from "./favorites.helpers"; import { NewFavoriteFolder } from "./new-fav-folder"; export const SidebarFavoritesMenu = observer(() => { @@ -101,15 +101,15 @@ export const SidebarFavoritesMenu = observer(() => { // handle reordering at root level if (droppedFavId) { if (instruction != "make-child") { - const destinationSequence = getDestinationStateSequence(groupedFavorites, droppedFavId, instruction); - handleReorder(sourceData.id, destinationSequence || 0); + // const destinationSequence = getgetDestinationSequence(droppedFavId, instruction); + handleReorder(sourceData.id, droppedFavId, instruction); } } } else { //handling reordering for favorites if (droppedFavId) { - const destinationSequence = getDestinationStateSequence(groupedFavorites, droppedFavId, instruction); - handleReorder(sourceData.id, destinationSequence || 0); + // const destinationSequence = getgetDestinationSequence(droppedFavId, instruction); + handleReorder(sourceData.id, droppedFavId, instruction); } // handle removal from folder if dropped outside a folder @@ -147,10 +147,8 @@ export const SidebarFavoritesMenu = observer(() => { }; const handleReorder = useCallback( - (favoriteId: string, sequence: number) => { - reOrderFavorite(workspaceSlug.toString(), favoriteId, { - sequence: sequence, - }).catch(() => { + (favoriteId: string, droppedFavId: string, edge: string | undefined) => { + reOrderFavorite(workspaceSlug.toString(), favoriteId, droppedFavId, edge).catch(() => { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", @@ -271,7 +269,6 @@ export const SidebarFavoritesMenu = observer(() => { isLastChild={index === length - 1} handleRemoveFromFavorites={handleRemoveFromFavorites} handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder} - handleReorder={handleReorder} handleDrop={handleDrop} /> ) : ( diff --git a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts index bf003e2c1a3..a7364c9ed9c 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts +++ b/web/core/components/workspace/sidebar/favorites/favorites.helpers.ts @@ -9,47 +9,6 @@ export type TargetData = { isChild: boolean; } -export const getDestinationStateSequence = ( - favoriteMap: Record, - destinationId: string, - edge: string | undefined -) => { - const defaultSequence = 65535; - if (!edge) return defaultSequence; - - - const favoriteIds = orderBy(Object.values(favoriteMap), "sequence", "desc") - .filter((fav: IFavorite) => !fav.parent) - .map((fav: IFavorite) => fav.id); - const destinationStateIndex = favoriteIds.findIndex((id) => id === destinationId); - const destinationStateSequence = favoriteMap[destinationId]?.sequence || undefined; - - if (!destinationStateSequence) return defaultSequence; - - - let resultSequence = defaultSequence; - if (edge === "reorder-above") { - const prevStateSequence = favoriteMap[favoriteIds[destinationStateIndex - 1]]?.sequence || undefined; - - if (prevStateSequence === undefined) { - resultSequence = destinationStateSequence + defaultSequence; - }else { - resultSequence = (destinationStateSequence + prevStateSequence) / 2 - } - } else if (edge === "reorder-below") { - const nextStateSequence = favoriteMap[favoriteIds.length - 1]?.sequence || undefined; - - if (nextStateSequence === undefined) { - resultSequence = destinationStateSequence - defaultSequence; - } else { - resultSequence = (destinationStateSequence + nextStateSequence) / 2; - } - } - resultSequence = Math.round(resultSequence) - - return resultSequence; -}; - /** * extracts the Payload and translates the instruction for the current dropTarget based on drag and drop payload * @param dropTarget dropTarget for which the instruction is required diff --git a/web/core/store/favorite.store.ts b/web/core/store/favorite.store.ts index cc1ac1b2df2..ac026e4d919 100644 --- a/web/core/store/favorite.store.ts +++ b/web/core/store/favorite.store.ts @@ -1,4 +1,4 @@ -import { uniqBy } from "lodash"; +import { orderBy, result, uniqBy } from "lodash"; import set from "lodash/set"; import { action, observable, makeObservable, runInAction, computed } from "mobx"; import { v4 as uuidv4 } from "uuid"; @@ -28,7 +28,12 @@ export interface IFavoriteStore { getGroupedFavorites: (workspaceSlug: string, favoriteId: string) => Promise; moveFavoriteToFolder: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; removeFavoriteEntity: (workspaceSlug: string, entityId: string) => Promise; - reOrderFavorite: (workspaceSlug: string, favoriteId: string, data: Partial) => Promise; + reOrderFavorite: ( + workspaceSlug: string, + favoriteId: string, + destinationId: string, + edge: string | undefined + ) => Promise; removeFromFavoriteFolder: (workspaceSlug: string, favoriteId: string) => Promise; removeFavoriteFromStore: (entity_identifier: string) => void; } @@ -190,14 +195,37 @@ export class FavoriteStore implements IFavoriteStore { } }; - reOrderFavorite = async (workspaceSlug: string, favoriteId: string, data: Partial) => { + reOrderFavorite = async ( + workspaceSlug: string, + favoriteId: string, + destinationId: string, + edge: string | undefined + ) => { const initialSequence = this.favoriteMap[favoriteId].sequence; try { + let resultSequence = 10000; + if (edge) { + const sortedIds = orderBy(this.favoriteMap, "sequence", "desc").map((fav: IFavorite) => fav.id); + const destinationSequence = this.favoriteMap[destinationId]?.sequence || undefined; + if (destinationSequence) { + const destinationIndex = sortedIds.findIndex((id) => id === destinationId); + if (edge === "reorder-above") { + const prevSequence = this.favoriteMap[sortedIds[destinationIndex - 1]]?.sequence || undefined; + if (prevSequence) { + resultSequence = (destinationSequence + prevSequence) / 2; + } else { + resultSequence = destinationSequence + resultSequence; + } + } else { + resultSequence = destinationSequence - resultSequence; + } + } + } runInAction(() => { - set(this.favoriteMap, [favoriteId, "sequence"], data.sequence); + set(this.favoriteMap, [favoriteId, "sequence"], resultSequence); }); - await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, data); + await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { sequence: resultSequence }); } catch (error) { console.error("Failed to move favorite folder"); runInAction(() => { @@ -214,8 +242,7 @@ export class FavoriteStore implements IFavoriteStore { //remove parent set(this.favoriteMap, [favoriteId, "parent"], null); }); - await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { parent: null}); - + await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { parent: null }); } catch (error) { console.error("Failed to move favorite"); runInAction(() => { From 6501cdd66d1c9d1e8c98b1483b8cdc2d49a379c6 Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Fri, 29 Nov 2024 15:36:38 +0530 Subject: [PATCH 13/15] added last child to drop handled --- .../workspace/sidebar/favorites/favorite-items/root.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx index 9a8dcf312de..5f0f4744d6e 100644 --- a/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorite-items/root.tsx @@ -157,7 +157,7 @@ export const FavoriteRoot: FC = observer((props) => { /> )} - + { isLastChild && } ); }); From e1e7865f263987659130bbd4895817076528ac43 Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Fri, 29 Nov 2024 15:50:48 +0530 Subject: [PATCH 14/15] fixed orderby function --- web/core/store/favorite.store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/store/favorite.store.ts b/web/core/store/favorite.store.ts index ac026e4d919..3f4f636d345 100644 --- a/web/core/store/favorite.store.ts +++ b/web/core/store/favorite.store.ts @@ -205,7 +205,7 @@ export class FavoriteStore implements IFavoriteStore { try { let resultSequence = 10000; if (edge) { - const sortedIds = orderBy(this.favoriteMap, "sequence", "desc").map((fav: IFavorite) => fav.id); + const sortedIds = orderBy(Object.values(this.favoriteMap), "sequence", "desc").map((fav: IFavorite) => fav.id); const destinationSequence = this.favoriteMap[destinationId]?.sequence || undefined; if (destinationSequence) { const destinationIndex = sortedIds.findIndex((id) => id === destinationId); From 9541f5bee6a6a1d2fbd5a09c13a153bced0bd243 Mon Sep 17 00:00:00 2001 From: vamsi krishna Date: Mon, 2 Dec 2024 12:31:51 +0530 Subject: [PATCH 15/15] removed unncessasary comments --- .../components/workspace/sidebar/favorites/favorites-menu.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx index cfde76ff666..8aea968ecaa 100644 --- a/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx +++ b/web/core/components/workspace/sidebar/favorites/favorites-menu.tsx @@ -101,14 +101,12 @@ export const SidebarFavoritesMenu = observer(() => { // handle reordering at root level if (droppedFavId) { if (instruction != "make-child") { - // const destinationSequence = getgetDestinationSequence(droppedFavId, instruction); handleReorder(sourceData.id, droppedFavId, instruction); } } } else { //handling reordering for favorites if (droppedFavId) { - // const destinationSequence = getgetDestinationSequence(droppedFavId, instruction); handleReorder(sourceData.id, droppedFavId, instruction); }