mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 01:28:35 +02:00
Even better styles
This commit is contained in:
@@ -1,17 +1,16 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { m } from 'framer-motion';
|
|
||||||
import type { CSSProperties, FormEvent } from 'react';
|
import type { CSSProperties, FormEvent } from 'react';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useAlert } from '../hooks/useAlert';
|
import { useAlert } from '../hooks/useAlert';
|
||||||
import type { GrpcMessage } from '../hooks/useGrpc';
|
import type { GrpcMessage } from '../hooks/useGrpc';
|
||||||
import { useGrpc } from '../hooks/useGrpc';
|
import { useGrpc } from '../hooks/useGrpc';
|
||||||
import { useKeyValue } from '../hooks/useKeyValue';
|
import { useKeyValue } from '../hooks/useKeyValue';
|
||||||
import { tryFormatJson } from '../lib/formatters';
|
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { Editor } from './core/Editor';
|
import { Editor } from './core/Editor';
|
||||||
import { HotKeyList } from './core/HotKeyList';
|
import { HotKeyList } from './core/HotKeyList';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
|
import { IconButton } from './core/IconButton';
|
||||||
import { JsonAttributeTree } from './core/JsonAttributeTree';
|
import { JsonAttributeTree } from './core/JsonAttributeTree';
|
||||||
import { Select } from './core/Select';
|
import { Select } from './core/Select';
|
||||||
import { Separator } from './core/Separator';
|
import { Separator } from './core/Separator';
|
||||||
@@ -144,12 +143,30 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
id="foo"
|
id="foo"
|
||||||
url={url.value ?? ''}
|
url={url.value ?? ''}
|
||||||
method={null}
|
method={null}
|
||||||
|
submitIcon={null}
|
||||||
forceUpdateKey="to-do"
|
forceUpdateKey="to-do"
|
||||||
placeholder="localhost:50051"
|
placeholder="localhost:50051"
|
||||||
onSubmit={handleConnect}
|
onSubmit={handleConnect}
|
||||||
isLoading={grpc.unary.isLoading}
|
isLoading={grpc.unary.isLoading}
|
||||||
onUrlChange={url.set}
|
onUrlChange={url.set}
|
||||||
submitIcon={
|
/>
|
||||||
|
<Select
|
||||||
|
hideLabel
|
||||||
|
name="service"
|
||||||
|
label="Service"
|
||||||
|
className="text-gray-800"
|
||||||
|
size="sm"
|
||||||
|
value={select.value}
|
||||||
|
onChange={handleChangeService}
|
||||||
|
options={select.options}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
className="border border-highlight"
|
||||||
|
size="sm"
|
||||||
|
title="ofo"
|
||||||
|
hotkeyAction="request.send"
|
||||||
|
onClick={handleConnect}
|
||||||
|
icon={
|
||||||
!activeMethod?.clientStreaming && activeMethod?.serverStreaming
|
!activeMethod?.clientStreaming && activeMethod?.serverStreaming
|
||||||
? 'arrowDownToDot'
|
? 'arrowDownToDot'
|
||||||
: activeMethod?.clientStreaming && !activeMethod?.serverStreaming
|
: activeMethod?.clientStreaming && !activeMethod?.serverStreaming
|
||||||
@@ -159,15 +176,6 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
: 'sendHorizontal'
|
: 'sendHorizontal'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Select
|
|
||||||
hideLabel
|
|
||||||
name="service"
|
|
||||||
label="Service"
|
|
||||||
size="sm"
|
|
||||||
value={select.value}
|
|
||||||
onChange={handleChangeService}
|
|
||||||
options={select.options}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<GrpcEditor
|
<GrpcEditor
|
||||||
forceUpdateKey={[service, method].join('::')}
|
forceUpdateKey={[service, method].join('::')}
|
||||||
@@ -225,10 +233,10 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
activeMessage ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-[100%]',
|
activeMessage ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-[100%]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="pb-2 px-2">
|
<div className="pb-1 px-2">
|
||||||
<Separator />
|
<Separator />
|
||||||
</div>
|
</div>
|
||||||
<div className="pl-2">
|
<div className="pl-2 pb-1">
|
||||||
<JsonAttributeTree
|
<JsonAttributeTree
|
||||||
depth={0}
|
depth={0}
|
||||||
attrValue={JSON.parse(activeMessage?.message ?? '{}')}
|
attrValue={JSON.parse(activeMessage?.message ?? '{}')}
|
||||||
|
|||||||
@@ -180,9 +180,9 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
<WebPageViewer response={activeResponse} />
|
<WebPageViewer response={activeResponse} />
|
||||||
) : contentType?.match(/csv|tab-separated/) ? (
|
) : contentType?.match(/csv|tab-separated/) ? (
|
||||||
<CsvViewer className="pb-2" response={activeResponse} />
|
<CsvViewer className="pb-2" response={activeResponse} />
|
||||||
|
) : contentType?.startsWith('application/json') ? (
|
||||||
|
<JsonViewer response={activeResponse} />
|
||||||
) : (
|
) : (
|
||||||
// ) : contentType?.startsWith('application/json') ? (
|
|
||||||
// <JsonViewer response={activeResponse} />
|
|
||||||
<TextViewer response={activeResponse} pretty={viewMode === 'pretty'} />
|
<TextViewer response={activeResponse} pretty={viewMode === 'pretty'} />
|
||||||
)}
|
)}
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type Props = Pick<HttpRequest, 'id' | 'url'> & {
|
|||||||
placeholder: string;
|
placeholder: string;
|
||||||
onSubmit: (e: FormEvent) => void;
|
onSubmit: (e: FormEvent) => void;
|
||||||
onUrlChange: (url: string) => void;
|
onUrlChange: (url: string) => void;
|
||||||
submitIcon?: IconProps['icon'];
|
submitIcon?: IconProps['icon'] | null;
|
||||||
onMethodChange?: (method: string) => void;
|
onMethodChange?: (method: string) => void;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
forceUpdateKey: string;
|
forceUpdateKey: string;
|
||||||
@@ -74,16 +74,18 @@ export const UrlBar = memo(function UrlBar({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
rightSlot={
|
rightSlot={
|
||||||
<IconButton
|
submitIcon !== null && (
|
||||||
size="xs"
|
<IconButton
|
||||||
iconSize="md"
|
size="xs"
|
||||||
title="Send Request"
|
iconSize="md"
|
||||||
type="submit"
|
title="Send Request"
|
||||||
className="w-8 mr-0.5 my-0.5"
|
type="submit"
|
||||||
icon={isLoading ? 'update' : submitIcon}
|
className="w-8 mr-0.5 my-0.5"
|
||||||
spin={isLoading}
|
icon={isLoading ? 'update' : submitIcon}
|
||||||
hotkeyAction="request.send"
|
spin={isLoading}
|
||||||
/>
|
hotkeyAction="request.send"
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
|
|||||||
))
|
))
|
||||||
: null,
|
: null,
|
||||||
isExpandable: true,
|
isExpandable: true,
|
||||||
label: isExpanded ? undefined : `{⋯}`,
|
label: isExpanded ? '{ }' : `{⋯}`,
|
||||||
labelClassName: 'text-gray-500',
|
labelClassName: 'text-gray-600',
|
||||||
};
|
};
|
||||||
} else if (jsonType === '[object Array]') {
|
} else if (jsonType === '[object Array]') {
|
||||||
return {
|
return {
|
||||||
@@ -54,8 +54,8 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
|
|||||||
))
|
))
|
||||||
: null,
|
: null,
|
||||||
isExpandable: true,
|
isExpandable: true,
|
||||||
label: isExpanded ? undefined : `[⋯]`,
|
label: isExpanded ? '[ ]' : `[⋯]`,
|
||||||
labelClassName: 'text-gray-500',
|
labelClassName: 'text-gray-600',
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@@ -72,25 +72,38 @@ export const JsonAttributeTree = ({ depth = 0, attrKey, attrValue, attrKeyJsonPa
|
|||||||
}
|
}
|
||||||
}, [attrValue, attrKeyJsonPath, isExpanded, depth]);
|
}, [attrValue, attrKeyJsonPath, isExpanded, depth]);
|
||||||
|
|
||||||
|
const labelEl = (
|
||||||
|
<span className={classNames(labelClassName, 'select-text group-hover:text-gray-800')}>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className={classNames(depth === 0 && '-ml-4', 'font-mono text-xs')}>
|
<div className={classNames(/*depth === 0 && '-ml-4',*/ 'font-mono text-xs')}>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{depth === 0 ? null : isExpandable ? (
|
{isExpandable ? (
|
||||||
<button className="relative flex items-center pl-4" onClick={toggleExpanded}>
|
<button className="group relative flex items-center pl-4" onClick={toggleExpanded}>
|
||||||
<Icon
|
<Icon
|
||||||
className={classNames(
|
|
||||||
'left-0 absolute transition-transform text-gray-500 flex gap-1 items-center',
|
|
||||||
isExpanded ? 'rotate-90' : '',
|
|
||||||
)}
|
|
||||||
size="xs"
|
size="xs"
|
||||||
icon="chevronRight"
|
icon="chevronRight"
|
||||||
|
className={classNames(
|
||||||
|
'left-0 absolute transition-transform text-gray-600 flex items-center',
|
||||||
|
'group-hover:text-gray-900',
|
||||||
|
isExpanded ? 'rotate-90' : '',
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<span className="text-violet-600 mr-1.5 whitespace-nowrap">{attrKey}:</span>
|
<span className="text-violet-600 mr-1.5 whitespace-nowrap">
|
||||||
|
{attrKey === undefined ? '$' : attrKey}:
|
||||||
|
</span>
|
||||||
|
{labelEl}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-violet-600 mr-1.5 pl-4 whitespace-nowrap">{attrKey}:</span>
|
<>
|
||||||
|
<span className="text-violet-600 mr-1.5 pl-4 whitespace-nowrap select-text">
|
||||||
|
{attrKey}:
|
||||||
|
</span>
|
||||||
|
{labelEl}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<span className={classNames(labelClassName, 'select-text')}>{label}</span>
|
|
||||||
</div>
|
</div>
|
||||||
{children && <div className="ml-4 whitespace-nowrap">{children}</div>}
|
{children && <div className="ml-4 whitespace-nowrap">{children}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,20 +19,8 @@ export function useGrpc(url: string | null) {
|
|||||||
useListenToTauriEvent<string>(
|
useListenToTauriEvent<string>(
|
||||||
'grpc_message',
|
'grpc_message',
|
||||||
(event) => {
|
(event) => {
|
||||||
console.log('GOT MESSAGE', event);
|
|
||||||
setMessages((prev) => [
|
setMessages((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
|
||||||
message: JSON.stringify({
|
|
||||||
dummy: 'Yo, this is a dummy message',
|
|
||||||
another: 'property',
|
|
||||||
list: [1, 2, 3, 4, 5],
|
|
||||||
null: null,
|
|
||||||
bool: true,
|
|
||||||
}),
|
|
||||||
time: new Date(),
|
|
||||||
isServer: false,
|
|
||||||
},
|
|
||||||
{ message: event.payload, time: new Date(), isServer: true },
|
{ message: event.payload, time: new Date(), isServer: true },
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
@@ -59,6 +47,9 @@ export function useGrpc(url: string | null) {
|
|||||||
mutationKey: ['grpc_server_streaming', url],
|
mutationKey: ['grpc_server_streaming', url],
|
||||||
mutationFn: async ({ service, method, message }) => {
|
mutationFn: async ({ service, method, message }) => {
|
||||||
if (url === null) throw new Error('No URL provided');
|
if (url === null) throw new Error('No URL provided');
|
||||||
|
setMessages([
|
||||||
|
{ isServer: false, message: JSON.stringify(JSON.parse(message)), time: new Date() },
|
||||||
|
]);
|
||||||
return (await invoke('grpc_server_streaming', {
|
return (await invoke('grpc_server_streaming', {
|
||||||
endpoint: url,
|
endpoint: url,
|
||||||
service,
|
service,
|
||||||
|
|||||||
Reference in New Issue
Block a user