mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 09:48:28 +02:00
Split layouts and things
This commit is contained in:
@@ -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,19 +124,22 @@ 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">
|
||||||
<Editor
|
Variables
|
||||||
format={tryFormatJson}
|
</Separator>
|
||||||
contentType="application/json"
|
<Editor
|
||||||
defaultValue={JSON.stringify(variables, null, 2)}
|
format={tryFormatJson}
|
||||||
heightMode="auto"
|
contentType="application/json"
|
||||||
onChange={handleChangeVariables}
|
defaultValue={JSON.stringify(variables, null, 2)}
|
||||||
placeholder="{}"
|
heightMode="auto"
|
||||||
useTemplating
|
onChange={handleChangeVariables}
|
||||||
autocompleteVariables
|
placeholder="{}"
|
||||||
{...extraEditorProps}
|
useTemplating
|
||||||
/>
|
autocompleteVariables
|
||||||
|
{...extraEditorProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,62 +256,63 @@ 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
|
||||||
<div className="overflow-y-auto">
|
name="grpc_messages2"
|
||||||
{...grpc.messages.map((m) => (
|
minHeightPx={20}
|
||||||
<HStack
|
defaultRatio={0.25}
|
||||||
key={m.time.getTime()}
|
leftSlot={() => (
|
||||||
space={2}
|
<div className="overflow-y-auto">
|
||||||
onClick={() => {
|
{...grpc.messages.map((m, i) => (
|
||||||
if (m === activeMessage) setActiveMessage(null);
|
<HStack
|
||||||
else setActiveMessage(m);
|
key={`${m.time.getTime()}::${m.message}::${i}`}
|
||||||
}}
|
space={2}
|
||||||
alignItems="center"
|
onClick={() => {
|
||||||
className={classNames(
|
if (m === activeMessage) setActiveMessage(null);
|
||||||
'px-2 py-1 font-mono',
|
else setActiveMessage(m);
|
||||||
m === activeMessage && 'bg-highlight',
|
}}
|
||||||
)}
|
alignItems="center"
|
||||||
>
|
className={classNames(
|
||||||
<Icon
|
'px-2 py-1 font-mono',
|
||||||
className={
|
m === activeMessage && 'bg-highlight',
|
||||||
m.type === 'server'
|
)}
|
||||||
? 'text-blue-600'
|
>
|
||||||
: m.type === 'client'
|
<Icon
|
||||||
? 'text-green-600'
|
className={
|
||||||
: 'text-gray-600'
|
m.type === 'server'
|
||||||
}
|
? 'text-blue-600'
|
||||||
icon={
|
: m.type === 'client'
|
||||||
m.type === 'server'
|
? 'text-green-600'
|
||||||
? 'arrowBigDownDash'
|
: 'text-gray-600'
|
||||||
: m.type === 'client'
|
}
|
||||||
? 'arrowBigUpDash'
|
icon={
|
||||||
: 'info'
|
m.type === 'server'
|
||||||
}
|
? 'arrowBigDownDash'
|
||||||
/>
|
: m.type === 'client'
|
||||||
<div className="w-full truncate text-gray-800 text-xs">{m.message}</div>
|
? 'arrowBigUpDash'
|
||||||
<div className="text-gray-600 text-2xs" title={m.time.toISOString()}>
|
: 'info'
|
||||||
{format(m.time, 'HH:mm:ss')}
|
}
|
||||||
|
/>
|
||||||
|
<div className="w-full truncate text-gray-800 text-xs">{m.message}</div>
|
||||||
|
<div className="text-gray-600 text-2xs" title={m.time.toISOString()}>
|
||||||
|
{format(m.time, 'HH:mm:ss')}
|
||||||
|
</div>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
rightSlot={() =>
|
||||||
|
activeMessage && (
|
||||||
|
<div className="grid grid-rows-[auto_minmax(0,1fr)]">
|
||||||
|
<div className="pb-3 px-2">
|
||||||
|
<Separator />
|
||||||
</div>
|
</div>
|
||||||
</HStack>
|
<div className="pl-2 overflow-y-auto">
|
||||||
))}
|
<JsonAttributeTree attrValue={JSON.parse(activeMessage?.message ?? '{}')} />
|
||||||
</div>
|
</div>
|
||||||
<div className={classNames(activeMessage ? 'block' : 'hidden')}>
|
</div>
|
||||||
<div className="pb-1 px-2">
|
)
|
||||||
<Separator />
|
}
|
||||||
</div>
|
/>
|
||||||
<div className="pl-2 pb-1 h-[6rem]">
|
|
||||||
<JsonAttributeTree attrValue={JSON.parse(activeMessage?.message ?? '{}')} />
|
|
||||||
</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>
|
|
||||||
) : resp ? (
|
) : resp ? (
|
||||||
<Editor
|
<Editor
|
||||||
className="bg-gray-50 dark:bg-gray-100"
|
className="bg-gray-50 dark:bg-gray-100"
|
||||||
|
|||||||
@@ -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'} />
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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() },
|
||||||
|
|||||||
Reference in New Issue
Block a user