Some small changes

This commit is contained in:
Gregory Schier
2023-03-14 00:08:03 -07:00
parent a209a486aa
commit 1e09f09bd6
7 changed files with 80 additions and 85 deletions

View File

@@ -4,6 +4,7 @@ import { useDeleteResponses } from '../hooks/useDeleteResponses';
import { useDeleteResponse } from '../hooks/useResponseDelete';
import { useResponses } from '../hooks/useResponses';
import { tryFormatJson } from '../lib/formatters';
import type { HttpResponse } from '../lib/models';
import { Dropdown } from './core/Dropdown';
import { Editor } from './core/Editor';
import { Icon } from './core/Icon';
@@ -20,11 +21,11 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
const [activeResponseId, setActiveResponseId] = useState<string | null>(null);
const [viewMode, setViewMode] = useState<'pretty' | 'raw'>('pretty');
const responses = useResponses();
const response = activeResponseId
? responses.find((r) => r.id === activeResponseId)
: responses[responses.length - 1];
const deleteResponse = useDeleteResponse(response);
const deleteAllResponses = useDeleteResponses(response?.requestId);
const activeResponse: HttpResponse | null = activeResponseId
? responses.find((r) => r.id === activeResponseId) ?? null
: responses[responses.length - 1] ?? null;
const deleteResponse = useDeleteResponse(activeResponse);
const deleteAllResponses = useDeleteResponses(activeResponse?.requestId);
useEffect(() => {
setActiveResponseId(null);
@@ -32,10 +33,15 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
const contentType = useMemo(
() =>
response?.headers.find((h) => h.name.toLowerCase() === 'content-type')?.value ?? 'text/plain',
[response],
activeResponse?.headers.find((h) => h.name.toLowerCase() === 'content-type')?.value ??
'text/plain',
[activeResponse],
);
if (activeResponse === null) {
return null;
}
return (
<div className="p-2">
<div
@@ -52,15 +58,15 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
alignItems="center"
className="italic text-gray-700 text-sm w-full mb-1 flex-shrink-0 pl-2"
>
{response && response.status > 0 && (
{activeResponse && activeResponse.status > 0 && (
<div className="whitespace-nowrap">
<StatusColor statusCode={response.status}>
{response.status}
{response.statusReason && ` ${response.statusReason}`}
<StatusColor statusCode={activeResponse.status}>
{activeResponse.status}
{activeResponse.statusReason && ` ${activeResponse.statusReason}`}
</StatusColor>
&nbsp;&bull;&nbsp;
{response.elapsed}ms &nbsp;&bull;&nbsp;
{Math.round(response.body.length / 1000)} KB
{activeResponse.elapsed}ms &nbsp;&bull;&nbsp;
{Math.round(activeResponse.body.length / 1000)} KB
</div>
)}
@@ -86,7 +92,7 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
'-----',
...responses.slice(0, 10).map((r) => ({
label: r.status + ' - ' + r.elapsed + ' ms',
leftSlot: response?.id === r.id ? <Icon icon="check" /> : <></>,
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
onSelect: () => setActiveResponseId(r.id),
})),
]}
@@ -96,33 +102,29 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
</HStack>
</HStack>
{response && (
<>
{response?.error ? (
<div className="p-1">
<div className="text-white bg-red-500 px-3 py-2 rounded">{response.error}</div>
</div>
) : viewMode === 'pretty' && contentType.includes('html') ? (
<Webview body={response.body} contentType={contentType} url={response.url} />
) : viewMode === 'pretty' && contentType.includes('json') ? (
<Editor
readOnly
key={`${contentType}:${response.updatedAt}:pretty`}
className="bg-gray-50 dark:!bg-gray-100"
defaultValue={tryFormatJson(response?.body)}
contentType={contentType}
/>
) : response?.body ? (
<Editor
readOnly
key={`${contentType}:${response.updatedAt}`}
className="bg-gray-50 dark:!bg-gray-100"
defaultValue={response?.body}
contentType={contentType}
/>
) : null}
</>
)}
{activeResponse?.error ? (
<div className="p-1">
<div className="text-white bg-red-500 px-3 py-2 rounded">{activeResponse.error}</div>
</div>
) : viewMode === 'pretty' && contentType.includes('html') ? (
<Webview body={activeResponse.body} contentType={contentType} url={activeResponse.url} />
) : viewMode === 'pretty' && contentType.includes('json') ? (
<Editor
readOnly
key={`${contentType}:${activeResponse.updatedAt}:pretty`}
className="bg-gray-50 dark:!bg-gray-100"
defaultValue={tryFormatJson(activeResponse?.body)}
contentType={contentType}
/>
) : activeResponse?.body ? (
<Editor
readOnly
key={`${contentType}:${activeResponse.updatedAt}`}
className="bg-gray-50 dark:!bg-gray-100"
defaultValue={activeResponse?.body}
contentType={contentType}
/>
) : null}
</div>
</div>
);

View File

@@ -1,5 +1,5 @@
import { useRouteError } from 'react-router-dom';
import { ButtonLink } from './core/ButtonLink';
import { Button } from './core/Button';
import { Heading } from './core/Heading';
import { VStack } from './core/Stacks';
@@ -14,9 +14,9 @@ export default function RouteError() {
<pre className="text-sm select-auto cursor-text bg-gray-100 p-3 rounded whitespace-normal">
{message}
</pre>
<ButtonLink to="/" color="primary">
<Button to="/" color="primary">
Go Home
</ButtonLink>
</Button>
</VStack>
</div>
);

View File

@@ -2,11 +2,12 @@ import classnames from 'classnames';
import { useState } from 'react';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useCreateRequest } from '../hooks/useCreateRequest';
import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useRequests } from '../hooks/useRequests';
import { useTheme } from '../hooks/useTheme';
import { useUpdateRequest } from '../hooks/useUpdateRequest';
import type { HttpRequest } from '../lib/models';
import { ButtonLink } from './core/ButtonLink';
import { Button } from './core/Button';
import { IconButton } from './core/IconButton';
import { HStack, VStack } from './core/Stacks';
import { WindowDragRegion } from './core/WindowDragRegion';
@@ -18,13 +19,14 @@ interface Props {
export function Sidebar({ className }: Props) {
const requests = useRequests();
const activeRequest = useActiveRequest();
const deleteRequest = useDeleteRequest(activeRequest);
const createRequest = useCreateRequest({ navigateAfter: true });
const { appearance, toggleAppearance } = useTheme();
return (
<div
className={classnames(
className,
'min-w-[12rem] bg-gray-100 h-full border-r border-gray-200 relative grid grid-rows-[auto,1fr]',
'w-[15rem] bg-gray-100 h-full border-r border-gray-200 relative grid grid-rows-[auto,1fr]',
)}
>
<HStack as={WindowDragRegion} alignItems="center" justifyContent="end">
@@ -47,6 +49,7 @@ export function Sidebar({ className }: Props) {
alignItems="center"
justifyContent="end"
>
<IconButton icon="trash" onClick={() => deleteRequest.mutate()} />
<IconButton icon={appearance === 'dark' ? 'moon' : 'sun'} onClick={toggleAppearance} />
</HStack>
</VStack>
@@ -64,21 +67,28 @@ function SidebarItem({ request, active }: { request: HttpRequest; active: boolea
const handleFocus = (el: HTMLInputElement | null) => {
el?.focus();
el?.select();
};
return (
<li key={request.id} className="flex">
<ButtonLink
<li>
<Button
color="custom"
size="sm"
className={classnames(
'w-full',
editing && 'focus-within:border-blue-400/40',
active
? 'bg-gray-200/70 text-gray-900'
: 'text-gray-600 hover:text-gray-800 active:bg-gray-200/30',
)}
onKeyDown={(e) => {
// Hitting enter on active request during keyboard nav will start edit
if (active && e.key === 'Enter') {
e.preventDefault();
setEditing(true);
}
}}
to={`/workspaces/${request.workspaceId}/requests/${request.id}`}
contentEditable={editing}
onDoubleClick={() => setEditing(true)}
justify="start"
>
@@ -86,18 +96,23 @@ function SidebarItem({ request, active }: { request: HttpRequest; active: boolea
<input
ref={handleFocus}
defaultValue={request.name}
className="bg-transparent outline-none"
className="bg-transparent outline-none w-full"
onBlur={(e) => handleSubmitNameEdit(e.currentTarget)}
onKeyDown={async (e) => {
if (e.key === 'Enter') {
await handleSubmitNameEdit(e.currentTarget);
switch (e.key) {
case 'Enter':
await handleSubmitNameEdit(e.currentTarget);
break;
case 'Escape':
setEditing(false);
break;
}
}}
/>
) : (
request.name || request.url
<span className="truncate">{request.name || request.url}</span>
)}
</ButtonLink>
</Button>
</li>
);
}

View File

@@ -1,4 +1,4 @@
import { ButtonLink } from './core/ButtonLink';
import { Button } from './core/Button';
import { Heading } from './core/Heading';
import { VStack } from './core/Stacks';
import { useWorkspaces } from '../hooks/useWorkspaces';
@@ -9,9 +9,9 @@ export default function Workspaces() {
<VStack as="ul" className="p-12">
<Heading>Workspaces</Heading>
{workspaces.map((w) => (
<ButtonLink key={w.id} color="gray" to={`/workspaces/${w.id}`}>
<Button key={w.id} color="gray" to={`/workspaces/${w.id}`}>
{w.name}
</ButtonLink>
</Button>
))}
</VStack>
);

View File

@@ -1,5 +1,5 @@
import classnames from 'classnames';
import type { KeyboardEvent, MouseEvent, ReactNode } from 'react';
import type { HTMLAttributes } from 'react';
import { forwardRef } from 'react';
import { Link } from 'react-router-dom';
import { Icon } from './Icon';
@@ -14,22 +14,13 @@ const colorStyles = {
danger: 'bg-red-400 text-white hover:bg-red-500',
};
export type ButtonProps = {
export type ButtonProps = HTMLAttributes<HTMLElement> & {
to?: string;
color?: keyof typeof colorStyles;
size?: 'sm' | 'md';
justify?: 'start' | 'center';
type?: 'button' | 'submit';
onClick?: (event: MouseEvent<HTMLElement>) => void;
onDoubleClick?: (event: MouseEvent<HTMLElement>) => void;
contentEditable?: boolean;
onKeyDown?: (event: KeyboardEvent<HTMLElement>) => void;
forDropdown?: boolean;
className?: string;
children?: ReactNode;
disabled?: boolean;
title?: string;
tabIndex?: number;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@@ -1,13 +0,0 @@
import classnames from 'classnames';
import type { ButtonProps } from './Button';
import { Button } from './Button';
type Props = ButtonProps & {
to: string;
};
export function ButtonLink({ to, className, ...buttonProps }: Props) {
return (
<Button to={to} className={classnames(className, 'w-full')} tabIndex={-1} {...buttonProps} />
);
}

View File

@@ -2,15 +2,15 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import type { HttpResponse } from '../lib/models';
export function useDeleteResponse(response?: HttpResponse) {
export function useDeleteResponse(response: HttpResponse | null) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () => {
if (response == null) return;
if (response === null) return;
await invoke('delete_response', { id: response.id });
},
onSuccess: () => {
if (response == null) return;
if (response === null) return;
queryClient.setQueryData(
['responses', { requestId: response.requestId }],
(responses: HttpResponse[] = []) => responses.filter((r) => r.id !== response.id),