mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-28 03:37:02 +02:00
Add back creation items to context menu
This commit is contained in:
@@ -25,8 +25,9 @@ import { activeCookieJarAtom } from '../hooks/useActiveCookieJar';
|
|||||||
import { activeEnvironmentAtom } from '../hooks/useActiveEnvironment';
|
import { activeEnvironmentAtom } from '../hooks/useActiveEnvironment';
|
||||||
import { activeFolderIdAtom } from '../hooks/useActiveFolderId';
|
import { activeFolderIdAtom } from '../hooks/useActiveFolderId';
|
||||||
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||||
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom, activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||||
|
import { getCreateDropdownItems } from '../hooks/useCreateDropdownItems';
|
||||||
import { getGrpcRequestActions } from '../hooks/useGrpcRequestActions';
|
import { getGrpcRequestActions } from '../hooks/useGrpcRequestActions';
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
import { getHttpRequestActions } from '../hooks/useHttpRequestActions';
|
import { getHttpRequestActions } from '../hooks/useHttpRequestActions';
|
||||||
@@ -52,80 +53,9 @@ import { Tree } from './core/tree/Tree';
|
|||||||
import type { TreeItemProps } from './core/tree/TreeItem';
|
import type { TreeItemProps } from './core/tree/TreeItem';
|
||||||
import { GitDropdown } from './GitDropdown';
|
import { GitDropdown } from './GitDropdown';
|
||||||
|
|
||||||
type Model = Workspace | Folder | HttpRequest | GrpcRequest | WebsocketRequest;
|
type SidebarModel = Workspace | Folder | HttpRequest | GrpcRequest | WebsocketRequest;
|
||||||
|
|
||||||
const opacitySubtle = 'opacity-80';
|
const OPACITY_SUBTLE = 'opacity-80';
|
||||||
|
|
||||||
function getItemKey(item: Model) {
|
|
||||||
const responses = jotaiStore.get(httpResponsesAtom);
|
|
||||||
const latestResponse = responses.find((r) => r.requestId === item.id) ?? null;
|
|
||||||
const url = 'url' in item ? item.url : 'n/a';
|
|
||||||
const method = 'method' in item ? item.method : 'n/a';
|
|
||||||
return [
|
|
||||||
item.id,
|
|
||||||
item.name,
|
|
||||||
url,
|
|
||||||
method,
|
|
||||||
latestResponse?.elapsed,
|
|
||||||
latestResponse?.id ?? 'n/a',
|
|
||||||
].join('::');
|
|
||||||
}
|
|
||||||
|
|
||||||
const SidebarLeftSlot = memo(function SidebarLeftSlot({
|
|
||||||
treeId,
|
|
||||||
item,
|
|
||||||
}: {
|
|
||||||
treeId: string;
|
|
||||||
item: Model;
|
|
||||||
}) {
|
|
||||||
if (item.model === 'folder') {
|
|
||||||
return <Icon icon="folder" />;
|
|
||||||
} else if (item.model === 'workspace') {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
const isSelected = jotaiStore.get(isSelectedFamily({ treeId, itemId: item.id }));
|
|
||||||
return (
|
|
||||||
<HttpMethodTag
|
|
||||||
short
|
|
||||||
className={classNames('text-xs', !isSelected && opacitySubtle)}
|
|
||||||
request={item}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const SidebarInnerItem = memo(function SidebarInnerItem({ item }: { treeId: string; item: Model }) {
|
|
||||||
const response = useAtomValue(
|
|
||||||
useMemo(
|
|
||||||
() =>
|
|
||||||
selectAtom(
|
|
||||||
atom((get) => [
|
|
||||||
...get(grpcConnectionsAtom),
|
|
||||||
...get(httpResponsesAtom),
|
|
||||||
...get(websocketConnectionsAtom),
|
|
||||||
]),
|
|
||||||
(responses) => responses.find((r) => r.requestId === item.id),
|
|
||||||
(a, b) => a?.state === b?.state && a?.id === b?.id, // Only update when the response state changes updated
|
|
||||||
),
|
|
||||||
[item.id],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2 min-w-0 h-full w-full text-left">
|
|
||||||
<div className="truncate">{resolvedModelName(item)}</div>
|
|
||||||
{response != null && (
|
|
||||||
<div className="ml-auto">
|
|
||||||
{response.state !== 'closed' ? (
|
|
||||||
<LoadingIcon size="sm" className="text-text-subtlest" />
|
|
||||||
) : response.model === 'http_response' ? (
|
|
||||||
<HttpStatusTag short className="text-xs" response={response} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function NewSidebar({ className }: { className?: string }) {
|
function NewSidebar({ className }: { className?: string }) {
|
||||||
const [hidden, setHidden] = useSidebarHidden();
|
const [hidden, setHidden] = useSidebarHidden();
|
||||||
@@ -161,13 +91,13 @@ function NewSidebar({ className }: { className?: string }) {
|
|||||||
children,
|
children,
|
||||||
insertAt,
|
insertAt,
|
||||||
}: {
|
}: {
|
||||||
items: Model[];
|
items: SidebarModel[];
|
||||||
parent: Model;
|
parent: SidebarModel;
|
||||||
children: Model[];
|
children: SidebarModel[];
|
||||||
insertAt: number;
|
insertAt: number;
|
||||||
}) {
|
}) {
|
||||||
const prev = children[insertAt - 1] as Exclude<Model, Workspace>;
|
const prev = children[insertAt - 1] as Exclude<SidebarModel, Workspace>;
|
||||||
const next = children[insertAt] as Exclude<Model, Workspace>;
|
const next = children[insertAt] as Exclude<SidebarModel, Workspace>;
|
||||||
const folderId = parent.model === 'folder' ? parent.id : null;
|
const folderId = parent.model === 'folder' ? parent.id : null;
|
||||||
|
|
||||||
const beforePriority = prev?.sortPriority ?? 0;
|
const beforePriority = prev?.sortPriority ?? 0;
|
||||||
@@ -248,8 +178,8 @@ const activeIdAtom = atom<string | null>((get) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function getEditOptions(
|
function getEditOptions(
|
||||||
item: Model,
|
item: SidebarModel,
|
||||||
): ReturnType<NonNullable<TreeItemProps<Model>['getEditOptions']>> {
|
): ReturnType<NonNullable<TreeItemProps<SidebarModel>['getEditOptions']>> {
|
||||||
return {
|
return {
|
||||||
onChange: handleSubmitEdit,
|
onChange: handleSubmitEdit,
|
||||||
defaultValue: resolvedModelName(item),
|
defaultValue: resolvedModelName(item),
|
||||||
@@ -257,18 +187,18 @@ function getEditOptions(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmitEdit(item: Model, text: string) {
|
async function handleSubmitEdit(item: SidebarModel, text: string) {
|
||||||
await patchModel(item, { name: text });
|
await patchModel(item, { name: text });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleActivate(item: Model) {
|
function handleActivate(item: SidebarModel) {
|
||||||
// TODO: Add folder layout support
|
// TODO: Add folder layout support
|
||||||
if (item.model !== 'folder' && item.model !== 'workspace') {
|
if (item.model !== 'folder' && item.model !== 'workspace') {
|
||||||
navigateToRequestOrFolderOrWorkspace(item.id, item.model);
|
navigateToRequestOrFolderOrWorkspace(item.id, item.model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allPotentialChildrenAtom = atom<Model[]>((get) => {
|
const allPotentialChildrenAtom = atom<SidebarModel[]>((get) => {
|
||||||
const requests = get(allRequestsAtom);
|
const requests = get(allRequestsAtom);
|
||||||
const folders = get(foldersAtom);
|
const folders = get(foldersAtom);
|
||||||
return [...requests, ...folders];
|
return [...requests, ...folders];
|
||||||
@@ -280,7 +210,7 @@ const sidebarTreeAtom = atom((get) => {
|
|||||||
const allModels = get(memoAllPotentialChildrenAtom);
|
const allModels = get(memoAllPotentialChildrenAtom);
|
||||||
const activeWorkspace = get(activeWorkspaceAtom);
|
const activeWorkspace = get(activeWorkspaceAtom);
|
||||||
|
|
||||||
const childrenMap: Record<string, Exclude<Model, Workspace>[]> = {};
|
const childrenMap: Record<string, Exclude<SidebarModel, Workspace>[]> = {};
|
||||||
for (const item of allModels) {
|
for (const item of allModels) {
|
||||||
if ('folderId' in item && item.folderId == null) {
|
if ('folderId' in item && item.folderId == null) {
|
||||||
childrenMap[item.workspaceId] = childrenMap[item.workspaceId] ?? [];
|
childrenMap[item.workspaceId] = childrenMap[item.workspaceId] ?? [];
|
||||||
@@ -291,14 +221,14 @@ const sidebarTreeAtom = atom((get) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const treeParentMap: Record<string, TreeNode<Model>> = {};
|
const treeParentMap: Record<string, TreeNode<SidebarModel>> = {};
|
||||||
|
|
||||||
if (activeWorkspace == null) {
|
if (activeWorkspace == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put requests and folders into a tree structure
|
// Put requests and folders into a tree structure
|
||||||
const next = (node: TreeNode<Model>, depth: number): TreeNode<Model> => {
|
const next = (node: TreeNode<SidebarModel>, depth: number): TreeNode<SidebarModel> => {
|
||||||
const childItems = childrenMap[node.item.id] ?? [];
|
const childItems = childrenMap[node.item.id] ?? [];
|
||||||
|
|
||||||
// Recurse to children
|
// Recurse to children
|
||||||
@@ -326,10 +256,10 @@ const sidebarTreeAtom = atom((get) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
'sidebar.delete_selected_item': async function (items: Model[]) {
|
'sidebar.delete_selected_item': async function (items: SidebarModel[]) {
|
||||||
await deleteModelWithConfirm(items);
|
await deleteModelWithConfirm(items);
|
||||||
},
|
},
|
||||||
'model.duplicate': async function (items: Model[]) {
|
'model.duplicate': async function (items: SidebarModel[]) {
|
||||||
if (items.length === 1) {
|
if (items.length === 1) {
|
||||||
const item = items[0]!;
|
const item = items[0]!;
|
||||||
const newId = await duplicateModel(item);
|
const newId = await duplicateModel(item);
|
||||||
@@ -338,22 +268,29 @@ const actions = {
|
|||||||
await Promise.all(items.map(duplicateModel));
|
await Promise.all(items.map(duplicateModel));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'request.send': async function (items: Model[]) {
|
'request.send': async function (items: SidebarModel[]) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
items.filter((i) => i.model === 'http_request').map((i) => sendAnyHttpRequest.mutate(i.id)),
|
items.filter((i) => i.model === 'http_request').map((i) => sendAnyHttpRequest.mutate(i.id)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const hotkeys: TreeProps<Model>['hotkeys'] = {
|
const hotkeys: TreeProps<SidebarModel>['hotkeys'] = {
|
||||||
priority: 10, // So these ones take precedence over global hotkeys when the sidebar is focused
|
priority: 10, // So these ones take precedence over global hotkeys when the sidebar is focused
|
||||||
actions,
|
actions,
|
||||||
enable: () => isSidebarFocused(),
|
enable: () => isSidebarFocused(),
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getContextMenu(items: Model[]): Promise<DropdownItem[]> {
|
async function getContextMenu(items: SidebarModel[]): Promise<DropdownItem[]> {
|
||||||
|
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||||
const child = items[0];
|
const child = items[0];
|
||||||
if (child == null) return [];
|
|
||||||
|
// No children means we're in the root
|
||||||
|
if (child == null) {
|
||||||
|
console.log('HELLO', child);
|
||||||
|
return getCreateDropdownItems({ workspaceId, activeRequest: null, folderId: null });
|
||||||
|
}
|
||||||
|
|
||||||
const workspaces = jotaiStore.get(workspacesAtom);
|
const workspaces = jotaiStore.get(workspacesAtom);
|
||||||
const onlyHttpRequests = items.every((i) => i.model === 'http_request');
|
const onlyHttpRequests = items.every((i) => i.model === 'http_request');
|
||||||
|
|
||||||
@@ -411,7 +348,13 @@ async function getContextMenu(items: Model[]): Promise<DropdownItem[]> {
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
|
const modelCreationItems: DropdownItem[] =
|
||||||
|
items.length === 1 && child.model === 'folder'
|
||||||
|
? [
|
||||||
|
{ type: 'separator' },
|
||||||
|
...getCreateDropdownItems({ workspaceId, activeRequest: null, folderId: child.id }),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
const menuItems: ContextMenuProps['items'] = [
|
const menuItems: ContextMenuProps['items'] = [
|
||||||
...initialItems,
|
...initialItems,
|
||||||
{ type: 'separator', hidden: initialItems.filter((v) => !v.hidden).length === 0 },
|
{ type: 'separator', hidden: initialItems.filter((v) => !v.hidden).length === 0 },
|
||||||
@@ -455,6 +398,83 @@ async function getContextMenu(items: Model[]): Promise<DropdownItem[]> {
|
|||||||
leftSlot: <Icon icon="trash" />,
|
leftSlot: <Icon icon="trash" />,
|
||||||
onSelect: () => actions['sidebar.delete_selected_item'](items),
|
onSelect: () => actions['sidebar.delete_selected_item'](items),
|
||||||
},
|
},
|
||||||
|
...modelCreationItems,
|
||||||
];
|
];
|
||||||
return menuItems;
|
return menuItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getItemKey(item: SidebarModel) {
|
||||||
|
const responses = jotaiStore.get(httpResponsesAtom);
|
||||||
|
const latestResponse = responses.find((r) => r.requestId === item.id) ?? null;
|
||||||
|
const url = 'url' in item ? item.url : 'n/a';
|
||||||
|
const method = 'method' in item ? item.method : 'n/a';
|
||||||
|
return [
|
||||||
|
item.id,
|
||||||
|
item.name,
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
latestResponse?.elapsed,
|
||||||
|
latestResponse?.id ?? 'n/a',
|
||||||
|
].join('::');
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarLeftSlot = memo(function SidebarLeftSlot({
|
||||||
|
treeId,
|
||||||
|
item,
|
||||||
|
}: {
|
||||||
|
treeId: string;
|
||||||
|
item: SidebarModel;
|
||||||
|
}) {
|
||||||
|
if (item.model === 'folder') {
|
||||||
|
return <Icon icon="folder" />;
|
||||||
|
} else if (item.model === 'workspace') {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
const isSelected = jotaiStore.get(isSelectedFamily({ treeId, itemId: item.id }));
|
||||||
|
return (
|
||||||
|
<HttpMethodTag
|
||||||
|
short
|
||||||
|
className={classNames('text-xs', !isSelected && OPACITY_SUBTLE)}
|
||||||
|
request={item}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const SidebarInnerItem = memo(function SidebarInnerItem({
|
||||||
|
item,
|
||||||
|
}: {
|
||||||
|
treeId: string;
|
||||||
|
item: SidebarModel;
|
||||||
|
}) {
|
||||||
|
const response = useAtomValue(
|
||||||
|
useMemo(
|
||||||
|
() =>
|
||||||
|
selectAtom(
|
||||||
|
atom((get) => [
|
||||||
|
...get(grpcConnectionsAtom),
|
||||||
|
...get(httpResponsesAtom),
|
||||||
|
...get(websocketConnectionsAtom),
|
||||||
|
]),
|
||||||
|
(responses) => responses.find((r) => r.requestId === item.id),
|
||||||
|
(a, b) => a?.state === b?.state && a?.id === b?.id, // Only update when the response state changes updated
|
||||||
|
),
|
||||||
|
[item.id],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2 min-w-0 h-full w-full text-left">
|
||||||
|
<div className="truncate">{resolvedModelName(item)}</div>
|
||||||
|
{response != null && (
|
||||||
|
<div className="ml-auto">
|
||||||
|
{response.state !== 'closed' ? (
|
||||||
|
<LoadingIcon size="sm" className="text-text-subtlest" />
|
||||||
|
) : response.model === 'http_response' ? (
|
||||||
|
<HttpStatusTag short className="text-xs" response={response} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -9,14 +9,23 @@ import {
|
|||||||
} from '@dnd-kit/core';
|
} from '@dnd-kit/core';
|
||||||
import { type } from '@tauri-apps/plugin-os';
|
import { type } from '@tauri-apps/plugin-os';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ComponentType, ReactElement, Ref, RefAttributes } from 'react';
|
import type { ComponentType, MouseEvent, ReactElement, Ref, RefAttributes } from 'react';
|
||||||
import { forwardRef, memo, useCallback, useImperativeHandle, useMemo, useRef } from 'react';
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useKey, useKeyPressEvent } from 'react-use';
|
import { useKey, useKeyPressEvent } from 'react-use';
|
||||||
import type { HotkeyAction, HotKeyOptions } from '../../../hooks/useHotKey';
|
import type { HotkeyAction, HotKeyOptions } from '../../../hooks/useHotKey';
|
||||||
import { useHotKey } from '../../../hooks/useHotKey';
|
import { useHotKey } from '../../../hooks/useHotKey';
|
||||||
import { computeSideForDragMove } from '../../../lib/dnd';
|
import { computeSideForDragMove } from '../../../lib/dnd';
|
||||||
import { jotaiStore } from '../../../lib/jotai';
|
import { jotaiStore } from '../../../lib/jotai';
|
||||||
import type { ContextMenuProps } from '../Dropdown';
|
import type { ContextMenuProps, DropdownItem } from '../Dropdown';
|
||||||
|
import { ContextMenu } from '../Dropdown';
|
||||||
import {
|
import {
|
||||||
collapsedFamily,
|
collapsedFamily,
|
||||||
draggingIdsFamily,
|
draggingIdsFamily,
|
||||||
@@ -73,6 +82,15 @@ function TreeInner<T extends { id: string }>(
|
|||||||
) {
|
) {
|
||||||
const treeRef = useRef<HTMLDivElement>(null);
|
const treeRef = useRef<HTMLDivElement>(null);
|
||||||
const selectableItems = useSelectableItems(root);
|
const selectableItems = useSelectableItems(root);
|
||||||
|
const [showContextMenu, setShowContextMenu] = useState<{
|
||||||
|
items: DropdownItem[];
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleCloseContextMenu = useCallback(() => {
|
||||||
|
setShowContextMenu(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const tryFocus = useCallback(() => {
|
const tryFocus = useCallback(() => {
|
||||||
treeRef.current?.querySelector<HTMLButtonElement>('.tree-item button[tabindex="0"]')?.focus();
|
treeRef.current?.querySelector<HTMLButtonElement>('.tree-item button[tabindex="0"]')?.focus();
|
||||||
@@ -403,11 +421,30 @@ function TreeInner<T extends { id: string }>(
|
|||||||
ItemLeftSlot,
|
ItemLeftSlot,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleContextMenu = useCallback(
|
||||||
|
async (e: MouseEvent<HTMLElement>) => {
|
||||||
|
if (getContextMenu == null) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const items = await getContextMenu([]);
|
||||||
|
setShowContextMenu({ items, x: e.clientX, y: e.clientY });
|
||||||
|
},
|
||||||
|
[getContextMenu],
|
||||||
|
);
|
||||||
|
|
||||||
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }));
|
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TreeHotKeys treeId={treeId} hotkeys={hotkeys} selectableItems={selectableItems} />
|
<TreeHotKeys treeId={treeId} hotkeys={hotkeys} selectableItems={selectableItems} />
|
||||||
|
{showContextMenu && (
|
||||||
|
<ContextMenu
|
||||||
|
items={showContextMenu.items}
|
||||||
|
triggerPosition={showContextMenu}
|
||||||
|
onClose={handleCloseContextMenu}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<DndContext
|
<DndContext
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
collisionDetection={pointerWithin}
|
collisionDetection={pointerWithin}
|
||||||
@@ -429,8 +466,6 @@ function TreeInner<T extends { id: string }>(
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'[&_.tree-item-inner]:bg-surface',
|
|
||||||
'[&_.tree-item-selectable.selected]:text-text',
|
|
||||||
'[&:focus-within]:[&_.tree-item.selected]:bg-surface-active',
|
'[&:focus-within]:[&_.tree-item.selected]:bg-surface-active',
|
||||||
'[&:not(:focus-within)]:[&_.tree-item.selected]:bg-surface-highlight',
|
'[&:not(:focus-within)]:[&_.tree-item.selected]:bg-surface-highlight',
|
||||||
|
|
||||||
@@ -446,7 +481,7 @@ function TreeInner<T extends { id: string }>(
|
|||||||
<TreeItemList nodes={selectableItems} treeId={treeId} {...treeItemListProps} />
|
<TreeItemList nodes={selectableItems} treeId={treeId} {...treeItemListProps} />
|
||||||
</div>
|
</div>
|
||||||
{/* Assign root ID so we can reuse our same move/end logic */}
|
{/* Assign root ID so we can reuse our same move/end logic */}
|
||||||
<DropRegionAfterList id={root.item.id} />
|
<DropRegionAfterList id={root.item.id} onContextMenu={handleContextMenu} />
|
||||||
</div>
|
</div>
|
||||||
<TreeDragOverlay
|
<TreeDragOverlay
|
||||||
treeId={treeId}
|
treeId={treeId}
|
||||||
@@ -476,9 +511,15 @@ export const Tree = memo(
|
|||||||
},
|
},
|
||||||
) as typeof Tree_;
|
) as typeof Tree_;
|
||||||
|
|
||||||
function DropRegionAfterList({ id }: { id: string }) {
|
function DropRegionAfterList({
|
||||||
|
id,
|
||||||
|
onContextMenu,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
onContextMenu?: (e: MouseEvent<HTMLDivElement>) => void;
|
||||||
|
}) {
|
||||||
const { setNodeRef } = useDroppable({ id });
|
const { setNodeRef } = useDroppable({ id });
|
||||||
return <div ref={setNodeRef} />;
|
return <div ref={setNodeRef} onContextMenu={onContextMenu} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TreeHotKeyProps<T extends { id: string }> extends HotKeyOptions {
|
interface TreeHotKeyProps<T extends { id: string }> extends HotKeyOptions {
|
||||||
|
|||||||
@@ -242,7 +242,6 @@ function TreeItem_<T extends { id: string }>({
|
|||||||
<TreeIndentGuide treeId={treeId} depth={depth} />
|
<TreeIndentGuide treeId={treeId} depth={depth} />
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'tree-item-selectable',
|
|
||||||
'text-text-subtle',
|
'text-text-subtle',
|
||||||
'grid grid-cols-[auto_minmax(0,1fr)] items-center rounded-md',
|
'grid grid-cols-[auto_minmax(0,1fr)] items-center rounded-md',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||||
|
import type { GrpcRequest } from '@yaakapp-internal/sync';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { createFolder } from '../commands/commands';
|
import { createFolder } from '../commands/commands';
|
||||||
@@ -5,7 +7,6 @@ import type { DropdownItem } from '../components/core/Dropdown';
|
|||||||
import { Icon } from '../components/core/Icon';
|
import { Icon } from '../components/core/Icon';
|
||||||
import { createRequestAndNavigate } from '../lib/createRequestAndNavigate';
|
import { createRequestAndNavigate } from '../lib/createRequestAndNavigate';
|
||||||
import { generateId } from '../lib/generateId';
|
import { generateId } from '../lib/generateId';
|
||||||
import { jotaiStore } from '../lib/jotai';
|
|
||||||
import { BODY_TYPE_GRAPHQL } from '../lib/model_util';
|
import { BODY_TYPE_GRAPHQL } from '../lib/model_util';
|
||||||
import { activeRequestAtom } from './useActiveRequest';
|
import { activeRequestAtom } from './useActiveRequest';
|
||||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||||
@@ -13,16 +14,35 @@ import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
|||||||
export function useCreateDropdownItems({
|
export function useCreateDropdownItems({
|
||||||
hideFolder,
|
hideFolder,
|
||||||
hideIcons,
|
hideIcons,
|
||||||
folderId: folderIdOption,
|
folderId,
|
||||||
}: {
|
}: {
|
||||||
hideFolder?: boolean;
|
hideFolder?: boolean;
|
||||||
hideIcons?: boolean;
|
hideIcons?: boolean;
|
||||||
folderId?: string | null | 'active-folder';
|
folderId?: string | null | 'active-folder';
|
||||||
} = {}): DropdownItem[] {
|
} = {}): DropdownItem[] {
|
||||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||||
|
const activeRequest = useAtomValue(activeRequestAtom);
|
||||||
|
|
||||||
const items = useMemo((): DropdownItem[] => {
|
const items = useMemo((): DropdownItem[] => {
|
||||||
const activeRequest = jotaiStore.get(activeRequestAtom);
|
return getCreateDropdownItems({ hideFolder, hideIcons, folderId, activeRequest, workspaceId });
|
||||||
|
}, [activeRequest, folderId, hideFolder, hideIcons, workspaceId]);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCreateDropdownItems({
|
||||||
|
hideFolder,
|
||||||
|
hideIcons,
|
||||||
|
folderId: folderIdOption,
|
||||||
|
workspaceId,
|
||||||
|
activeRequest,
|
||||||
|
}: {
|
||||||
|
hideFolder?: boolean;
|
||||||
|
hideIcons?: boolean;
|
||||||
|
folderId?: string | null | 'active-folder';
|
||||||
|
workspaceId: string | null;
|
||||||
|
activeRequest: HttpRequest | GrpcRequest | WebsocketRequest | null;
|
||||||
|
}): DropdownItem[] {
|
||||||
const folderId =
|
const folderId =
|
||||||
(folderIdOption === 'active-folder' ? activeRequest?.folderId : folderIdOption) ?? null;
|
(folderIdOption === 'active-folder' ? activeRequest?.folderId : folderIdOption) ?? null;
|
||||||
if (workspaceId == null) return [];
|
if (workspaceId == null) return [];
|
||||||
@@ -68,7 +88,4 @@ export function useCreateDropdownItems({
|
|||||||
},
|
},
|
||||||
]) as DropdownItem[]),
|
]) as DropdownItem[]),
|
||||||
];
|
];
|
||||||
}, [folderIdOption, hideFolder, hideIcons, workspaceId]);
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user