Context menu, logs in DevTools, export, tweaks

This commit is contained in:
Gregory Schier
2023-11-09 09:28:01 -08:00
parent aeda72f13e
commit 9ebb3ef532
20 changed files with 593 additions and 157 deletions

View File

@@ -4,6 +4,8 @@ import React, { forwardRef, Fragment, useCallback, useMemo, useRef, useState } f
import type { XYCoord } from 'react-dnd';
import { useDrag, useDrop } from 'react-dnd';
import { useKey, useKeyPressEvent } from 'react-use';
import { showMenu } from 'tauri-plugin-context-menu';
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
import { useActiveRequestId } from '../hooks/useActiveRequestId';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
@@ -12,13 +14,15 @@ import { useCreateFolder } from '../hooks/useCreateFolder';
import { useCreateRequest } from '../hooks/useCreateRequest';
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
import { useDeleteFolder } from '../hooks/useDeleteFolder';
import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useFolders } from '../hooks/useFolders';
import { useKeyValue } from '../hooks/useKeyValue';
import { useLatestResponse } from '../hooks/useLatestResponse';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { usePrompt } from '../hooks/usePrompt';
import { useRequests } from '../hooks/useRequests';
import { useSendAnyRequest } from '../hooks/useSendAnyRequest';
import { useSendManyRequests } from '../hooks/useSendFolder';
import { useSendRequest } from '../hooks/useSendRequest';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
import { useUpdateAnyRequest } from '../hooks/useUpdateAnyRequest';
@@ -489,10 +493,12 @@ const SidebarItem = forwardRef(function SidebarItem(
}: SidebarItemProps,
ref: ForwardedRef<HTMLLIElement>,
) {
const sendAnyRequest = useSendAnyRequest();
const createRequest = useCreateRequest();
const createFolder = useCreateFolder();
const deleteRequest = useDeleteFolder(itemId);
const deleteFolder = useDeleteFolder(itemId);
const sendRequest = useSendRequest(itemId);
const sendManyRequests = useSendManyRequests();
const deleteRequest = useDeleteRequest(itemId);
const latestResponse = useLatestResponse(itemId);
const updateRequest = useUpdateRequest(itemId);
const updateAnyFolder = useUpdateAnyFolder();
@@ -543,9 +549,42 @@ const SidebarItem = forwardRef(function SidebarItem(
[handleSubmitNameEdit],
);
const handleSelect = useCallback(() => {
onSelect(itemId);
}, [onSelect, itemId]);
const handleContextMenu = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
showMenu({
pos: { x: e.clientX, y: e.clientY },
items:
itemModel === 'http_request'
? [
{
label: 'Send Request',
event: () => sendRequest.mutate(),
},
{
label: 'Delete Request',
event: () => deleteRequest.mutate(),
},
]
: [
{
label: 'Send All',
event: () => sendManyRequests.mutate(child.children.map((c) => c.item.id)),
},
{
label: 'Delete Folder',
event: () => deleteFolder.mutate(),
},
],
})
.then((r) => console.log(r))
.catch((e) => console.log(e));
},
[itemModel, sendRequest, deleteRequest, sendManyRequests, child.children],
);
const handleSelect = useCallback(() => onSelect(itemId), [onSelect, itemId]);
return (
<li ref={ref}>
@@ -557,13 +596,7 @@ const SidebarItem = forwardRef(function SidebarItem(
key: 'sendAll',
label: 'Send All',
leftSlot: <Icon icon="paperPlane" />,
onSelect: () => {
for (const { item } of child.children) {
if (item.model === 'http_request') {
sendAnyRequest.mutate(item.id);
}
}
},
onSelect: () => sendManyRequests.mutate(child.children.map((c) => c.item.id)),
},
{ type: 'separator', label: itemName },
{
@@ -590,7 +623,7 @@ const SidebarItem = forwardRef(function SidebarItem(
label: 'Delete',
variant: 'danger',
leftSlot: <Icon icon="trash" />,
onSelect: () => deleteRequest.mutate(),
onSelect: () => deleteFolder.mutate(),
},
{ type: 'separator' },
{
@@ -617,9 +650,10 @@ const SidebarItem = forwardRef(function SidebarItem(
)}
<button
// tabIndex={-1} // Will prevent drag-n-drop
onClick={handleSelect}
disabled={editing}
onClick={handleSelect}
onDoubleClick={handleStartEditing}
onContextMenu={handleContextMenu}
data-active={isActive}
data-selected={selected}
className={classNames(

View File

@@ -3,6 +3,7 @@ import { useCreateFolder } from '../hooks/useCreateFolder';
import { useCreateRequest } from '../hooks/useCreateRequest';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks';
@@ -26,12 +27,14 @@ export const SidebarActions = memo(function SidebarActions() {
items={[
{
key: 'create-request',
label: 'Create Request',
label: 'New Request',
leftSlot: <Icon icon="plus" />,
onSelect: () => createRequest.mutate({}),
},
{
key: 'create-folder',
label: 'Create Folder',
label: 'New Folder',
leftSlot: <Icon icon="plus" />,
onSelect: () => createFolder.mutate({}),
},
]}

View File

@@ -1,25 +1,24 @@
import { invoke } from '@tauri-apps/api';
import { open } from '@tauri-apps/api/dialog';
import classNames from 'classnames';
import { memo, useCallback, useMemo } from 'react';
import { memo, useMemo } from 'react';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
import { useExportData } from '../hooks/useExportData';
import { useImportData } from '../hooks/useImportData';
import { usePrompt } from '../hooks/usePrompt';
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
import { useTheme } from '../hooks/useTheme';
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
import { useWorkspaces } from '../hooks/useWorkspaces';
import type { Environment, Folder, HttpRequest, Workspace } from '../lib/models';
import { pluralize } from '../lib/pluralize';
import type { ButtonProps } from './core/Button';
import { Button } from './core/Button';
import type { DropdownItem } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { InlineCode } from './core/InlineCode';
import { HStack, VStack } from './core/Stacks';
import { HStack } from './core/Stacks';
import { useDialog } from './DialogContext';
type Props = Pick<ButtonProps, 'className' | 'justify' | 'forDropdown' | 'leftSlot'>;
@@ -34,72 +33,13 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
const importData = useImportData();
const exportData = useExportData();
const { appearance, toggleAppearance } = useTheme();
const dialog = useDialog();
const prompt = usePrompt();
const routes = useAppRoutes();
const importData = useCallback(async () => {
const selected = await open({
multiple: true,
filters: [
{
name: 'Export File',
extensions: ['json'],
},
],
});
if (selected == null || selected.length === 0) return;
const imported: {
workspaces: Workspace[];
environments: Environment[];
folders: Folder[];
requests: HttpRequest[];
} = await invoke('import_data', {
filePaths: selected,
});
const importedWorkspace = imported.workspaces[0];
dialog.show({
title: 'Import Complete',
size: 'dynamic',
hideX: true,
render: ({ hide }) => {
const { workspaces, environments, folders, requests } = imported;
return (
<VStack space={3}>
<ul className="list-disc pl-6">
<li>
{workspaces.length} {pluralize('Workspace', workspaces.length)}
</li>
<li>
{environments.length} {pluralize('Environment', environments.length)}
</li>
<li>
{folders.length} {pluralize('Folder', folders.length)}
</li>
<li>
{requests.length} {pluralize('Request', requests.length)}
</li>
</ul>
<div>
<Button className="ml-auto" onClick={hide} color="primary">
Done
</Button>
</div>
</VStack>
);
},
});
if (importedWorkspace != null) {
routes.navigate('workspace', {
workspaceId: importedWorkspace.id,
environmentId: imported.environments[0]?.id,
});
}
}, [routes, dialog]);
const items: DropdownItem[] = useMemo(() => {
const workspaceItems: DropdownItem[] = workspaces.map((w) => ({
key: w.id,
@@ -193,30 +133,36 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
{ type: 'separator' },
{
key: 'create-workspace',
label: 'Create Workspace',
label: 'New Workspace',
leftSlot: <Icon icon="plus" />,
onSelect: async () => {
const name = await prompt({
name: 'name',
label: 'Name',
defaultValue: 'My Workspace',
title: 'Create Workspace',
title: 'New Workspace',
});
createWorkspace.mutate({ name });
},
},
{
key: 'import',
label: 'Import Data',
onSelect: importData,
leftSlot: <Icon icon="download" />,
},
{
key: 'appearance',
label: 'Toggle Theme',
onSelect: toggleAppearance,
leftSlot: <Icon icon={appearance === 'dark' ? 'sun' : 'moon'} />,
},
{
key: 'export-data',
label: 'Export Data',
leftSlot: <Icon icon="upload" />,
onSelect: () => exportData.mutate(),
},
{
key: 'import-data',
label: 'Import Data',
leftSlot: <Icon icon="download" />,
onSelect: () => importData.mutate(),
},
];
}, [
activeWorkspace?.name,
@@ -225,6 +171,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
createWorkspace,
deleteWorkspace.mutate,
dialog,
exportData,
importData,
prompt,
routes,

View File

@@ -37,6 +37,7 @@ import {
TriangleLeftIcon,
TriangleRightIcon,
UpdateIcon,
UploadIcon,
} from '@radix-ui/react-icons';
import classNames from 'classnames';
import type { HTMLAttributes } from 'react';
@@ -84,6 +85,7 @@ const icons = {
triangleLeft: TriangleLeftIcon,
triangleRight: TriangleRightIcon,
update: UpdateIcon,
upload: UploadIcon,
x: Cross2Icon,
empty: (props: HTMLAttributes<HTMLSpanElement>) => <span {...props} />,
};