Split layouts and things

This commit is contained in:
Gregory Schier
2024-02-02 12:41:37 -08:00
parent e27ed9becc
commit 67aa7b7268
8 changed files with 126 additions and 98 deletions
+6 -3
View File
@@ -72,7 +72,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
const dialog = useDialog(); const dialog = useDialog();
return ( return (
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]"> <div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto]">
<Editor <Editor
contentType="application/graphql" contentType="application/graphql"
defaultValue={query ?? ''} defaultValue={query ?? ''}
@@ -124,8 +124,10 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
} }
{...extraEditorProps} {...extraEditorProps}
/> />
<Separator variant="primary" /> <div className="grid min-h-[5rem]">
<p className="py-1 text-gray-500 text-sm">Variables</p> <Separator variant="primary" className="pb-1">
Variables
</Separator>
<Editor <Editor
format={tryFormatJson} format={tryFormatJson}
contentType="application/json" contentType="application/json"
@@ -138,5 +140,6 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
{...extraEditorProps} {...extraEditorProps}
/> />
</div> </div>
</div>
); );
} }
+24 -18
View File
@@ -154,6 +154,8 @@ export function GrpcConnectionLayout({ style }: Props) {
return ( return (
<SplitLayout <SplitLayout
name="grpc_layout"
className="p-3"
style={style} style={style}
leftSlot={() => ( leftSlot={() => (
<VStack space={2}> <VStack space={2}>
@@ -188,7 +190,10 @@ export function GrpcConnectionLayout({ style }: Props) {
> >
<Button <Button
size="sm" size="sm"
className="border border-highlight font-mono text-xs text-gray-800" className={classNames(
'border border-highlight font-mono text-xs text-gray-800',
paneSize < 400 && 'flex-1',
)}
rightSlot={<Icon className="text-gray-600" size="sm" icon="chevronDown" />} rightSlot={<Icon className="text-gray-600" size="sm" icon="chevronDown" />}
> >
{select.options.find((o) => o.value === select.value)?.label} {select.options.find((o) => o.value === select.value)?.label}
@@ -198,7 +203,7 @@ export function GrpcConnectionLayout({ style }: Props) {
className="border border-highlight" className="border border-highlight"
size="sm" size="sm"
title="to-do" title="to-do"
hotkeyAction="request.send" hotkeyAction={grpc.isStreaming ? undefined : 'request.send'}
onClick={grpc.isStreaming ? handleCancel : handleConnect} onClick={grpc.isStreaming ? handleCancel : handleConnect}
icon={ icon={
grpc.isStreaming grpc.isStreaming
@@ -212,7 +217,7 @@ export function GrpcConnectionLayout({ style }: Props) {
: 'sendHorizontal' : 'sendHorizontal'
} }
/> />
{activeMethod?.clientStreaming && ( {activeMethod?.clientStreaming && grpc.isStreaming && (
<IconButton <IconButton
className="border border-highlight" className="border border-highlight"
size="sm" size="sm"
@@ -251,11 +256,15 @@ export function GrpcConnectionLayout({ style }: Props) {
{grpc.unary.error} {grpc.unary.error}
</Banner> </Banner>
) : grpc.messages.length > 0 ? ( ) : grpc.messages.length > 0 ? (
<div className="grid grid-rows-[minmax(0,1fr)_auto] overflow-hidden"> <SplitLayout
name="grpc_messages2"
minHeightPx={20}
defaultRatio={0.25}
leftSlot={() => (
<div className="overflow-y-auto"> <div className="overflow-y-auto">
{...grpc.messages.map((m) => ( {...grpc.messages.map((m, i) => (
<HStack <HStack
key={m.time.getTime()} key={`${m.time.getTime()}::${m.message}::${i}`}
space={2} space={2}
onClick={() => { onClick={() => {
if (m === activeMessage) setActiveMessage(null); if (m === activeMessage) setActiveMessage(null);
@@ -290,23 +299,20 @@ export function GrpcConnectionLayout({ style }: Props) {
</HStack> </HStack>
))} ))}
</div> </div>
<div className={classNames(activeMessage ? 'block' : 'hidden')}> )}
<div className="pb-1 px-2"> rightSlot={() =>
activeMessage && (
<div className="grid grid-rows-[auto_minmax(0,1fr)]">
<div className="pb-3 px-2">
<Separator /> <Separator />
</div> </div>
<div className="pl-2 pb-1 h-[6rem]"> <div className="pl-2 overflow-y-auto">
<JsonAttributeTree attrValue={JSON.parse(activeMessage?.message ?? '{}')} /> <JsonAttributeTree attrValue={JSON.parse(activeMessage?.message ?? '{}')} />
</div> </div>
{/*<Editor*/}
{/* className="bg-gray-50 dark:bg-gray-100 max-h-30"*/}
{/* contentType="application/json"*/}
{/* heightMode="auto"*/}
{/* defaultValue={tryFormatJson(activeMessage?.message ?? '')}*/}
{/* forceUpdateKey={activeMessage?.time.getTime()}*/}
{/* readOnly*/}
{/*/>*/}
</div>
</div> </div>
)
}
/>
) : resp ? ( ) : resp ? (
<Editor <Editor
className="bg-gray-50 dark:bg-gray-100" className="bg-gray-50 dark:bg-gray-100"
+2
View File
@@ -11,6 +11,8 @@ interface Props {
export function HttpRequestLayout({ style }: Props) { export function HttpRequestLayout({ style }: Props) {
return ( return (
<SplitLayout <SplitLayout
name="http_layout"
className="p-3"
style={style} style={style}
leftSlot={({ orientation, style }) => ( leftSlot={({ orientation, style }) => (
<RequestPane style={style} fullHeight={orientation === 'horizontal'} /> <RequestPane style={style} fullHeight={orientation === 'horizontal'} />
+4 -2
View File
@@ -1,6 +1,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
import React from 'react'; import React from 'react';
import { Separator } from './core/Separator';
interface ResizeBarProps { interface ResizeBarProps {
style?: CSSProperties; style?: CSSProperties;
@@ -17,6 +18,7 @@ export function ResizeHandle({
style, style,
justify, justify,
className, className,
barClassName,
onResizeStart, onResizeStart,
onReset, onReset,
isResizing, isResizing,
@@ -28,6 +30,8 @@ export function ResizeHandle({
aria-hidden aria-hidden
draggable draggable
style={style} style={style}
onDragStart={onResizeStart}
onDoubleClick={onReset}
className={classNames( className={classNames(
className, className,
'group z-10 flex', 'group z-10 flex',
@@ -39,8 +43,6 @@ export function ResizeHandle({
side === 'left' && 'left-0', side === 'left' && 'left-0',
side === 'top' && 'top-0', side === 'top' && 'top-0',
)} )}
onDragStart={onResizeStart}
onDoubleClick={onReset}
> >
{/* Show global overlay with cursor style to ensure cursor remains the same when moving quickly */} {/* Show global overlay with cursor style to ensure cursor remains the same when moving quickly */}
{isResizing && ( {isResizing && (
+1 -1
View File
@@ -100,7 +100,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
) : null} ) : null}
<div <div
className={classNames( className={classNames(
'max-w-[15em] truncate w-full', 'truncate w-full',
justify === 'start' ? 'text-left' : 'text-center', justify === 'start' ? 'text-left' : 'text-center',
innerClassName, innerClassName,
)} )}
+2 -1
View File
@@ -1,10 +1,11 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { ReactNode } from 'react';
interface Props { interface Props {
orientation?: 'horizontal' | 'vertical'; orientation?: 'horizontal' | 'vertical';
variant?: 'primary' | 'secondary'; variant?: 'primary' | 'secondary';
className?: string; className?: string;
children?: string; children?: ReactNode;
} }
export function Separator({ export function Separator({
+32 -18
View File
@@ -15,27 +15,40 @@ interface SlotProps {
} }
interface Props { interface Props {
style: CSSProperties; name: string;
leftSlot: (props: SlotProps) => ReactNode; leftSlot: (props: SlotProps) => ReactNode;
rightSlot: (props: SlotProps) => ReactNode; rightSlot: (props: SlotProps) => ReactNode;
style?: CSSProperties;
className?: string;
defaultRatio?: number;
minHeightPx?: number;
minWidthPx?: number;
} }
const areaL = { gridArea: 'left' }; const areaL = { gridArea: 'left' };
const areaR = { gridArea: 'right' }; const areaR = { gridArea: 'right' };
const areaD = { gridArea: 'drag' }; const areaD = { gridArea: 'drag' };
const DEFAULT = 0.5;
const MIN_WIDTH_PX = 10;
const MIN_HEIGHT_PX = 30;
const STACK_VERTICAL_WIDTH = 700; const STACK_VERTICAL_WIDTH = 700;
export function SplitLayout({ style, leftSlot, rightSlot }: Props) { export function SplitLayout({
style,
leftSlot,
rightSlot,
className,
name,
defaultRatio = 0.5,
minHeightPx = 10,
minWidthPx = 10,
}: Props) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [vertical, setVertical] = useState<boolean>(false); const [vertical, setVertical] = useState<boolean>(false);
const [widthRaw, setWidth] = useLocalStorage<number>(`body_width::${useActiveWorkspaceId()}`); const [widthRaw, setWidth] = useLocalStorage<number>(`${name}_width::${useActiveWorkspaceId()}`);
const [heightRaw, setHeight] = useLocalStorage<number>(`body_height::${useActiveWorkspaceId()}`); const [heightRaw, setHeight] = useLocalStorage<number>(
const width = widthRaw ?? DEFAULT; `${name}_height::${useActiveWorkspaceId()}`,
const height = heightRaw ?? DEFAULT; );
const width = widthRaw ?? defaultRatio;
const height = heightRaw ?? defaultRatio;
const [isResizing, setIsResizing] = useState<boolean>(false); const [isResizing, setIsResizing] = useState<boolean>(false);
const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>( const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>(
null, null,
@@ -52,7 +65,7 @@ export function SplitLayout({ style, leftSlot, rightSlot }: Props) {
? ` ? `
' ${areaL.gridArea}' minmax(0,${1 - height}fr) ' ${areaL.gridArea}' minmax(0,${1 - height}fr)
' ${areaD.gridArea}' 0 ' ${areaD.gridArea}' 0
' ${areaR.gridArea}' minmax(0,${height}fr) ' ${areaR.gridArea}' minmax(${minHeightPx}px,${height}fr)
/ 1fr / 1fr
` `
: ` : `
@@ -71,8 +84,8 @@ export function SplitLayout({ style, leftSlot, rightSlot }: Props) {
}; };
const handleReset = useCallback( const handleReset = useCallback(
() => (vertical ? setHeight(DEFAULT) : setWidth(DEFAULT)), () => (vertical ? setHeight(defaultRatio) : setWidth(defaultRatio)),
[setHeight, vertical, setWidth], [vertical, setHeight, defaultRatio, setWidth],
); );
const handleResizeStart = useCallback( const handleResizeStart = useCallback(
@@ -91,18 +104,18 @@ export function SplitLayout({ style, leftSlot, rightSlot }: Props) {
move: (e: MouseEvent) => { move: (e: MouseEvent) => {
e.preventDefault(); // Prevent text selection and things e.preventDefault(); // Prevent text selection and things
if (vertical) { if (vertical) {
const maxHeightPx = containerRect.height - MIN_HEIGHT_PX; const maxHeightPx = containerRect.height - minHeightPx;
const newHeightPx = clamp( const newHeightPx = clamp(
startHeight - (e.clientY - mouseStartY), startHeight - (e.clientY - mouseStartY),
MIN_HEIGHT_PX, minHeightPx,
maxHeightPx, maxHeightPx,
); );
setHeight(newHeightPx / containerRect.height); setHeight(newHeightPx / containerRect.height);
} else { } else {
const maxWidthPx = containerRect.width - MIN_WIDTH_PX; const maxWidthPx = containerRect.width - minWidthPx;
const newWidthPx = clamp( const newWidthPx = clamp(
startWidth - (e.clientX - mouseStartX), startWidth - (e.clientX - mouseStartX),
MIN_WIDTH_PX, minWidthPx,
maxWidthPx, maxWidthPx,
); );
setWidth(newWidthPx / containerRect.width); setWidth(newWidthPx / containerRect.width);
@@ -118,7 +131,7 @@ export function SplitLayout({ style, leftSlot, rightSlot }: Props) {
document.documentElement.addEventListener('mouseup', moveState.current.up); document.documentElement.addEventListener('mouseup', moveState.current.up);
setIsResizing(true); setIsResizing(true);
}, },
[width, height, vertical, setHeight, setWidth], [width, height, vertical, minHeightPx, setHeight, minWidthPx, setWidth],
); );
const activeRequestId = useActiveRequestId(); const activeRequestId = useActiveRequestId();
@@ -127,11 +140,12 @@ export function SplitLayout({ style, leftSlot, rightSlot }: Props) {
} }
return ( return (
<div ref={containerRef} className="grid gap-1.5 w-full h-full p-3" style={styles}> <div ref={containerRef} className={classNames(className, 'grid w-full h-full')} style={styles}>
{leftSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })} {leftSlot({ style: areaL, orientation: vertical ? 'vertical' : 'horizontal' })}
<ResizeHandle <ResizeHandle
style={areaD} style={areaD}
isResizing={isResizing} isResizing={isResizing}
barClassName={'bg-red-300'}
className={classNames(vertical ? 'translate-y-0.5' : 'translate-x-0.5')} className={classNames(vertical ? 'translate-y-0.5' : 'translate-x-0.5')}
onResizeStart={handleResizeStart} onResizeStart={handleResizeStart}
onReset={handleReset} onReset={handleReset}
+4 -4
View File
@@ -1,9 +1,7 @@
import { useMutation, useQuery } from '@tanstack/react-query'; import { useMutation, useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import { message } from '@tauri-apps/api/dialog';
import { emit } from '@tauri-apps/api/event'; import { emit } from '@tauri-apps/api/event';
import { useState } from 'react'; import { useState } from 'react';
import { send } from 'vite';
import { useListenToTauriEvent } from './useListenToTauriEvent'; import { useListenToTauriEvent } from './useListenToTauriEvent';
interface ReflectResponseService { interface ReflectResponseService {
@@ -20,6 +18,7 @@ export interface GrpcMessage {
export function useGrpc(url: string | null) { export function useGrpc(url: string | null) {
const [messages, setMessages] = useState<GrpcMessage[]>([]); const [messages, setMessages] = useState<GrpcMessage[]>([]);
const [activeConnectionId, setActiveConnectionId] = useState<string | null>(null); const [activeConnectionId, setActiveConnectionId] = useState<string | null>(null);
useListenToTauriEvent<string>( useListenToTauriEvent<string>(
'grpc_message', 'grpc_message',
(event) => { (event) => {
@@ -28,8 +27,9 @@ export function useGrpc(url: string | null) {
{ message: event.payload, time: new Date(), type: 'server' }, { message: event.payload, time: new Date(), type: 'server' },
]); ]);
}, },
[], [setMessages],
); );
const unary = useMutation<string, string, { service: string; method: string; message: string }>({ const unary = useMutation<string, string, { service: string; method: string; message: string }>({
mutationKey: ['grpc_unary', url], mutationKey: ['grpc_unary', url],
mutationFn: async ({ service, method, message }) => { mutationFn: async ({ service, method, message }) => {
@@ -94,8 +94,8 @@ export function useGrpc(url: string | null) {
const cancel = useMutation({ const cancel = useMutation({
mutationKey: ['grpc_cancel', url], mutationKey: ['grpc_cancel', url],
mutationFn: async () => { mutationFn: async () => {
await emit('grpc_message_in', 'Cancel');
setActiveConnectionId(null); setActiveConnectionId(null);
await emit('grpc_message_in', 'Cancel');
setMessages((m) => [ setMessages((m) => [
...m, ...m,
{ type: 'info', message: 'Cancelled by client', time: new Date() }, { type: 'info', message: 'Cancelled by client', time: new Date() },