mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 17:28:29 +02:00
Allow moving requests and folders to end of list
This commit is contained in:
@@ -9,6 +9,7 @@ import { getAnyModel, patchModelById } from '@yaakapp-internal/models';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
import { useAtom, useAtomValue } from 'jotai';
|
||||||
import React, { useCallback, useRef, useState } from 'react';
|
import React, { useCallback, useRef, useState } from 'react';
|
||||||
|
import { useDrop } from 'react-dnd';
|
||||||
import { useKey, useKeyPressEvent } from 'react-use';
|
import { useKey, useKeyPressEvent } from 'react-use';
|
||||||
import { activeRequestIdAtom } from '../../hooks/useActiveRequestId';
|
import { activeRequestIdAtom } from '../../hooks/useActiveRequestId';
|
||||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||||
@@ -22,6 +23,8 @@ import { router } from '../../lib/router';
|
|||||||
import { setWorkspaceSearchParams } from '../../lib/setWorkspaceSearchParams';
|
import { setWorkspaceSearchParams } from '../../lib/setWorkspaceSearchParams';
|
||||||
import { ContextMenu } from '../core/Dropdown';
|
import { ContextMenu } from '../core/Dropdown';
|
||||||
import { GitDropdown } from '../GitDropdown';
|
import { GitDropdown } from '../GitDropdown';
|
||||||
|
import type { DragItem } from './dnd';
|
||||||
|
import { ItemTypes } from './dnd';
|
||||||
import { sidebarSelectedIdAtom, sidebarTreeAtom } from './SidebarAtoms';
|
import { sidebarSelectedIdAtom, sidebarTreeAtom } from './SidebarAtoms';
|
||||||
import type { SidebarItemProps } from './SidebarItem';
|
import type { SidebarItemProps } from './SidebarItem';
|
||||||
import { SidebarItems } from './SidebarItems';
|
import { SidebarItems } from './SidebarItems';
|
||||||
@@ -204,15 +207,22 @@ export function Sidebar({ className }: Props) {
|
|||||||
[hasFocus, selectableRequests, selectedId, setSelectedId, setSelectedTree],
|
[hasFocus, selectableRequests, selectedId, setSelectedId, setSelectedTree],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleMoveToSidebarEnd = useCallback(() => {
|
||||||
|
setHoveredTree(tree);
|
||||||
|
// Put at the end of the top tree
|
||||||
|
setHoveredIndex(tree?.children?.length ?? 0);
|
||||||
|
}, [tree]);
|
||||||
|
|
||||||
const handleMove = useCallback<SidebarItemProps['onMove']>(
|
const handleMove = useCallback<SidebarItemProps['onMove']>(
|
||||||
async (id, side) => {
|
(id, side) => {
|
||||||
let hoveredTree = treeParentMap[id] ?? null;
|
let hoveredTree = treeParentMap[id] ?? null;
|
||||||
const dragIndex = hoveredTree?.children.findIndex((n) => n.id === id) ?? -99;
|
const dragIndex = hoveredTree?.children.findIndex((n) => n.id === id) ?? -99;
|
||||||
const hoveredItem = hoveredTree?.children[dragIndex] ?? null;
|
const hoveredItem = hoveredTree?.children[dragIndex] ?? null;
|
||||||
let hoveredIndex = dragIndex + (side === 'above' ? 0 : 1);
|
let hoveredIndex = dragIndex + (side === 'above' ? 0 : 1);
|
||||||
|
|
||||||
const isHoveredItemCollapsed =
|
const collapsedMap = getSidebarCollapsedMap();
|
||||||
hoveredItem != null ? getSidebarCollapsedMap()[hoveredItem.id] : false;
|
const isHoveredItemCollapsed = hoveredItem != null ? collapsedMap[hoveredItem.id] : false;
|
||||||
|
|
||||||
if (hoveredItem?.model === 'folder' && side === 'below' && !isHoveredItemCollapsed) {
|
if (hoveredItem?.model === 'folder' && side === 'below' && !isHoveredItemCollapsed) {
|
||||||
// Move into the folder if it's open and we're moving below it
|
// Move into the folder if it's open and we're moving below it
|
||||||
hoveredTree = hoveredTree?.children.find((n) => n.id === id) ?? null;
|
hoveredTree = hoveredTree?.children.find((n) => n.id === id) ?? null;
|
||||||
@@ -298,6 +308,20 @@ export function Sidebar({ className }: Props) {
|
|||||||
|
|
||||||
const mainContextMenuItems = useCreateDropdownItems({ folderId: null });
|
const mainContextMenuItems = useCreateDropdownItems({ folderId: null });
|
||||||
|
|
||||||
|
const [, connectDrop] = useDrop<DragItem, void>(
|
||||||
|
{
|
||||||
|
accept: ItemTypes.REQUEST,
|
||||||
|
hover: (_, monitor) => {
|
||||||
|
if (sidebarRef.current == null) return;
|
||||||
|
if (!monitor.isOver({ shallow: true })) return;
|
||||||
|
handleMoveToSidebarEnd();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[handleMoveToSidebarEnd],
|
||||||
|
);
|
||||||
|
|
||||||
|
connectDrop(sidebarRef);
|
||||||
|
|
||||||
// Not ready to render yet
|
// Not ready to render yet
|
||||||
if (tree == null) {
|
if (tree == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -20,15 +20,13 @@ import { HttpMethodTag } from '../core/HttpMethodTag';
|
|||||||
import { HttpStatusTag } from '../core/HttpStatusTag';
|
import { HttpStatusTag } from '../core/HttpStatusTag';
|
||||||
import { Icon } from '../core/Icon';
|
import { Icon } from '../core/Icon';
|
||||||
import { LoadingIcon } from '../core/LoadingIcon';
|
import { LoadingIcon } from '../core/LoadingIcon';
|
||||||
|
import type { DragItem} from './dnd';
|
||||||
|
import { ItemTypes } from './dnd';
|
||||||
import type { SidebarTreeNode } from './Sidebar';
|
import type { SidebarTreeNode } from './Sidebar';
|
||||||
import { sidebarSelectedIdAtom } from './SidebarAtoms';
|
import { sidebarSelectedIdAtom } from './SidebarAtoms';
|
||||||
import { SidebarItemContextMenu } from './SidebarItemContextMenu';
|
import { SidebarItemContextMenu } from './SidebarItemContextMenu';
|
||||||
import type { SidebarItemsProps } from './SidebarItems';
|
import type { SidebarItemsProps } from './SidebarItems';
|
||||||
|
|
||||||
enum ItemTypes {
|
|
||||||
REQUEST = 'request',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SidebarItemProps = {
|
export type SidebarItemProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
itemId: string;
|
itemId: string;
|
||||||
@@ -44,11 +42,6 @@ export type SidebarItemProps = {
|
|||||||
latestWebsocketConnection: WebsocketConnection | null;
|
latestWebsocketConnection: WebsocketConnection | null;
|
||||||
} & Pick<SidebarItemsProps, 'onSelect'>;
|
} & Pick<SidebarItemsProps, 'onSelect'>;
|
||||||
|
|
||||||
type DragItem = {
|
|
||||||
id: string;
|
|
||||||
itemName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SidebarItem = memo(function SidebarItem({
|
export const SidebarItem = memo(function SidebarItem({
|
||||||
itemName,
|
itemName,
|
||||||
itemId,
|
itemId,
|
||||||
@@ -69,9 +62,10 @@ export const SidebarItem = memo(function SidebarItem({
|
|||||||
|
|
||||||
const [, connectDrop] = useDrop<DragItem, void>(
|
const [, connectDrop] = useDrop<DragItem, void>(
|
||||||
{
|
{
|
||||||
accept: ItemTypes.REQUEST,
|
accept: [ItemTypes.REQUEST, ItemTypes.SIDEBAR],
|
||||||
hover: (_, monitor) => {
|
hover: (_, monitor) => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
|
if (!monitor.isOver()) return;
|
||||||
const hoverBoundingRect = ref.current?.getBoundingClientRect();
|
const hoverBoundingRect = ref.current?.getBoundingClientRect();
|
||||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||||
const clientOffset = monitor.getClientOffset();
|
const clientOffset = monitor.getClientOffset();
|
||||||
|
|||||||
9
src-web/components/sidebar/dnd.ts
Normal file
9
src-web/components/sidebar/dnd.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export enum ItemTypes {
|
||||||
|
REQUEST = 'request',
|
||||||
|
SIDEBAR = 'sidebar',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DragItem = {
|
||||||
|
id: string;
|
||||||
|
itemName: string;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user