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 type { DropdownItem } from './core/Dropdown';
import { Icon } from './core/Icon';
import type { RadioDropdownItem } from './core/RadioDropdown';
import { RadioDropdown } from './core/RadioDropdown';
type Props = {
@@ -8,7 +12,15 @@ type Props = {
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,
label: m,
}));
@@ -18,8 +30,32 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({
onChange,
className,
}: 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 (
<RadioDropdown value={method} items={methodItems} onChange={onChange}>
<RadioDropdown value={method} items={radioItems} extraItems={extraItems} onChange={onChange}>
<Button size="xs" className={className}>
{method.toUpperCase()}
</Button>

View File

@@ -21,7 +21,7 @@ export const SettingsDialog = () => {
return (
<VStack space={2}>
<HStack alignItems="center" space={2}>
<HStack className="mt-1" alignItems="center" space={2}>
<div className="w-1/3">Appearance</div>
<select
value={settings.appearance}
@@ -42,11 +42,11 @@ export const SettingsDialog = () => {
<Heading size={2}>
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}
</div>
</Heading>
<VStack className="w-full" space={3}>
<VStack className="mt-1 w-full" space={3}>
<Input
size="xs"
name="requestTimeout"

View File

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

View File

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

View File

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

View File

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

View File

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