[WEB-2774] Chore: re-ordering functionality for entities in favorites. (#6078)
* fixed re order for favorites * fixed lint errors * added reorder * fixed reorder inside folder * fixed lint issues * memoized reorder * removed unnecessary comments * seprated duplicate logic to a common file * removed code comments
This commit is contained in:
parent
6376a09318
commit
fa2e60101f
6 changed files with 336 additions and 155 deletions
|
|
@ -2,27 +2,31 @@
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
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 { 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 { useParams } from "next/navigation";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
import { PenSquare, Star, MoreHorizontal, ChevronRight, GripVertical } from "lucide-react";
|
import { PenSquare, Star, MoreHorizontal, ChevronRight, GripVertical } from "lucide-react";
|
||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
// plane helpers
|
// plane helpers
|
||||||
import { useOutsideClickDetector } from "@plane/helpers";
|
import { useOutsideClickDetector } from "@plane/helpers";
|
||||||
// ui
|
// ui
|
||||||
import { IFavorite } from "@plane/types";
|
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
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useAppTheme } from "@/hooks/store";
|
import { useAppTheme } from "@/hooks/store";
|
||||||
import { useFavorite } from "@/hooks/store/use-favorite";
|
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// constants
|
// constants
|
||||||
import { FavoriteRoot } from "./favorite-items";
|
import { FavoriteRoot } from "./favorite-items";
|
||||||
import { getDestinationStateSequence } from "./favorites.helpers";
|
import { getCanDrop, getInstructionFromPayload } from "./favorites.helpers";
|
||||||
import { NewFavoriteFolder } from "./new-fav-folder";
|
import { NewFavoriteFolder } from "./new-fav-folder";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -30,132 +34,95 @@ type Props = {
|
||||||
favorite: IFavorite;
|
favorite: IFavorite;
|
||||||
handleRemoveFromFavorites: (favorite: IFavorite) => void;
|
handleRemoveFromFavorites: (favorite: IFavorite) => void;
|
||||||
handleRemoveFromFavoritesFolder: (favoriteId: string) => 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> = (props) => {
|
export const FavoriteFolder: React.FC<Props> = (props) => {
|
||||||
const { favorite, handleRemoveFromFavorites, handleRemoveFromFavoritesFolder } = props;
|
const { favorite, handleRemoveFromFavorites, isLastChild, handleDrop } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
|
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
|
||||||
|
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
const { moveFavorite, getGroupedFavorites, groupedFavorites, moveFavoriteFolder } = useFavorite();
|
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
// states
|
// states
|
||||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [folderToRename, setFolderToRename] = useState<string | boolean | null>(null);
|
const [folderToRename, setFolderToRename] = useState<string | boolean | null>(null);
|
||||||
const [isDraggedOver, setIsDraggedOver] = useState(false);
|
const [instruction, setInstruction] = useState<InstructionType | undefined>(undefined);
|
||||||
const [closestEdge, setClosestEdge] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// refs
|
// refs
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||||
const elementRef = useRef<HTMLDivElement | null>(null);
|
const elementRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
!favorite.children && getGroupedFavorites(workspaceSlug.toString(), favorite.id);
|
|
||||||
|
|
||||||
const handleOnDrop = (source: string, destination: string) => {
|
|
||||||
moveFavorite(workspaceSlug.toString(), source, {
|
|
||||||
parent: destination,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
setToast({
|
|
||||||
type: TOAST_TYPE.SUCCESS,
|
|
||||||
title: "Success!",
|
|
||||||
message: "Favorite moved successfully.",
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setToast({
|
|
||||||
type: TOAST_TYPE.ERROR,
|
|
||||||
title: "Error!",
|
|
||||||
message: "Failed to move favorite.",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
const element = elementRef.current;
|
const element = elementRef.current;
|
||||||
|
|
||||||
if (!element) return;
|
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(
|
return combine(
|
||||||
draggable({
|
draggable({
|
||||||
element,
|
element,
|
||||||
getInitialData: () => initialData,
|
getInitialData: () => initialData,
|
||||||
onDragStart: () => setIsDragging(true),
|
onDragStart: () => setIsDragging(true),
|
||||||
onDrop: (data) => {
|
onGenerateDragPreview: ({ nativeSetDragImage }) =>{
|
||||||
setIsDraggedOver(false);
|
setCustomNativeDragPreview({
|
||||||
if (!data.location.current.dropTargets[0]) return;
|
getOffset: pointerOutsideOfPreview({ x: "0px", y: "0px" }),
|
||||||
const destinationData = data.location.current.dropTargets[0].data;
|
render: ({ container }) => {
|
||||||
|
const root = createRoot(container);
|
||||||
if (favorite.id && destinationData) {
|
root.render(
|
||||||
const edge = extractClosestEdge(destinationData) || undefined;
|
<div className="rounded flex gap-1 bg-custom-background-100 text-sm p-1 pr-2">
|
||||||
const payload = {
|
<div className="size-5 grid place-items-center flex-shrink-0">
|
||||||
id: favorite.id,
|
<FavoriteFolderIcon />
|
||||||
sequence: Math.round(
|
</div>
|
||||||
getDestinationStateSequence(groupedFavorites, destinationData.id as string, edge) || 0
|
<p className="truncate text-sm font-medium text-custom-sidebar-text-200">{favorite.name}</p>
|
||||||
),
|
</div>
|
||||||
};
|
);
|
||||||
|
return () => root.unmount();
|
||||||
handleOnDropFolder(payload);
|
},
|
||||||
}
|
nativeSetDragImage,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDrop: () => {
|
||||||
|
setIsDragging(false)
|
||||||
}, // canDrag: () => isDraggable,
|
}, // canDrag: () => isDraggable,
|
||||||
}),
|
}),
|
||||||
dropTargetForElements({
|
dropTargetForElements({
|
||||||
element,
|
element,
|
||||||
getData: ({ input, element }) =>
|
canDrop: ({ source }) => getCanDrop(source, favorite, false),
|
||||||
attachClosestEdge(initialData, {
|
getData: ({ input, element }) =>{
|
||||||
|
|
||||||
|
const blockedStates: InstructionType[] = [];
|
||||||
|
if(!isLastChild){
|
||||||
|
blockedStates.push('reorder-below');
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachInstruction(initialData,{
|
||||||
input,
|
input,
|
||||||
element,
|
element,
|
||||||
allowedEdges: ["top", "bottom"],
|
currentLevel: 0,
|
||||||
}),
|
indentPerLevel: 0,
|
||||||
onDragEnter: (args) => {
|
mode: isLastChild ? 'last-in-group' : 'standard',
|
||||||
setIsDragging(true);
|
block: blockedStates
|
||||||
setIsDraggedOver(true);
|
})
|
||||||
args.source.data.is_folder && setClosestEdge(extractClosestEdge(args.self.data));
|
},
|
||||||
|
onDrag: ({source, self, location}) => {
|
||||||
|
const instruction = getInstructionFromPayload(self,source, location);
|
||||||
|
setInstruction(instruction);
|
||||||
},
|
},
|
||||||
onDragLeave: () => {
|
onDragLeave: () => {
|
||||||
setIsDragging(false);
|
setInstruction(undefined);
|
||||||
setIsDraggedOver(false);
|
|
||||||
setClosestEdge(null);
|
|
||||||
},
|
|
||||||
onDragStart: () => {
|
|
||||||
setIsDragging(true);
|
|
||||||
},
|
|
||||||
onDrop: ({ self, source }) => {
|
|
||||||
setIsDragging(false);
|
|
||||||
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);
|
|
||||||
},
|
},
|
||||||
|
onDrop: ({ self, source, location})=>{
|
||||||
|
setInstruction(undefined);
|
||||||
|
handleDrop(self, source,location);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [elementRef.current, isDragging, favorite.id, handleOnDrop]);
|
}, [isDragging, favorite.id ]);
|
||||||
|
|
||||||
|
|
||||||
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
||||||
|
|
||||||
|
|
@ -174,10 +141,11 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
|
||||||
// id={`sidebar-${projectId}-${projectListType}`}
|
// id={`sidebar-${projectId}-${projectListType}`}
|
||||||
className={cn("relative", {
|
className={cn("relative", {
|
||||||
"bg-custom-sidebar-background-80 opacity-60": isDragging,
|
"bg-custom-sidebar-background-80 opacity-60": isDragging,
|
||||||
|
"border-[2px] border-custom-primary-100" : instruction === 'make-child'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{/* draggable drop top indicator */}
|
{/* draggable drop top indicator */}
|
||||||
<DropIndicator isVisible={isDraggedOver && closestEdge === "top"} />
|
<DropIndicator isVisible={instruction === "reorder-above"}/>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
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",
|
"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",
|
||||||
|
|
@ -316,21 +284,22 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
|
||||||
"px-2": !isSidebarCollapsed,
|
"px-2": !isSidebarCollapsed,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{uniqBy(favorite.children, "id").map((child) => (
|
{orderBy(favorite.children,'sequence','desc').map((child,index) => (
|
||||||
<FavoriteRoot
|
<FavoriteRoot
|
||||||
key={child.id}
|
key={child.id}
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
favorite={child}
|
favorite={child}
|
||||||
|
isLastChild={index === favorite.children.length - 1}
|
||||||
|
parentId={favorite.id}
|
||||||
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
||||||
handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder}
|
handleDrop={handleDrop}
|
||||||
favoriteMap={groupedFavorites}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Transition>
|
</Transition>
|
||||||
)}
|
)}
|
||||||
{/* draggable drop bottom indicator */}
|
{/* draggable drop bottom indicator */}
|
||||||
<DropIndicator isVisible={isDraggedOver && closestEdge === "bottom"} />{" "}
|
{ isLastChild && <DropIndicator isVisible={instruction === "reorder-below"} />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,20 @@
|
||||||
|
|
||||||
import React, { FC, useEffect, useRef, useState } from "react";
|
import React, { FC, useEffect, useRef, useState } from "react";
|
||||||
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
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";
|
||||||
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// plane helpers
|
// plane helpers
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
import { useOutsideClickDetector } from "@plane/helpers";
|
import { useOutsideClickDetector } from "@plane/helpers";
|
||||||
// ui
|
// ui
|
||||||
import { IFavorite } from "@plane/types";
|
import { IFavorite, InstructionType } from "@plane/types";
|
||||||
// components
|
// components
|
||||||
|
import { DropIndicator } from "@plane/ui";
|
||||||
import {
|
import {
|
||||||
FavoriteItemDragHandle,
|
FavoriteItemDragHandle,
|
||||||
FavoriteItemQuickAction,
|
FavoriteItemQuickAction,
|
||||||
|
|
@ -18,67 +25,114 @@ import {
|
||||||
// hooks
|
// hooks
|
||||||
import { useAppTheme } from "@/hooks/store";
|
import { useAppTheme } from "@/hooks/store";
|
||||||
import { useFavoriteItemDetails } from "@/hooks/use-favorite-item-details";
|
import { useFavoriteItemDetails } from "@/hooks/use-favorite-item-details";
|
||||||
|
//helpers
|
||||||
|
import { getCanDrop, getInstructionFromPayload} from "../favorites.helpers";
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
isLastChild: boolean;
|
||||||
|
parentId: string | undefined;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
favorite: IFavorite;
|
favorite: IFavorite;
|
||||||
favoriteMap: Record<string, IFavorite>;
|
|
||||||
handleRemoveFromFavorites: (favorite: IFavorite) => void;
|
handleRemoveFromFavorites: (favorite: IFavorite) => void;
|
||||||
handleRemoveFromFavoritesFolder: (favoriteId: string) => void;
|
handleDrop: (self: DropTargetRecord,source: ElementDragPayload, location: DragLocationHistory) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FavoriteRoot: FC<Props> = observer((props) => {
|
export const FavoriteRoot: FC<Props> = observer((props) => {
|
||||||
// props
|
// props
|
||||||
const { workspaceSlug, favorite, favoriteMap, handleRemoveFromFavorites, handleRemoveFromFavoritesFolder } = props;
|
const {
|
||||||
|
isLastChild,
|
||||||
|
parentId,
|
||||||
|
workspaceSlug,
|
||||||
|
favorite,
|
||||||
|
handleRemoveFromFavorites,
|
||||||
|
handleDrop,
|
||||||
|
} = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { sidebarCollapsed } = useAppTheme();
|
const { sidebarCollapsed } = useAppTheme();
|
||||||
|
const { itemLink, itemIcon, itemTitle } = useFavoriteItemDetails(workspaceSlug, favorite);
|
||||||
//state
|
//state
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||||
|
const [instruction, setInstruction] = useState<InstructionType | undefined>(undefined);
|
||||||
|
|
||||||
//ref
|
//ref
|
||||||
const elementRef = useRef<HTMLDivElement>(null);
|
const elementRef = useRef<HTMLDivElement>(null);
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const handleQuickAction = (value: boolean) => setIsMenuActive(value);
|
const handleQuickAction = (value: boolean) => setIsMenuActive(value);
|
||||||
|
|
||||||
const { itemLink, itemIcon, itemTitle } = useFavoriteItemDetails(workspaceSlug, favorite);
|
|
||||||
|
|
||||||
// drag and drop
|
// drag and drop
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = elementRef.current;
|
const element = elementRef.current;
|
||||||
|
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
const initialData = { id: favorite.id, isGroup: false, isChild: !!parentId, parentId };
|
||||||
return combine(
|
return combine(
|
||||||
draggable({
|
draggable({
|
||||||
element,
|
element,
|
||||||
dragHandle: elementRef.current,
|
dragHandle: elementRef.current,
|
||||||
canDrag: () => true,
|
getInitialData: () => initialData,
|
||||||
getInitialData: () => ({ id: favorite.id, type: "CHILD" }),
|
|
||||||
onDragStart: () => {
|
onDragStart: () => {
|
||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
},
|
},
|
||||||
onDrop: () => {
|
onDrop: () => {
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
},
|
},
|
||||||
|
onGenerateDragPreview: ({ nativeSetDragImage }) => {
|
||||||
|
setCustomNativeDragPreview({
|
||||||
|
getOffset: pointerOutsideOfPreview({ x: "0px", y: "0px" }),
|
||||||
|
render: ({ container }) => {
|
||||||
|
const root = createRoot(container);
|
||||||
|
root.render(
|
||||||
|
<div className="rounded bg-custom-background-100 text-sm p-1 pr-2">
|
||||||
|
<FavoriteItemTitle
|
||||||
|
href={itemLink}
|
||||||
|
icon={itemIcon}
|
||||||
|
title={itemTitle}
|
||||||
|
isSidebarCollapsed={!!sidebarCollapsed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return () => root.unmount();
|
||||||
|
},
|
||||||
|
nativeSetDragImage,
|
||||||
|
});
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
dropTargetForElements({
|
dropTargetForElements({
|
||||||
element,
|
element,
|
||||||
|
canDrop: ({ source }) => getCanDrop(source, favorite, !!parentId),
|
||||||
onDragStart: () => {
|
onDragStart: () => {
|
||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
},
|
},
|
||||||
onDragEnter: () => {
|
getData: ({ input, element }) =>{
|
||||||
setIsDragging(true);
|
|
||||||
|
const blockedStates: InstructionType[] = ['make-child'];
|
||||||
|
if(!isLastChild){
|
||||||
|
blockedStates.push('reorder-below');
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachInstruction(initialData,{
|
||||||
|
input,
|
||||||
|
element,
|
||||||
|
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: () => {
|
onDragLeave: () => {
|
||||||
setIsDragging(false);
|
setInstruction(undefined);
|
||||||
},
|
},
|
||||||
onDrop: ({ source }) => {
|
onDrop: ({ self, source, location }) => {
|
||||||
setIsDragging(false);
|
setInstruction(undefined);
|
||||||
const sourceId = source?.data?.id as string | undefined;
|
handleDrop(self,source,location)
|
||||||
if (!sourceId || !favoriteMap[sourceId].parent) return;
|
|
||||||
handleRemoveFromFavoritesFolder(sourceId);
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -89,6 +143,7 @@ export const FavoriteRoot: FC<Props> = observer((props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<DropIndicator isVisible={instruction === "reorder-above"}/>
|
||||||
<FavoriteItemWrapper elementRef={elementRef} isMenuActive={isMenuActive} sidebarCollapsed={sidebarCollapsed}>
|
<FavoriteItemWrapper elementRef={elementRef} isMenuActive={isMenuActive} sidebarCollapsed={sidebarCollapsed}>
|
||||||
{!sidebarCollapsed && <FavoriteItemDragHandle isDragging={isDragging} sort_order={favorite.sort_order} />}
|
{!sidebarCollapsed && <FavoriteItemDragHandle isDragging={isDragging} sort_order={favorite.sort_order} />}
|
||||||
<FavoriteItemTitle href={itemLink} icon={itemIcon} title={itemTitle} isSidebarCollapsed={!!sidebarCollapsed} />
|
<FavoriteItemTitle href={itemLink} icon={itemIcon} title={itemTitle} isSidebarCollapsed={!!sidebarCollapsed} />
|
||||||
|
|
@ -102,6 +157,7 @@ export const FavoriteRoot: FC<Props> = observer((props) => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</FavoriteItemWrapper>
|
</FavoriteItemWrapper>
|
||||||
|
{ isLastChild && <DropIndicator isVisible={instruction === "reorder-below"} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
"use client";
|
"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 { 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 { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||||
import orderBy from "lodash/orderBy";
|
import orderBy from "lodash/orderBy";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
|
@ -23,6 +28,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web components
|
// plane web components
|
||||||
import { FavoriteFolder } from "./favorite-folder";
|
import { FavoriteFolder } from "./favorite-folder";
|
||||||
import { FavoriteRoot } from "./favorite-items";
|
import { FavoriteRoot } from "./favorite-items";
|
||||||
|
import { getDestinationStateSequence, getInstructionFromPayload, TargetData } from "./favorites.helpers";
|
||||||
import { NewFavoriteFolder } from "./new-fav-folder";
|
import { NewFavoriteFolder } from "./new-fav-folder";
|
||||||
|
|
||||||
export const SidebarFavoritesMenu = observer(() => {
|
export const SidebarFavoritesMenu = observer(() => {
|
||||||
|
|
@ -33,7 +39,14 @@ export const SidebarFavoritesMenu = observer(() => {
|
||||||
|
|
||||||
// store hooks
|
// store hooks
|
||||||
const { sidebarCollapsed } = useAppTheme();
|
const { sidebarCollapsed } = useAppTheme();
|
||||||
const { favoriteIds, groupedFavorites, deleteFavorite, removeFromFavoriteFolder } = useFavorite();
|
const {
|
||||||
|
favoriteIds,
|
||||||
|
groupedFavorites,
|
||||||
|
deleteFavorite,
|
||||||
|
removeFromFavoriteFolder,
|
||||||
|
reOrderFavorite,
|
||||||
|
moveFavoriteToFolder,
|
||||||
|
} = useFavorite();
|
||||||
const { workspaceSlug } = useParams();
|
const { workspaceSlug } = useParams();
|
||||||
|
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
|
|
@ -46,6 +59,66 @@ export const SidebarFavoritesMenu = observer(() => {
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const elementRef = 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) => {
|
const handleRemoveFromFavorites = (favorite: IFavorite) => {
|
||||||
deleteFavorite(workspaceSlug.toString(), favorite.id)
|
deleteFavorite(workspaceSlug.toString(), favorite.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
@ -64,18 +137,7 @@ export const SidebarFavoritesMenu = observer(() => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const handleRemoveFromFavoritesFolder = (favoriteId: string) => {
|
const handleRemoveFromFavoritesFolder = (favoriteId: string) => {
|
||||||
removeFromFavoriteFolder(workspaceSlug.toString(), favoriteId, {
|
removeFromFavoriteFolder(workspaceSlug.toString(), favoriteId).catch(() => {
|
||||||
id: favoriteId,
|
|
||||||
parent: null,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
setToast({
|
|
||||||
type: TOAST_TYPE.SUCCESS,
|
|
||||||
title: "Success!",
|
|
||||||
message: "Favorite moved successfully.",
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
|
|
@ -83,6 +145,22 @@ export const SidebarFavoritesMenu = observer(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[workspaceSlug, reOrderFavorite]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sidebarCollapsed) toggleFavoriteMenu(true);
|
if (sidebarCollapsed) toggleFavoriteMenu(true);
|
||||||
}, [sidebarCollapsed, toggleFavoriteMenu]);
|
}, [sidebarCollapsed, toggleFavoriteMenu]);
|
||||||
|
|
@ -109,7 +187,6 @@ export const SidebarFavoritesMenu = observer(() => {
|
||||||
const sourceId = source?.data?.id as string | undefined;
|
const sourceId = source?.data?.id as string | undefined;
|
||||||
console.log({ sourceId });
|
console.log({ sourceId });
|
||||||
if (!sourceId || !groupedFavorites[sourceId].parent) return;
|
if (!sourceId || !groupedFavorites[sourceId].parent) return;
|
||||||
handleRemoveFromFavoritesFolder(sourceId);
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -138,7 +215,7 @@ export const SidebarFavoritesMenu = observer(() => {
|
||||||
<FolderPlus
|
<FolderPlus
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCreateNewFolder(true);
|
setCreateNewFolder(true);
|
||||||
!isFavoriteMenuOpen && toggleFavoriteMenu(!isFavoriteMenuOpen);
|
if (!isFavoriteMenuOpen) toggleFavoriteMenu(!isFavoriteMenuOpen);
|
||||||
}}
|
}}
|
||||||
className={cn("size-4 flex-shrink-0 text-custom-sidebar-text-400 transition-transform")}
|
className={cn("size-4 flex-shrink-0 text-custom-sidebar-text-400 transition-transform")}
|
||||||
/>
|
/>
|
||||||
|
|
@ -179,7 +256,7 @@ export const SidebarFavoritesMenu = observer(() => {
|
||||||
) : (
|
) : (
|
||||||
orderBy(Object.values(groupedFavorites), "sequence", "desc")
|
orderBy(Object.values(groupedFavorites), "sequence", "desc")
|
||||||
.filter((fav) => !fav.parent)
|
.filter((fav) => !fav.parent)
|
||||||
.map((fav, index) => (
|
.map((fav, index, { length }) => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key={fav.id}
|
key={fav.id}
|
||||||
tooltipContent={fav?.entity_data ? fav.entity_data?.name : fav?.name}
|
tooltipContent={fav?.entity_data ? fav.entity_data?.name : fav?.name}
|
||||||
|
|
@ -191,17 +268,20 @@ export const SidebarFavoritesMenu = observer(() => {
|
||||||
{fav.is_folder ? (
|
{fav.is_folder ? (
|
||||||
<FavoriteFolder
|
<FavoriteFolder
|
||||||
favorite={fav}
|
favorite={fav}
|
||||||
isLastChild={index === favoriteIds.length - 1}
|
isLastChild={index === length - 1}
|
||||||
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
||||||
handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder}
|
handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder}
|
||||||
|
handleReorder={handleReorder}
|
||||||
|
handleDrop={handleDrop}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FavoriteRoot
|
<FavoriteRoot
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
favorite={fav}
|
favorite={fav}
|
||||||
|
isLastChild={index === length - 1}
|
||||||
|
parentId={undefined}
|
||||||
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
handleRemoveFromFavorites={handleRemoveFromFavorites}
|
||||||
handleRemoveFromFavoritesFolder={handleRemoveFromFavoritesFolder}
|
handleDrop={handleDrop}
|
||||||
favoriteMap={groupedFavorites}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
|
import { extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
|
||||||
import orderBy from "lodash/orderBy";
|
import orderBy from "lodash/orderBy";
|
||||||
import { IFavorite } from "@plane/types";
|
import { IFavorite, InstructionType, IPragmaticPayloadLocation, TDropTarget } from "@plane/types";
|
||||||
|
|
||||||
|
export type TargetData = {
|
||||||
|
id: string;
|
||||||
|
parentId: string | null;
|
||||||
|
isGroup: boolean;
|
||||||
|
isChild: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const getDestinationStateSequence = (
|
export const getDestinationStateSequence = (
|
||||||
favoriteMap: Record<string, IFavorite>,
|
favoriteMap: Record<string, IFavorite>,
|
||||||
|
|
@ -9,6 +17,7 @@ export const getDestinationStateSequence = (
|
||||||
const defaultSequence = 65535;
|
const defaultSequence = 65535;
|
||||||
if (!edge) return defaultSequence;
|
if (!edge) return defaultSequence;
|
||||||
|
|
||||||
|
|
||||||
const favoriteIds = orderBy(Object.values(favoriteMap), "sequence", "desc")
|
const favoriteIds = orderBy(Object.values(favoriteMap), "sequence", "desc")
|
||||||
.filter((fav: IFavorite) => !fav.parent)
|
.filter((fav: IFavorite) => !fav.parent)
|
||||||
.map((fav: IFavorite) => fav.id);
|
.map((fav: IFavorite) => fav.id);
|
||||||
|
|
@ -17,19 +26,84 @@ export const getDestinationStateSequence = (
|
||||||
|
|
||||||
if (!destinationStateSequence) return defaultSequence;
|
if (!destinationStateSequence) return defaultSequence;
|
||||||
|
|
||||||
if (edge === "top") {
|
|
||||||
|
let resultSequence = defaultSequence;
|
||||||
|
if (edge === "reorder-above") {
|
||||||
const prevStateSequence = favoriteMap[favoriteIds[destinationStateIndex - 1]]?.sequence || undefined;
|
const prevStateSequence = favoriteMap[favoriteIds[destinationStateIndex - 1]]?.sequence || undefined;
|
||||||
|
|
||||||
if (prevStateSequence === undefined) {
|
if (prevStateSequence === undefined) {
|
||||||
return destinationStateSequence + defaultSequence;
|
resultSequence = destinationStateSequence + defaultSequence;
|
||||||
|
}else {
|
||||||
|
resultSequence = (destinationStateSequence + prevStateSequence) / 2
|
||||||
}
|
}
|
||||||
return (destinationStateSequence + prevStateSequence) / 2;
|
} else if (edge === "reorder-below") {
|
||||||
} else if (edge === "bottom") {
|
|
||||||
const nextStateSequence = favoriteMap[favoriteIds[destinationStateIndex + 1]]?.sequence || undefined;
|
const nextStateSequence = favoriteMap[favoriteIds[destinationStateIndex + 1]]?.sequence || undefined;
|
||||||
|
|
||||||
if (nextStateSequence === undefined) {
|
if (nextStateSequence === undefined) {
|
||||||
return destinationStateSequence - defaultSequence;
|
resultSequence = destinationStateSequence - defaultSequence;
|
||||||
|
} else {
|
||||||
|
resultSequence = (destinationStateSequence + nextStateSequence) / 2;
|
||||||
}
|
}
|
||||||
return (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
|
||||||
|
* @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;
|
||||||
};
|
};
|
||||||
|
|
@ -298,12 +298,13 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
||||||
<>
|
<>
|
||||||
<PublishProjectModal isOpen={publishModalOpen} project={project} onClose={() => setPublishModal(false)} />
|
<PublishProjectModal isOpen={publishModalOpen} project={project} onClose={() => setPublishModal(false)} />
|
||||||
<LeaveProjectModal project={project} isOpen={leaveProjectModalOpen} onClose={() => setLeaveProjectModal(false)} />
|
<LeaveProjectModal project={project} isOpen={leaveProjectModalOpen} onClose={() => setLeaveProjectModal(false)} />
|
||||||
<Disclosure key={`${project.id}_${URLProjectId}`} ref={projectRef} defaultOpen={isProjectListOpen} as="div">
|
<Disclosure key={`${project.id}_${URLProjectId}`} defaultOpen={isProjectListOpen} as="div">
|
||||||
<div
|
<div
|
||||||
id={`sidebar-${projectId}-${projectListType}`}
|
id={`sidebar-${projectId}-${projectListType}`}
|
||||||
className={cn("relative", {
|
className={cn("relative", {
|
||||||
"bg-custom-sidebar-background-80 opacity-60": isDragging,
|
"bg-custom-sidebar-background-80 opacity-60": isDragging,
|
||||||
})}
|
})}
|
||||||
|
ref={projectRef}
|
||||||
>
|
>
|
||||||
<DropIndicator classNames="absolute top-0" isVisible={instruction === "DRAG_OVER"} />
|
<DropIndicator classNames="absolute top-0" isVisible={instruction === "DRAG_OVER"} />
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@ export interface IFavoriteStore {
|
||||||
updateFavorite: (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => Promise<IFavorite>;
|
updateFavorite: (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => Promise<IFavorite>;
|
||||||
deleteFavorite: (workspaceSlug: string, favoriteId: string) => Promise<void>;
|
deleteFavorite: (workspaceSlug: string, favoriteId: string) => Promise<void>;
|
||||||
getGroupedFavorites: (workspaceSlug: string, favoriteId: string) => Promise<IFavorite[]>;
|
getGroupedFavorites: (workspaceSlug: string, favoriteId: string) => Promise<IFavorite[]>;
|
||||||
moveFavorite: (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => Promise<void>;
|
moveFavoriteToFolder: (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => Promise<void>;
|
||||||
removeFavoriteEntity: (workspaceSlug: string, entityId: string) => Promise<void>;
|
removeFavoriteEntity: (workspaceSlug: string, entityId: string) => Promise<void>;
|
||||||
moveFavoriteFolder: (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => Promise<void>;
|
reOrderFavorite: (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => Promise<void>;
|
||||||
removeFromFavoriteFolder: (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => Promise<void>;
|
removeFromFavoriteFolder: (workspaceSlug: string, favoriteId: string) => Promise<void>;
|
||||||
removeFavoriteFromStore: (entity_identifier: string) => void;
|
removeFavoriteFromStore: (entity_identifier: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,9 +64,9 @@ export class FavoriteStore implements IFavoriteStore {
|
||||||
// CRUD actions
|
// CRUD actions
|
||||||
addFavorite: action,
|
addFavorite: action,
|
||||||
getGroupedFavorites: action,
|
getGroupedFavorites: action,
|
||||||
moveFavorite: action,
|
moveFavoriteToFolder: action,
|
||||||
removeFavoriteEntity: action,
|
removeFavoriteEntity: action,
|
||||||
moveFavoriteFolder: action,
|
reOrderFavorite: action,
|
||||||
removeFavoriteEntityFromStore: action,
|
removeFavoriteEntityFromStore: action,
|
||||||
removeFromFavoriteFolder: action,
|
removeFromFavoriteFolder: action,
|
||||||
});
|
});
|
||||||
|
|
@ -168,7 +168,7 @@ export class FavoriteStore implements IFavoriteStore {
|
||||||
* @param data
|
* @param data
|
||||||
* @returns Promise<void>
|
* @returns Promise<void>
|
||||||
*/
|
*/
|
||||||
moveFavorite = async (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => {
|
moveFavoriteToFolder = async (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => {
|
||||||
const oldParent = this.favoriteMap[favoriteId].parent;
|
const oldParent = this.favoriteMap[favoriteId].parent;
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
|
@ -190,7 +190,7 @@ export class FavoriteStore implements IFavoriteStore {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
moveFavoriteFolder = async (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => {
|
reOrderFavorite = async (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => {
|
||||||
const initialSequence = this.favoriteMap[favoriteId].sequence;
|
const initialSequence = this.favoriteMap[favoriteId].sequence;
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
|
@ -207,14 +207,15 @@ export class FavoriteStore implements IFavoriteStore {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
removeFromFavoriteFolder = async (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => {
|
removeFromFavoriteFolder = async (workspaceSlug: string, favoriteId: string) => {
|
||||||
const parent = this.favoriteMap[favoriteId].parent;
|
const parent = this.favoriteMap[favoriteId].parent;
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
//remove parent
|
//remove parent
|
||||||
set(this.favoriteMap, [favoriteId, "parent"], null);
|
set(this.favoriteMap, [favoriteId, "parent"], null);
|
||||||
});
|
});
|
||||||
await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, data);
|
await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { parent: null});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to move favorite");
|
console.error("Failed to move favorite");
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue