mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 00:58:32 +02:00
More subtle layout tweaks
This commit is contained in:
@@ -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")]
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
Reference in New Issue
Block a user