mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 17:58:27 +02:00
Better performance on large workspaces
This commit is contained in:
@@ -258,7 +258,7 @@ function SidebarButton({
|
|||||||
</div>
|
</div>
|
||||||
{environment != null && (
|
{environment != null && (
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
show={showContextMenu}
|
triggerPosition={showContextMenu}
|
||||||
onClose={() => setShowContextMenu(null)}
|
onClose={() => setShowContextMenu(null)}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ForwardedRef, ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React, { forwardRef, Fragment, useCallback, useMemo, useRef, useState } from 'react';
|
import React, { Fragment, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import type { XYCoord } from 'react-dnd';
|
import type { XYCoord } from 'react-dnd';
|
||||||
import { useDrag, useDrop } from 'react-dnd';
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
import { useKey, useKeyPressEvent } from 'react-use';
|
import { useKey, useKeyPressEvent } from 'react-use';
|
||||||
@@ -299,7 +299,7 @@ export function Sidebar({ className }: Props) {
|
|||||||
[hasFocus, selectableRequests, selectedId, setSelectedId, setSelectedTree],
|
[hasFocus, selectableRequests, selectedId, setSelectedId, setSelectedTree],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMove = useCallback<DraggableSidebarItemProps['onMove']>(
|
const handleMove = useCallback<SidebarItemProps['onMove']>(
|
||||||
(id, side) => {
|
(id, side) => {
|
||||||
let hoveredTree = treeParentMap[id] ?? null;
|
let hoveredTree = treeParentMap[id] ?? null;
|
||||||
const dragIndex = hoveredTree?.children.findIndex((n) => n.item.id === id) ?? -99;
|
const dragIndex = hoveredTree?.children.findIndex((n) => n.item.id === id) ?? -99;
|
||||||
@@ -318,11 +318,11 @@ export function Sidebar({ className }: Props) {
|
|||||||
[isCollapsed, treeParentMap],
|
[isCollapsed, treeParentMap],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDragStart = useCallback<DraggableSidebarItemProps['onDragStart']>((id: string) => {
|
const handleDragStart = useCallback<SidebarItemProps['onDragStart']>((id: string) => {
|
||||||
setDraggingId(id);
|
setDraggingId(id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleEnd = useCallback<DraggableSidebarItemProps['onEnd']>(
|
const handleEnd = useCallback<SidebarItemProps['onEnd']>(
|
||||||
async (itemId) => {
|
async (itemId) => {
|
||||||
setHoveredTree(null);
|
setHoveredTree(null);
|
||||||
handleClearSelected();
|
handleClearSelected();
|
||||||
@@ -436,7 +436,7 @@ export function Sidebar({ className }: Props) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
show={showMainContextMenu}
|
triggerPosition={showMainContextMenu}
|
||||||
items={mainContextMenuItems}
|
items={mainContextMenuItems}
|
||||||
onClose={() => setShowMainContextMenu(null)}
|
onClose={() => setShowMainContextMenu(null)}
|
||||||
/>
|
/>
|
||||||
@@ -511,8 +511,7 @@ function SidebarItems({
|
|||||||
return (
|
return (
|
||||||
<Fragment key={child.item.id}>
|
<Fragment key={child.item.id}>
|
||||||
{hoveredIndex === i && hoveredTree?.item.id === tree.item.id && <DropMarker />}
|
{hoveredIndex === i && hoveredTree?.item.id === tree.item.id && <DropMarker />}
|
||||||
<DraggableSidebarItem
|
<SidebarItem
|
||||||
draggable
|
|
||||||
selected={selected}
|
selected={selected}
|
||||||
itemId={child.item.id}
|
itemId={child.item.id}
|
||||||
itemName={child.item.name}
|
itemName={child.item.name}
|
||||||
@@ -558,7 +557,7 @@ function SidebarItems({
|
|||||||
handleDragStart={handleDragStart}
|
handleDragStart={handleDragStart}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DraggableSidebarItem>
|
</SidebarItem>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -579,28 +578,74 @@ type SidebarItemProps = {
|
|||||||
useProminentStyles?: boolean;
|
useProminentStyles?: boolean;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
draggable?: boolean;
|
draggable?: boolean;
|
||||||
|
onMove: (id: string, side: 'above' | 'below') => void;
|
||||||
|
onEnd: (id: string) => void;
|
||||||
|
onDragStart: (id: string) => void;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
child: TreeNode;
|
child: TreeNode;
|
||||||
} & Pick<SidebarItemsProps, 'isCollapsed' | 'onSelect'>;
|
} & Pick<SidebarItemsProps, 'isCollapsed' | 'onSelect'>;
|
||||||
|
|
||||||
const SidebarItem = forwardRef(function SidebarItem(
|
type DragItem = {
|
||||||
{
|
id: string;
|
||||||
children,
|
itemName: string;
|
||||||
className,
|
};
|
||||||
itemName,
|
|
||||||
itemFallbackName,
|
function SidebarItem({
|
||||||
itemId,
|
itemName,
|
||||||
itemModel,
|
itemId,
|
||||||
itemPrefix,
|
itemModel,
|
||||||
useProminentStyles,
|
child,
|
||||||
selected,
|
onMove,
|
||||||
onSelect,
|
onEnd,
|
||||||
isCollapsed,
|
onDragStart,
|
||||||
child,
|
onSelect,
|
||||||
draggable,
|
isCollapsed,
|
||||||
}: SidebarItemProps,
|
itemPrefix,
|
||||||
ref: ForwardedRef<HTMLLIElement>,
|
className,
|
||||||
) {
|
selected,
|
||||||
|
itemFallbackName,
|
||||||
|
useProminentStyles,
|
||||||
|
children,
|
||||||
|
}: SidebarItemProps) {
|
||||||
|
const ref = useRef<HTMLLIElement>(null);
|
||||||
|
|
||||||
|
const [, connectDrop] = useDrop<DragItem, void>(
|
||||||
|
{
|
||||||
|
accept: ItemTypes.REQUEST,
|
||||||
|
hover: (_, monitor) => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
const hoverBoundingRect = ref.current?.getBoundingClientRect();
|
||||||
|
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||||
|
const clientOffset = monitor.getClientOffset();
|
||||||
|
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;
|
||||||
|
onMove(itemId, hoverClientY < hoverMiddleY ? 'above' : 'below');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[onMove],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [, connectDrag] = useDrag<
|
||||||
|
DragItem,
|
||||||
|
unknown,
|
||||||
|
{
|
||||||
|
isDragging: boolean;
|
||||||
|
}
|
||||||
|
>(
|
||||||
|
() => ({
|
||||||
|
type: ItemTypes.REQUEST,
|
||||||
|
item: () => {
|
||||||
|
onDragStart(itemId);
|
||||||
|
return { id: itemId, itemName };
|
||||||
|
},
|
||||||
|
collect: (m) => ({ isDragging: m.isDragging() }),
|
||||||
|
options: { dropEffect: 'move' },
|
||||||
|
end: () => onEnd(itemId),
|
||||||
|
}),
|
||||||
|
[onEnd],
|
||||||
|
);
|
||||||
|
|
||||||
|
connectDrag(connectDrop(ref));
|
||||||
|
|
||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
const deleteFolder = useDeleteFolder(itemId);
|
const deleteFolder = useDeleteFolder(itemId);
|
||||||
const deleteRequest = useDeleteRequest(itemId);
|
const deleteRequest = useDeleteRequest(itemId);
|
||||||
@@ -673,134 +718,163 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
y: number;
|
y: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleCloseContextMenu = useCallback(() => {
|
||||||
|
setShowContextMenu(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleContextMenu = useCallback((e: React.MouseEvent<HTMLElement>) => {
|
const handleContextMenu = useCallback((e: React.MouseEvent<HTMLElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setShowContextMenu({ x: e.clientX, y: e.clientY });
|
setShowContextMenu({ x: e.clientX, y: e.clientY });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const items = useMemo<DropdownItem[]>(() => {
|
||||||
|
if (itemModel === 'folder') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'sendAll',
|
||||||
|
label: 'Send All',
|
||||||
|
leftSlot: <Icon icon="sendHorizontal" />,
|
||||||
|
onSelect: () => sendManyRequests.mutate(child.children.map((c) => c.item.id)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'rename',
|
||||||
|
label: 'Rename',
|
||||||
|
leftSlot: <Icon icon="pencil" />,
|
||||||
|
onSelect: async () => {
|
||||||
|
const name = await prompt({
|
||||||
|
id: 'rename-folder',
|
||||||
|
title: 'Rename Folder',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Enter a new name for <InlineCode>{itemName}</InlineCode>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
placeholder: 'New Name',
|
||||||
|
defaultValue: itemName,
|
||||||
|
});
|
||||||
|
updateAnyFolder.mutate({ id: itemId, update: (f) => ({ ...f, name }) });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'deleteFolder',
|
||||||
|
label: 'Delete',
|
||||||
|
variant: 'danger',
|
||||||
|
leftSlot: <Icon icon="trash" />,
|
||||||
|
onSelect: () => deleteFolder.mutate(),
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
...createDropdownItems,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
const requestItems: DropdownItem[] =
|
||||||
|
itemModel === 'http_request'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: 'sendRequest',
|
||||||
|
label: 'Send',
|
||||||
|
hotKeyAction: 'http_request.send',
|
||||||
|
hotKeyLabelOnly: true, // Already bound in URL bar
|
||||||
|
leftSlot: <Icon icon="sendHorizontal" />,
|
||||||
|
onSelect: () => sendRequest.mutate(itemId),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'copyCurl',
|
||||||
|
label: 'Copy as Curl',
|
||||||
|
leftSlot: <Icon icon="copy" />,
|
||||||
|
onSelect: copyAsCurl,
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
return [
|
||||||
|
...requestItems,
|
||||||
|
{
|
||||||
|
key: 'renameRequest',
|
||||||
|
label: 'Rename',
|
||||||
|
leftSlot: <Icon icon="pencil" />,
|
||||||
|
onSelect: async () => {
|
||||||
|
const name = await prompt({
|
||||||
|
id: 'rename-request',
|
||||||
|
title: 'Rename Request',
|
||||||
|
description:
|
||||||
|
itemName === '' ? (
|
||||||
|
'Enter a new name'
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Enter a new name for <InlineCode>{itemName}</InlineCode>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
placeholder: 'New Name',
|
||||||
|
defaultValue: itemName,
|
||||||
|
});
|
||||||
|
if (itemModel === 'http_request') {
|
||||||
|
updateHttpRequest.mutate({ id: itemId, update: (r) => ({ ...r, name }) });
|
||||||
|
} else {
|
||||||
|
updateGrpcRequest.mutate({ id: itemId, update: (r) => ({ ...r, name }) });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'duplicateRequest',
|
||||||
|
label: 'Duplicate',
|
||||||
|
hotKeyAction: 'http_request.duplicate',
|
||||||
|
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
||||||
|
leftSlot: <Icon icon="copy" />,
|
||||||
|
onSelect: () => {
|
||||||
|
itemModel === 'http_request'
|
||||||
|
? duplicateHttpRequest.mutate()
|
||||||
|
: duplicateGrpcRequest.mutate();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'moveWorkspace',
|
||||||
|
label: 'Change Workspace',
|
||||||
|
leftSlot: <Icon icon="house" />,
|
||||||
|
hidden: workspaces.length <= 1,
|
||||||
|
onSelect: moveToWorkspace.mutate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'deleteRequest',
|
||||||
|
variant: 'danger',
|
||||||
|
label: 'Delete',
|
||||||
|
leftSlot: <Icon icon="trash" />,
|
||||||
|
onSelect: () => deleteRequest.mutate(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
child.children,
|
||||||
|
copyAsCurl,
|
||||||
|
createDropdownItems,
|
||||||
|
deleteFolder,
|
||||||
|
deleteRequest,
|
||||||
|
duplicateGrpcRequest,
|
||||||
|
duplicateHttpRequest,
|
||||||
|
itemId,
|
||||||
|
itemModel,
|
||||||
|
itemName,
|
||||||
|
moveToWorkspace.mutate,
|
||||||
|
prompt,
|
||||||
|
sendManyRequests,
|
||||||
|
sendRequest,
|
||||||
|
updateAnyFolder,
|
||||||
|
updateGrpcRequest,
|
||||||
|
updateHttpRequest,
|
||||||
|
workspaces.length,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li ref={ref} draggable={draggable}>
|
<li ref={ref} draggable>
|
||||||
<div className={classNames(className, 'block relative group/item px-1.5 pb-0.5')}>
|
<div className={classNames(className, 'block relative group/item px-1.5 pb-0.5')}>
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
show={showContextMenu}
|
triggerPosition={showContextMenu}
|
||||||
items={
|
items={items}
|
||||||
itemModel === 'folder'
|
onClose={handleCloseContextMenu}
|
||||||
? [
|
|
||||||
{
|
|
||||||
key: 'sendAll',
|
|
||||||
label: 'Send All',
|
|
||||||
leftSlot: <Icon icon="sendHorizontal" />,
|
|
||||||
onSelect: () => sendManyRequests.mutate(child.children.map((c) => c.item.id)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'rename',
|
|
||||||
label: 'Rename',
|
|
||||||
leftSlot: <Icon icon="pencil" />,
|
|
||||||
onSelect: async () => {
|
|
||||||
const name = await prompt({
|
|
||||||
id: 'rename-folder',
|
|
||||||
title: 'Rename Folder',
|
|
||||||
description: (
|
|
||||||
<>
|
|
||||||
Enter a new name for <InlineCode>{itemName}</InlineCode>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
placeholder: 'New Name',
|
|
||||||
defaultValue: itemName,
|
|
||||||
});
|
|
||||||
updateAnyFolder.mutate({ id: itemId, update: (f) => ({ ...f, name }) });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'deleteFolder',
|
|
||||||
label: 'Delete',
|
|
||||||
variant: 'danger',
|
|
||||||
leftSlot: <Icon icon="trash" />,
|
|
||||||
onSelect: () => deleteFolder.mutate(),
|
|
||||||
},
|
|
||||||
{ type: 'separator' },
|
|
||||||
...createDropdownItems,
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
...((itemModel === 'http_request'
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
key: 'sendRequest',
|
|
||||||
label: 'Send',
|
|
||||||
hotKeyAction: 'http_request.send',
|
|
||||||
hotKeyLabelOnly: true, // Already bound in URL bar
|
|
||||||
leftSlot: <Icon icon="sendHorizontal" />,
|
|
||||||
onSelect: () => sendRequest.mutate(itemId),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'copyCurl',
|
|
||||||
label: 'Copy as Curl',
|
|
||||||
leftSlot: <Icon icon="copy" />,
|
|
||||||
onSelect: copyAsCurl,
|
|
||||||
},
|
|
||||||
{ type: 'separator' },
|
|
||||||
]
|
|
||||||
: []) as DropdownItem[]),
|
|
||||||
{
|
|
||||||
key: 'renameRequest',
|
|
||||||
label: 'Rename',
|
|
||||||
leftSlot: <Icon icon="pencil" />,
|
|
||||||
onSelect: async () => {
|
|
||||||
const name = await prompt({
|
|
||||||
id: 'rename-request',
|
|
||||||
title: 'Rename Request',
|
|
||||||
description:
|
|
||||||
itemName === '' ? (
|
|
||||||
'Enter a new name'
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
Enter a new name for <InlineCode>{itemName}</InlineCode>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
placeholder: 'New Name',
|
|
||||||
defaultValue: itemName,
|
|
||||||
});
|
|
||||||
if (itemModel === 'http_request') {
|
|
||||||
updateHttpRequest.mutate({ id: itemId, update: (r) => ({ ...r, name }) });
|
|
||||||
} else {
|
|
||||||
updateGrpcRequest.mutate({ id: itemId, update: (r) => ({ ...r, name }) });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'duplicateRequest',
|
|
||||||
label: 'Duplicate',
|
|
||||||
hotKeyAction: 'http_request.duplicate',
|
|
||||||
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
|
||||||
leftSlot: <Icon icon="copy" />,
|
|
||||||
onSelect: () => {
|
|
||||||
itemModel === 'http_request'
|
|
||||||
? duplicateHttpRequest.mutate()
|
|
||||||
: duplicateGrpcRequest.mutate();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'moveWorkspace',
|
|
||||||
label: 'Change Workspace',
|
|
||||||
leftSlot: <Icon icon="house" />,
|
|
||||||
hidden: workspaces.length <= 1,
|
|
||||||
onSelect: moveToWorkspace.mutate,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'deleteRequest',
|
|
||||||
variant: 'danger',
|
|
||||||
label: 'Delete',
|
|
||||||
leftSlot: <Icon icon="trash" />,
|
|
||||||
onSelect: () => deleteRequest.mutate(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
onClose={() => setShowContextMenu(null)}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
// tabIndex={-1} // Will prevent drag-n-drop
|
// tabIndex={-1} // Will prevent drag-n-drop
|
||||||
@@ -864,79 +938,4 @@ const SidebarItem = forwardRef(function SidebarItem(
|
|||||||
{children}
|
{children}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
type DraggableSidebarItemProps = SidebarItemProps & {
|
|
||||||
onMove: (id: string, side: 'above' | 'below') => void;
|
|
||||||
onEnd: (id: string) => void;
|
|
||||||
onDragStart: (id: string) => void;
|
|
||||||
children?: ReactNode;
|
|
||||||
child?: TreeNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DragItem = {
|
|
||||||
id: string;
|
|
||||||
itemName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function DraggableSidebarItem({
|
|
||||||
itemName,
|
|
||||||
itemId,
|
|
||||||
itemModel,
|
|
||||||
child,
|
|
||||||
onMove,
|
|
||||||
onEnd,
|
|
||||||
onDragStart,
|
|
||||||
...props
|
|
||||||
}: DraggableSidebarItemProps) {
|
|
||||||
const ref = useRef<HTMLLIElement>(null);
|
|
||||||
|
|
||||||
const [, connectDrop] = useDrop<DragItem, void>(
|
|
||||||
{
|
|
||||||
accept: ItemTypes.REQUEST,
|
|
||||||
hover: (_, monitor) => {
|
|
||||||
if (!ref.current) return;
|
|
||||||
const hoverBoundingRect = ref.current?.getBoundingClientRect();
|
|
||||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
|
||||||
const clientOffset = monitor.getClientOffset();
|
|
||||||
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;
|
|
||||||
onMove(itemId, hoverClientY < hoverMiddleY ? 'above' : 'below');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[onMove],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [{ isDragging }, connectDrag] = useDrag<
|
|
||||||
DragItem,
|
|
||||||
unknown,
|
|
||||||
{
|
|
||||||
isDragging: boolean;
|
|
||||||
}
|
|
||||||
>(
|
|
||||||
() => ({
|
|
||||||
type: ItemTypes.REQUEST,
|
|
||||||
item: () => {
|
|
||||||
onDragStart(itemId);
|
|
||||||
return { id: itemId, itemName };
|
|
||||||
},
|
|
||||||
collect: (m) => ({ isDragging: m.isDragging() }),
|
|
||||||
options: { dropEffect: 'move' },
|
|
||||||
end: () => onEnd(itemId),
|
|
||||||
}),
|
|
||||||
[onEnd],
|
|
||||||
);
|
|
||||||
|
|
||||||
connectDrag(connectDrop(ref));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarItem
|
|
||||||
ref={ref}
|
|
||||||
className={classNames(isDragging && 'opacity-20')}
|
|
||||||
itemName={itemName}
|
|
||||||
itemId={itemId}
|
|
||||||
itemModel={itemModel}
|
|
||||||
child={child}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,32 +167,34 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
|
|||||||
});
|
});
|
||||||
|
|
||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
show: { x: number; y: number } | null;
|
triggerPosition: { x: number; y: number } | null;
|
||||||
className?: string;
|
className?: string;
|
||||||
items: DropdownProps['items'];
|
items: DropdownProps['items'];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ContextMenu = forwardRef<DropdownRef, ContextMenuProps>(function ContextMenu(
|
export const ContextMenu = forwardRef<DropdownRef, ContextMenuProps>(function ContextMenu(
|
||||||
{ show, className, items, onClose },
|
{ triggerPosition, className, items, onClose },
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const triggerShape = useMemo(
|
const triggerShape = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
top: show?.y ?? 0,
|
top: triggerPosition?.y ?? 0,
|
||||||
bottom: show?.y ?? 0,
|
bottom: triggerPosition?.y ?? 0,
|
||||||
left: show?.x ?? 0,
|
left: triggerPosition?.x ?? 0,
|
||||||
right: show?.x ?? 0,
|
right: triggerPosition?.x ?? 0,
|
||||||
}),
|
}),
|
||||||
[show],
|
[triggerPosition],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (triggerPosition == null) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
|
isOpen // Always open because we return null if not
|
||||||
className={className}
|
className={className}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
items={items}
|
items={items}
|
||||||
isOpen={show != null}
|
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
triggerShape={triggerShape}
|
triggerShape={triggerShape}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user