Custom HTTP method names

This commit is contained in:
Gregory Schier
2024-01-17 14:52:19 -08:00
parent 466d412e65
commit 321c3862fe
8 changed files with 128 additions and 77 deletions

View File

@@ -1,5 +1,9 @@
import { memo } from 'react'; import { memo, useMemo } from 'react';
import { usePrompt } from '../hooks/usePrompt';
import { Button } from './core/Button'; import { Button } from './core/Button';
import type { DropdownItem } from './core/Dropdown';
import { Icon } from './core/Icon';
import type { RadioDropdownItem } from './core/RadioDropdown';
import { RadioDropdown } from './core/RadioDropdown'; import { RadioDropdown } from './core/RadioDropdown';
type Props = { type Props = {
@@ -8,7 +12,15 @@ type Props = {
onChange: (method: string) => void; onChange: (method: string) => void;
}; };
const methodItems = ['GET', 'PUT', 'POST', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'].map((m) => ({ const radioItems: RadioDropdownItem<string>[] = [
'GET',
'PUT',
'POST',
'PATCH',
'DELETE',
'OPTIONS',
'HEAD',
].map((m) => ({
value: m, value: m,
label: m, label: m,
})); }));
@@ -18,8 +30,32 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({
onChange, onChange,
className, className,
}: Props) { }: Props) {
const prompt = usePrompt();
const extraItems = useMemo<DropdownItem[]>(
() => [
{ type: 'separator' },
{
key: 'custom',
label: 'CUSTOM',
leftSlot: <Icon icon="sparkles" />,
onSelect: async () => {
const newMethod = await prompt({
label: 'Http Method',
name: 'httpMethod',
defaultValue: '',
title: 'Custom Method',
description: 'Enter a custom method name',
placeholder: 'CUSTOM',
});
onChange(newMethod);
},
},
],
[onChange, prompt],
);
return ( return (
<RadioDropdown value={method} items={methodItems} onChange={onChange}> <RadioDropdown value={method} items={radioItems} extraItems={extraItems} onChange={onChange}>
<Button size="xs" className={className}> <Button size="xs" className={className}>
{method.toUpperCase()} {method.toUpperCase()}
</Button> </Button>

View File

@@ -21,7 +21,7 @@ export const SettingsDialog = () => {
return ( return (
<VStack space={2}> <VStack space={2}>
<HStack alignItems="center" space={2}> <HStack className="mt-1" alignItems="center" space={2}>
<div className="w-1/3">Appearance</div> <div className="w-1/3">Appearance</div>
<select <select
value={settings.appearance} value={settings.appearance}
@@ -42,11 +42,11 @@ export const SettingsDialog = () => {
<Heading size={2}> <Heading size={2}>
Workspace{' '} Workspace{' '}
<div className="inline-block ml-1 bg-gray-500 dark:bg-gray-300 px-2 py-0.5 !text-md rounded text-base text-white dark:text-gray-900"> <div className="inline-block ml-1 bg-gray-500 dark:bg-gray-300 px-2 py-0.5 text-sm rounded text-white dark:text-gray-900">
{workspace.name} {workspace.name}
</div> </div>
</Heading> </Heading>
<VStack className="w-full" space={3}> <VStack className="mt-1 w-full" space={3}>
<Input <Input
size="xs" size="xs"
name="requestTimeout" name="requestTimeout"

View File

@@ -656,12 +656,6 @@ const SidebarItem = forwardRef(function SidebarItem(
leftSlot: <Icon icon="sendHorizontal" />, leftSlot: <Icon icon="sendHorizontal" />,
onSelect: () => sendRequest.mutate(), onSelect: () => sendRequest.mutate(),
}, },
{
key: 'sendAndDownloadRequest',
label: 'Send and Download',
leftSlot: <Icon icon="download" />,
onSelect: () => sendAndDownloadRequest.mutate(),
},
{ type: 'separator' }, { type: 'separator' },
{ {
key: 'duplicateRequest', key: 'duplicateRequest',

View File

@@ -1,63 +1,64 @@
import * as lucide from 'lucide-react'; import * as lucide from 'lucide-react';
import classNames from 'classnames'; import classNames from 'classnames';
import type {HTMLAttributes} from 'react'; import type { HTMLAttributes } from 'react';
import {memo} from 'react'; import { memo } from 'react';
const icons = { const icons = {
archive: lucide.ArchiveIcon, archive: lucide.ArchiveIcon,
box: lucide.BoxIcon, box: lucide.BoxIcon,
chat: lucide.MessageSquare, chat: lucide.MessageSquare,
check: lucide.CheckIcon, check: lucide.CheckIcon,
chevronDown: lucide.ChevronDownIcon, chevronDown: lucide.ChevronDownIcon,
chevronRight: lucide.ChevronRightIcon, chevronRight: lucide.ChevronRightIcon,
code: lucide.CodeIcon, code: lucide.CodeIcon,
copy: lucide.CopyIcon, copy: lucide.CopyIcon,
download: lucide.DownloadIcon, download: lucide.DownloadIcon,
externalLink: lucide.ExternalLinkIcon, externalLink: lucide.ExternalLinkIcon,
eye: lucide.EyeIcon, eye: lucide.EyeIcon,
eyeClosed: lucide.EyeOffIcon, eyeClosed: lucide.EyeOffIcon,
filter: lucide.FilterIcon, filter: lucide.FilterIcon,
flask: lucide.FlaskConicalIcon, flask: lucide.FlaskConicalIcon,
gripVertical: lucide.GripVerticalIcon, gripVertical: lucide.GripVerticalIcon,
keyboard: lucide.KeyboardIcon, keyboard: lucide.KeyboardIcon,
leftPanelHidden: lucide.PanelLeftOpenIcon, leftPanelHidden: lucide.PanelLeftOpenIcon,
leftPanelVisible: lucide.PanelLeftCloseIcon, leftPanelVisible: lucide.PanelLeftCloseIcon,
magicWand: lucide.Wand2Icon, magicWand: lucide.Wand2Icon,
moreVertical: lucide.MoreVerticalIcon, moreVertical: lucide.MoreVerticalIcon,
pencil: lucide.PencilIcon, pencil: lucide.PencilIcon,
plus: lucide.PlusIcon, plus: lucide.PlusIcon,
plusCircle: lucide.PlusCircleIcon, plusCircle: lucide.PlusCircleIcon,
question: lucide.ShieldQuestionIcon, question: lucide.ShieldQuestionIcon,
sendHorizontal: lucide.SendHorizonalIcon, sendHorizontal: lucide.SendHorizonalIcon,
settings2: lucide.Settings2Icon, settings2: lucide.Settings2Icon,
settings: lucide.SettingsIcon, settings: lucide.SettingsIcon,
trash: lucide.TrashIcon, sparkles: lucide.SparklesIcon,
update: lucide.RefreshCcwIcon, trash: lucide.TrashIcon,
upload: lucide.UploadIcon, update: lucide.RefreshCcwIcon,
x: lucide.XIcon, upload: lucide.UploadIcon,
x: lucide.XIcon,
empty: (props: HTMLAttributes<HTMLSpanElement>) => <span {...props} />, empty: (props: HTMLAttributes<HTMLSpanElement>) => <span {...props} />,
}; };
export interface IconProps { export interface IconProps {
icon: keyof typeof icons; icon: keyof typeof icons;
className?: string; className?: string;
size?: 'xs' | 'sm' | 'md'; size?: 'xs' | 'sm' | 'md';
spin?: boolean; spin?: boolean;
} }
export const Icon = memo(function Icon({icon, spin, size = 'md', className}: IconProps) { export const Icon = memo(function Icon({ icon, spin, size = 'md', className }: IconProps) {
const Component = icons[icon] ?? icons.question; const Component = icons[icon] ?? icons.question;
return ( return (
<Component <Component
className={classNames( className={classNames(
className, className,
'text-inherit', 'text-inherit',
size === 'md' && 'h-4 w-4', size === 'md' && 'h-4 w-4',
size === 'sm' && 'h-3.5 w-3.5', size === 'sm' && 'h-3.5 w-3.5',
size === 'xs' && 'h-3 w-3', size === 'xs' && 'h-3 w-3',
spin && 'animate-spin', spin && 'animate-spin',
)} )}
/> />
); );
}); });

View File

@@ -16,18 +16,20 @@ export interface RadioDropdownProps<T = string | null> {
value: T; value: T;
onChange: (value: T) => void; onChange: (value: T) => void;
items: RadioDropdownItem<T>[]; items: RadioDropdownItem<T>[];
extraItems?: DropdownProps['items'];
children: DropdownProps['children']; children: DropdownProps['children'];
} }
export function RadioDropdown<T = string | null>({ export function RadioDropdown<T = string | null>({
value, value,
items, items,
extraItems,
onChange, onChange,
children, children,
}: RadioDropdownProps<T>) { }: RadioDropdownProps<T>) {
const dropdownItems = useMemo( const dropdownItems = useMemo(
() => () => [
items.map((item) => { ...items.map((item) => {
if (item.type === 'separator') { if (item.type === 'separator') {
return item; return item;
} else { } else {
@@ -40,7 +42,9 @@ export function RadioDropdown<T = string | null>({
}; };
} }
}), }),
[items, value, onChange], ...(extraItems ?? []),
],
[items, extraItems, value, onChange],
); );
return <Dropdown items={dropdownItems}>{children}</Dropdown>; return <Dropdown items={dropdownItems}>{children}</Dropdown>;

View File

@@ -10,10 +10,11 @@ export interface PromptProps {
onResult: (value: string) => void; onResult: (value: string) => void;
label: InputProps['label']; label: InputProps['label'];
name: InputProps['name']; name: InputProps['name'];
defaultValue: InputProps['defaultValue']; defaultValue?: InputProps['defaultValue'];
placeholder?: InputProps['placeholder'];
} }
export function Prompt({ onHide, label, name, defaultValue, onResult }: PromptProps) { export function Prompt({ onHide, label, name, defaultValue, placeholder, onResult }: PromptProps) {
const [value, setValue] = useState<string>(defaultValue ?? ''); const [value, setValue] = useState<string>(defaultValue ?? '');
const handleSubmit = useCallback( const handleSubmit = useCallback(
(e: FormEvent<HTMLFormElement>) => { (e: FormEvent<HTMLFormElement>) => {
@@ -33,6 +34,7 @@ export function Prompt({ onHide, label, name, defaultValue, onResult }: PromptPr
hideLabel hideLabel
require require
autoSelect autoSelect
placeholder={placeholder}
label={label} label={label}
name={name} name={name}
defaultValue={defaultValue} defaultValue={defaultValue}

View File

@@ -1,3 +1,4 @@
import { dialog } from '@tauri-apps/api';
import type { DialogProps } from '../components/core/Dialog'; import type { DialogProps } from '../components/core/Dialog';
import { useDialog } from '../components/DialogContext'; import { useDialog } from '../components/DialogContext';
import type { PromptProps } from './Prompt'; import type { PromptProps } from './Prompt';
@@ -11,20 +12,17 @@ export function usePrompt() {
name, name,
label, label,
defaultValue, defaultValue,
}: { placeholder,
title: DialogProps['title']; }: Pick<DialogProps, 'title' | 'description'> &
description?: DialogProps['description']; Pick<PromptProps, 'name' | 'label' | 'defaultValue' | 'placeholder'>) =>
name: PromptProps['name'];
label: PromptProps['label'];
defaultValue: PromptProps['defaultValue'];
}) =>
new Promise((onResult: PromptProps['onResult']) => { new Promise((onResult: PromptProps['onResult']) => {
dialog.show({ dialog.show({
title, title,
description, description,
hideX: true, hideX: true,
size: 'sm', size: 'sm',
render: ({ hide }) => Prompt({ onHide: hide, onResult, name, label, defaultValue }), render: ({ hide }) =>
Prompt({ onHide: hide, onResult, name, label, defaultValue, placeholder }),
}); });
}); });
} }

View File

@@ -101,6 +101,22 @@ export function generateColors(
} }
const lightnessMap: Record<Appearance, Record<AppThemeColorVariant, number>> = { const lightnessMap: Record<Appearance, Record<AppThemeColorVariant, number>> = {
system: {
// Not actually used
0: 1,
50: 1,
100: 0.9,
200: 0.7,
300: 0.4,
400: 0.2,
500: 0,
600: -0.2,
700: -0.4,
800: -0.6,
900: -0.8,
950: -0.9,
1000: -1,
},
light: { light: {
0: 1, 0: 1,
50: 1, 50: 1,