More subtle layout tweaks

This commit is contained in:
Gregory Schier
2023-03-06 08:57:57 -08:00
parent 8ad301a666
commit 7c1755a0dc
10 changed files with 75 additions and 107 deletions

View File

@@ -1,7 +1,7 @@
use tauri::{Runtime, Window}; use tauri::{Runtime, Window};
const TRAFFIC_LIGHT_OFFSET_X: f64 = 15.0; const TRAFFIC_LIGHT_OFFSET_X: f64 = 15.0;
const TRAFFIC_LIGHT_OFFSET_Y: f64 = 20.0; const TRAFFIC_LIGHT_OFFSET_Y: f64 = 18.0;
pub trait WindowExt { pub trait WindowExt {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]

View File

@@ -1,21 +1,12 @@
import classnames from 'classnames'; import classnames from 'classnames';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { Button } from './components/Button';
import { Divider } from './components/Divider';
import { Grid } from './components/Grid';
import { IconButton } from './components/IconButton';
import { RequestPane } from './components/RequestPane'; import { RequestPane } from './components/RequestPane';
import { ResponsePane } from './components/ResponsePane'; import { ResponsePane } from './components/ResponsePane';
import { Sidebar } from './components/Sidebar'; import { Sidebar } from './components/Sidebar';
import { HStack } from './components/Stacks'; import { HStack } from './components/Stacks';
import { WindowDragRegion } from './components/WindowDragRegion'; import { WindowDragRegion } from './components/WindowDragRegion';
import { import { useRequests } from './hooks/useRequest';
useDeleteRequest,
useRequests,
useRequestUpdate,
useSendRequest,
} from './hooks/useRequest';
type Params = { type Params = {
workspaceId: string; workspaceId: string;
@@ -26,7 +17,6 @@ function App() {
const p = useParams<Params>(); const p = useParams<Params>();
const workspaceId = p.workspaceId ?? ''; const workspaceId = p.workspaceId ?? '';
const { data: requests } = useRequests(workspaceId); const { data: requests } = useRequests(workspaceId);
const navigate = useNavigate();
const request = requests?.find((r) => r.id === p.requestId); const request = requests?.find((r) => r.id === p.requestId);
const [screenWidth, setScreenWidth] = useState(window.innerWidth); const [screenWidth, setScreenWidth] = useState(window.innerWidth);
@@ -36,40 +26,28 @@ function App() {
const isH = screenWidth > 900; const isH = screenWidth > 900;
return ( return (
<div className="grid grid-cols-[auto_1fr] h-full text-gray-900"> <div className="grid grid-cols-[auto_1fr] h-full text-gray-900 overflow-hidden rounded-[11px]">
<Sidebar requests={requests ?? []} workspaceId={workspaceId} activeRequestId={request?.id} /> <Sidebar requests={requests ?? []} workspaceId={workspaceId} activeRequestId={request?.id} />
{request && ( {request && (
<div className="p-2 h-full"> <div className="h-full">
<div className="grid grid-rows-[auto_1fr] rounded-md h-full overflow-hidden"> <div className="grid grid-rows-[auto_1fr] h-full overflow-hidden">
<HStack <HStack
as={WindowDragRegion} as={WindowDragRegion}
className="pl-1 pr-3 bg-gray-50 text-sm" className="px-3 bg-gray-50/50 text-sm text-gray-900 border-b border-b-gray-50 pt-[1px]"
justify="center"
items="center" items="center"
> >
<div className="mr-auto"> {request.name}
<IconButton
size="xs"
icon="x"
onClick={() => navigate(`/workspaces/${workspaceId}`)}
/>
</div>
<div>{request.name}</div>
<div className="ml-auto"></div>
</HStack> </HStack>
<div <div
className={classnames( className={classnames(
'py-2 px-1 bg-gray-25 grid overflow-auto', 'bg-gray-25 grid overflow-auto',
isH ? 'grid-cols-[1fr_1fr]' : 'grid-rows-[minmax(0,auto)_minmax(0,100%)]', isH ? 'grid-cols-[1fr_1fr]' : 'grid-rows-[minmax(0,auto)_minmax(0,100%)]',
)} )}
> >
<RequestPane <RequestPane
fullHeight={isH} fullHeight={isH}
request={request} request={request}
className={classnames( className={classnames(isH ? 'pr-0' : 'pb-3 mb-1')}
'border-gray-100/50',
isH ? 'pr-0 border-r' : 'pb-3 mb-1 border-b',
)}
/> />
<ResponsePane requestId={request.id} /> <ResponsePane requestId={request.id} />
</div> </div>

View File

@@ -52,7 +52,7 @@ export const Button = forwardRef(function Button<T extends ElementType>(
justify === 'center' && 'justify-center', justify === 'center' && 'justify-center',
size === 'md' && 'h-10 px-4', size === 'md' && 'h-10 px-4',
size === 'sm' && 'h-8 px-3 text-sm', size === 'sm' && 'h-8 px-3 text-sm',
size === 'xs' && 'h-6 px-3 text-xs', size === 'xs' && 'h-7 px-2.5 text-sm',
)} )}
{...props} {...props}
> >

View File

@@ -12,7 +12,7 @@
} }
.cm-editor { .cm-editor {
@apply w-full block text-[0.85rem] bg-gray-25; @apply bg-background w-full block text-[0.85rem];
&.cm-focused { &.cm-focused {
outline: none !important; outline: none !important;
@@ -70,7 +70,7 @@
} }
.cm-editor .cm-gutters { .cm-editor .cm-gutters {
@apply bg-gray-25 border-0 text-gray-200; @apply bg-background border-0 text-gray-200;
} }
.cm-editor .cm-gutterElement { .cm-editor .cm-gutterElement {

View File

@@ -4,7 +4,7 @@ import { Compartment, EditorState } from '@codemirror/state';
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view'; import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
import classnames from 'classnames'; import classnames from 'classnames';
import { EditorView } from 'codemirror'; import { EditorView } from 'codemirror';
import type { HTMLAttributes } from 'react'; import type { CSSProperties, HTMLAttributes } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import './Editor.css'; import './Editor.css';
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions'; import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
@@ -13,6 +13,7 @@ import { singleLineExt } from './singleLine';
export interface EditorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> { export interface EditorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
height?: 'auto' | 'full'; height?: 'auto' | 'full';
contentType?: string; contentType?: string;
backgroundColor?: string;
autoFocus?: boolean; autoFocus?: boolean;
valueKey?: string | number; valueKey?: string | number;
defaultValue?: string; defaultValue?: string;
@@ -26,6 +27,7 @@ export interface EditorProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onCha
export default function Editor({ export default function Editor({
height, height,
contentType, contentType,
backgroundColor,
autoFocus, autoFocus,
placeholder, placeholder,
valueKey, valueKey,
@@ -100,6 +102,7 @@ export default function Editor({
height === 'auto' ? 'cm-auto-height' : 'cm-full-height', height === 'auto' ? 'cm-auto-height' : 'cm-full-height',
singleLine ? 'cm-singleline' : 'cm-multiline', singleLine ? 'cm-singleline' : 'cm-multiline',
)} )}
data-color-background="var(--color-gray-50)"
{...props} {...props}
/> />
); );

View File

@@ -2,7 +2,6 @@ import classnames from 'classnames';
import { useRequestUpdate, useSendRequest } from '../hooks/useRequest'; import { useRequestUpdate, useSendRequest } from '../hooks/useRequest';
import type { HttpRequest } from '../lib/models'; import type { HttpRequest } from '../lib/models';
import { Button } from './Button'; import { Button } from './Button';
import { Divider } from './Divider';
import Editor from './Editor/Editor'; import Editor from './Editor/Editor';
import { ScrollArea } from './ScrollArea'; import { ScrollArea } from './ScrollArea';
import { HStack } from './Stacks'; import { HStack } from './Stacks';
@@ -18,10 +17,12 @@ export function RequestPane({ fullHeight, request, className }: Props) {
const updateRequest = useRequestUpdate(request ?? null); const updateRequest = useRequestUpdate(request ?? null);
const sendRequest = useSendRequest(request ?? null); const sendRequest = useSendRequest(request ?? null);
return ( return (
<div className={classnames(className, 'grid grid-rows-[auto_auto_minmax(0,1fr)] grid-cols-1')}> <div
<div> className={classnames(className, 'py-2 grid grid-rows-[auto_auto_minmax(0,1fr)] grid-cols-1')}
>
<div className="pl-2">
<UrlBar <UrlBar
className="bg-transparent border-0 mb-1" className="border-0 mb-1"
key={request.id} key={request.id}
method={request.method} method={request.method}
url={request.url} url={request.url}
@@ -30,9 +31,7 @@ export function RequestPane({ fullHeight, request, className }: Props) {
onUrlChange={(url) => updateRequest.mutate({ url })} onUrlChange={(url) => updateRequest.mutate({ url })}
sendRequest={sendRequest.mutate} sendRequest={sendRequest.mutate}
/> />
<div className="mx-2"> {/*<Divider />*/}
<Divider />
</div>
</div> </div>
{/*<Divider className="mb-2" />*/} {/*<Divider className="mb-2" />*/}
<ScrollArea className="max-w-full pb-2 mx-2"> <ScrollArea className="max-w-full pb-2 mx-2">

View File

@@ -1,13 +1,10 @@
import classnames from 'classnames'; import classnames from 'classnames';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useDeleteAllResponses, useDeleteResponse, useResponses } from '../hooks/useResponses'; import { useDeleteAllResponses, useDeleteResponse, useResponses } from '../hooks/useResponses';
import { Button } from './Button';
import { Divider } from './Divider';
import { Dropdown } from './Dropdown'; import { Dropdown } from './Dropdown';
import Editor from './Editor/Editor'; import Editor from './Editor/Editor';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { IconButton } from './IconButton'; import { IconButton } from './IconButton';
import { ScrollArea } from './ScrollArea';
import { HStack } from './Stacks'; import { HStack } from './Stacks';
interface Props { interface Props {
@@ -45,23 +42,23 @@ export function ResponsePane({ requestId, className }: Props) {
}, [response?.body, contentType]); }, [response?.body, contentType]);
return ( return (
<div <div className="p-2">
className={classnames( <div
className, className={classnames(
'max-h-full h-full grid grid-rows-[auto_auto_minmax(0,1fr)] grid-cols-1', className,
)} 'max-h-full h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1 bg-gray-50/50 rounded-md',
> )}
{/*<HStack as={WindowDragRegion} items="center" className="pl-1.5 pr-1">*/} >
{/*</HStack>*/} {/*<HStack as={WindowDragRegion} items="center" className="pl-1.5 pr-1">*/}
{response?.error && ( {/*</HStack>*/}
<div className="text-white bg-red-500 px-2 py-1 rounded">{response.error}</div> {response?.error && (
)} <div className="text-white bg-red-500 px-2 py-1 rounded">{response.error}</div>
{response && ( )}
<> {response && (
<div> <>
<HStack <HStack
items="center" items="center"
className="italic text-gray-500 text-sm w-full mb-1 flex-shrink-0 pl-2" className="italic text-gray-500 text-sm w-full mb-1 flex-shrink-0 pl-2 py-1"
> >
<div className="whitespace-nowrap"> <div className="whitespace-nowrap">
{response.status} {response.status}
@@ -104,43 +101,27 @@ export function ResponsePane({ requestId, className }: Props) {
</Dropdown> </Dropdown>
</HStack> </HStack>
</HStack> </HStack>
<div className="px-2">
<Divider />
</div>
</div>
<ScrollArea className="max-w-full pb-2 mx-2">
<HStack className="mt-2 hide-scrollbar" space={1}>
{['Preview', 'Headers', 'Cookies', 'Timing'].map((label, i) => (
<Button
key={label}
size="xs"
color={i === 0 && 'gray'}
className={i !== 0 && 'opacity-50 hover:opacity-60'}
>
{label}
</Button>
))}
</HStack>
</ScrollArea>
{viewMode === 'pretty' && contentForIframe !== null ? ( {viewMode === 'pretty' && contentForIframe !== null ? (
<div className="pl-2"> <div className="px-2 pb-2">
<iframe <iframe
title="Response preview" title="Response preview"
srcDoc={contentForIframe} srcDoc={contentForIframe}
sandbox="allow-scripts allow-same-origin" sandbox="allow-scripts allow-same-origin"
className="h-full w-full rounded-lg" className="h-full w-full rounded-md border border-gray-100/20"
/>
</div>
) : response?.body ? (
<Editor
backgroundColor="red"
valueKey={`${contentType}:${response.body}`}
defaultValue={response?.body}
contentType={contentType}
/> />
</div> ) : null}
) : response?.body ? ( </>
<Editor )}
valueKey={`${contentType}:${response.body}`} </div>
defaultValue={response?.body}
contentType={contentType}
/>
) : null}
</>
)}
</div> </div>
); );
} }

View File

@@ -23,21 +23,24 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests, ...
const { toggleTheme } = useTheme(); const { toggleTheme } = useTheme();
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
return ( return (
<div className={classnames(className, 'w-52 bg-gray-50 h-full px-2')} {...props}> <div
<HStack as={WindowDragRegion} items="center" className="py-2" justify="end"> className={classnames(className, 'w-52 bg-gray-50 h-full border-gray-100/50 relative z-10')}
{...props}
>
<HStack as={WindowDragRegion} items="center" justify="end">
<Dialog wide open={open} onOpenChange={setOpen} title="Edit Headers"> <Dialog wide open={open} onOpenChange={setOpen} title="Edit Headers">
<HeaderEditor /> <HeaderEditor />
<Button className="ml-auto mt-5" color="primary" onClick={() => setOpen(false)}> <Button className="ml-auto mt-5" color="primary" onClick={() => setOpen(false)}>
Save Save
</Button> </Button>
</Dialog> </Dialog>
<IconButton {/*<IconButton*/}
size="sm" {/* size="sm"*/}
icon="camera" {/* icon="camera"*/}
onClick={() => { {/* onClick={() => {*/}
setOpen((v) => !v); {/* setOpen((v) => !v);*/}
}} {/* }}*/}
/> {/*/>*/}
<IconButton size="sm" icon="sun" onClick={toggleTheme} /> <IconButton size="sm" icon="sun" onClick={toggleTheme} />
<IconButton <IconButton
size="sm" size="sm"
@@ -47,7 +50,7 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests, ...
}} }}
/> />
</HStack> </HStack>
<VStack as="ul" className="pb-3" space={1}> <VStack as="ul" className="py-3 px-2" space={1}>
{requests.map((r) => ( {requests.map((r) => (
<SidebarItem key={r.id} request={r} active={r.id === activeRequestId} /> <SidebarItem key={r.id} request={r} active={r.id === activeRequestId} />
))} ))}
@@ -63,7 +66,7 @@ function SidebarItem({ request, active }: { request: HttpRequest; active: boolea
as={Link} as={Link}
to={`/workspaces/${request.workspaceId}/requests/${request.id}`} to={`/workspaces/${request.workspaceId}/requests/${request.id}`}
className={classnames('w-full', active && 'bg-gray-500/[0.1] text-gray-900')} className={classnames('w-full', active && 'bg-gray-500/[0.1] text-gray-900')}
size="sm" size="xs"
justify="start" justify="start"
> >
{request.name} {request.name}

View File

@@ -7,7 +7,7 @@ export function WindowDragRegion({ className, ...props }: Props) {
return ( return (
<div <div
data-tauri-drag-region data-tauri-drag-region
className={classnames(className, 'w-full h-8 flex-shrink-0 box-content')} className={classnames(className, 'w-full h-10 flex-shrink-0')}
{...props} {...props}
/> />
); );

View File

@@ -41,6 +41,10 @@ html, body, #root {
/* }*/ /* }*/
/*}*/ /*}*/
[data-color-background] {
--color-background: attr(data-bg-color);
}
@layer base { @layer base {
:root, [data-theme="light"] { :root, [data-theme="light"] {
/* Colors */ /* Colors */