mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-22 09:29:16 +01:00
A bit better handling of responses
This commit is contained in:
@@ -189,25 +189,29 @@ async fn cmd_grpc_bidi_streaming(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let event_handler = app_handle.listen_global("grpc_message_in", cb);
|
let event_handler =
|
||||||
|
app_handle.listen_global(format!("grpc_client_msg_{}", conn_id).as_str(), cb);
|
||||||
|
|
||||||
let app_handle2 = app_handle.clone();
|
let grpc_listen = {
|
||||||
let grpc_listen = async move {
|
let app_handle = app_handle.clone();
|
||||||
loop {
|
let conn_id = conn_id.clone();
|
||||||
match stream.next().await {
|
async move {
|
||||||
Some(Ok(item)) => {
|
loop {
|
||||||
let item = serde_json::to_string_pretty(&item).unwrap();
|
match stream.next().await {
|
||||||
app_handle2
|
Some(Ok(item)) => {
|
||||||
.emit_all("grpc_message", item)
|
let item = serde_json::to_string_pretty(&item).unwrap();
|
||||||
.expect("Failed to emit");
|
app_handle
|
||||||
}
|
.emit_all(format!("grpc_server_msg_{}", &conn_id).as_str(), item)
|
||||||
Some(Err(e)) => {
|
.expect("Failed to emit");
|
||||||
error!("gRPC stream error: {:?}", e);
|
}
|
||||||
// TODO: Handle error
|
Some(Err(e)) => {
|
||||||
}
|
error!("gRPC stream error: {:?}", e);
|
||||||
None => {
|
// TODO: Handle error
|
||||||
info!("gRPC stream closed by sender");
|
}
|
||||||
break;
|
None => {
|
||||||
|
info!("gRPC stream closed by sender");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,25 +287,29 @@ async fn cmd_grpc_server_streaming(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let event_handler = app_handle.listen_global("grpc_message_in", cb);
|
let event_handler =
|
||||||
|
app_handle.listen_global(format!("grpc_client_msg_{}", conn_id).as_str(), cb);
|
||||||
|
|
||||||
let app_handle2 = app_handle.clone();
|
let grpc_listen = {
|
||||||
let grpc_listen = async move {
|
let app_handle = app_handle.clone();
|
||||||
loop {
|
let conn_id = conn_id.clone();
|
||||||
match stream.next().await {
|
async move {
|
||||||
Some(Ok(item)) => {
|
loop {
|
||||||
let item = serde_json::to_string_pretty(&item).unwrap();
|
match stream.next().await {
|
||||||
app_handle2
|
Some(Ok(item)) => {
|
||||||
.emit_all("grpc_message", item)
|
let item = serde_json::to_string_pretty(&item).unwrap();
|
||||||
.expect("Failed to emit");
|
app_handle
|
||||||
}
|
.emit_all(format!("grpc_server_msg_{}", &conn_id).as_str(), item)
|
||||||
Some(Err(e)) => {
|
.expect("Failed to emit");
|
||||||
error!("gRPC stream error: {:?}", e);
|
}
|
||||||
// TODO: Handle error
|
Some(Err(e)) => {
|
||||||
}
|
error!("gRPC stream error: {:?}", e);
|
||||||
None => {
|
// TODO: Handle error
|
||||||
info!("gRPC stream closed by sender");
|
}
|
||||||
break;
|
None => {
|
||||||
|
info!("gRPC stream closed by sender");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import classNames from 'classnames';
|
|||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import type { CSSProperties, FormEvent } from 'react';
|
import type { CSSProperties, FormEvent } from 'react';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||||
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';
|
||||||
@@ -26,26 +27,31 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function GrpcConnectionLayout({ style }: Props) {
|
export function GrpcConnectionLayout({ style }: Props) {
|
||||||
const url = useKeyValue<string>({ namespace: 'debug', key: 'grpc_url', defaultValue: '' });
|
const activeRequestId = useActiveRequestId();
|
||||||
|
const url = useKeyValue<string>({
|
||||||
|
namespace: 'debug',
|
||||||
|
key: ['grpc_url', activeRequestId ?? ''],
|
||||||
|
defaultValue: '',
|
||||||
|
});
|
||||||
const alert = useAlert();
|
const alert = useAlert();
|
||||||
const service = useKeyValue<string | null>({
|
const service = useKeyValue<string | null>({
|
||||||
namespace: 'debug',
|
namespace: 'debug',
|
||||||
key: 'grpc_service',
|
key: ['grpc_service', activeRequestId ?? ''],
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
});
|
});
|
||||||
const method = useKeyValue<string | null>({
|
const method = useKeyValue<string | null>({
|
||||||
namespace: 'debug',
|
namespace: 'debug',
|
||||||
key: 'grpc_method',
|
key: ['grpc_method', activeRequestId ?? ''],
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
});
|
});
|
||||||
const message = useKeyValue<string>({
|
const message = useKeyValue<string>({
|
||||||
namespace: 'debug',
|
namespace: 'debug',
|
||||||
key: 'grpc_message',
|
key: ['grpc_message', activeRequestId ?? ''],
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
});
|
});
|
||||||
const [activeMessage, setActiveMessage] = useState<GrpcMessage | null>(null);
|
const [activeMessage, setActiveMessage] = useState<GrpcMessage | null>(null);
|
||||||
const [resp, setResp] = useState<string>('');
|
const [resp, setResp] = useState<string>('');
|
||||||
const grpc = useGrpc(url.value ?? null);
|
const grpc = useGrpc(url.value ?? null, activeRequestId);
|
||||||
|
|
||||||
const activeMethod = useMemo(() => {
|
const activeMethod = useMemo(() => {
|
||||||
if (grpc.schema == null) return null;
|
if (grpc.schema == null) return null;
|
||||||
@@ -105,6 +111,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log('GrpcConnectionLayout');
|
||||||
if (grpc.schema == null) return;
|
if (grpc.schema == null) return;
|
||||||
const s = grpc.schema.find((s) => s.name === service.value);
|
const s = grpc.schema.find((s) => s.name === service.value);
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
@@ -167,11 +174,10 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<UrlBar
|
<UrlBar
|
||||||
id="foo"
|
|
||||||
url={url.value ?? ''}
|
url={url.value ?? ''}
|
||||||
method={null}
|
method={null}
|
||||||
submitIcon={null}
|
submitIcon={null}
|
||||||
forceUpdateKey="to-do"
|
forceUpdateKey={activeRequestId ?? ''}
|
||||||
placeholder="localhost:50051"
|
placeholder="localhost:50051"
|
||||||
onSubmit={handleConnect}
|
onSubmit={handleConnect}
|
||||||
isLoading={grpc.unary.isLoading}
|
isLoading={grpc.unary.isLoading}
|
||||||
@@ -231,7 +237,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
{!service.isLoading && !method.isLoading && (
|
{!service.isLoading && !method.isLoading && (
|
||||||
<GrpcEditor
|
<GrpcEditor
|
||||||
forceUpdateKey={[service, method].join('::')}
|
forceUpdateKey={activeRequestId ?? ''}
|
||||||
url={url.value ?? ''}
|
url={url.value ?? ''}
|
||||||
defaultValue={message.value}
|
defaultValue={message.value}
|
||||||
onChange={message.set}
|
onChange={message.set}
|
||||||
@@ -255,16 +261,16 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
<Banner color="danger" className="m-2">
|
<Banner color="danger" className="m-2">
|
||||||
{grpc.unary.error}
|
{grpc.unary.error}
|
||||||
</Banner>
|
</Banner>
|
||||||
) : grpc.messages.length > 0 ? (
|
) : (grpc.messages.value ?? []).length > 0 ? (
|
||||||
<SplitLayout
|
<SplitLayout
|
||||||
name="grpc_messages2"
|
name="grpc_messages2"
|
||||||
minHeightPx={20}
|
minHeightPx={20}
|
||||||
defaultRatio={0.25}
|
defaultRatio={0.25}
|
||||||
leftSlot={() => (
|
leftSlot={() => (
|
||||||
<div className="overflow-y-auto">
|
<div className="overflow-y-auto">
|
||||||
{...grpc.messages.map((m, i) => (
|
{...(grpc.messages.value ?? []).map((m, i) => (
|
||||||
<HStack
|
<HStack
|
||||||
key={`${m.time.getTime()}::${m.message}::${i}`}
|
key={`${m.timestamp}::${m.message}::${i}`}
|
||||||
space={2}
|
space={2}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (m === activeMessage) setActiveMessage(null);
|
if (m === activeMessage) setActiveMessage(null);
|
||||||
@@ -292,25 +298,29 @@ export function GrpcConnectionLayout({ style }: Props) {
|
|||||||
: 'info'
|
: 'info'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className="w-full truncate text-gray-800 text-xs">{m.message}</div>
|
<div className="w-full truncate text-gray-800 text-2xs">{m.message}</div>
|
||||||
<div className="text-gray-600 text-2xs" title={m.time.toISOString()}>
|
<div className="text-gray-600 text-2xs">
|
||||||
{format(m.time, 'HH:mm:ss')}
|
{format(m.timestamp, 'HH:mm:ss')}
|
||||||
</div>
|
</div>
|
||||||
</HStack>
|
</HStack>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
rightSlot={() =>
|
rightSlot={
|
||||||
activeMessage && (
|
!activeMessage
|
||||||
<div className="grid grid-rows-[auto_minmax(0,1fr)]">
|
? null
|
||||||
<div className="pb-3 px-2">
|
: () => (
|
||||||
<Separator />
|
<div className="grid grid-rows-[auto_minmax(0,1fr)]">
|
||||||
</div>
|
<div className="pb-3 px-2">
|
||||||
<div className="pl-2 overflow-y-auto">
|
<Separator />
|
||||||
<JsonAttributeTree attrValue={JSON.parse(activeMessage?.message ?? '{}')} />
|
</div>
|
||||||
</div>
|
<div className="pl-2 overflow-y-auto">
|
||||||
</div>
|
<JsonAttributeTree
|
||||||
)
|
attrValue={JSON.parse(activeMessage?.message ?? '{}')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : resp ? (
|
) : resp ? (
|
||||||
|
|||||||
@@ -209,8 +209,6 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
{activeRequest && (
|
{activeRequest && (
|
||||||
<>
|
<>
|
||||||
<UrlBar
|
<UrlBar
|
||||||
key={activeRequest.id} // Force-reset the url bar when the active request changes
|
|
||||||
id={activeRequest.id}
|
|
||||||
url={activeRequest.url}
|
url={activeRequest.url}
|
||||||
method={activeRequest.method}
|
method={activeRequest.method}
|
||||||
placeholder="https://example.com"
|
placeholder="https://example.com"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { IconButton } from './core/IconButton';
|
|||||||
import { Input } from './core/Input';
|
import { Input } from './core/Input';
|
||||||
import { RequestMethodDropdown } from './RequestMethodDropdown';
|
import { RequestMethodDropdown } from './RequestMethodDropdown';
|
||||||
|
|
||||||
type Props = Pick<HttpRequest, 'id' | 'url'> & {
|
type Props = Pick<HttpRequest, 'url'> & {
|
||||||
className?: string;
|
className?: string;
|
||||||
method: HttpRequest['method'] | null;
|
method: HttpRequest['method'] | null;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface SlotProps {
|
|||||||
interface Props {
|
interface Props {
|
||||||
name: string;
|
name: string;
|
||||||
leftSlot: (props: SlotProps) => ReactNode;
|
leftSlot: (props: SlotProps) => ReactNode;
|
||||||
rightSlot: (props: SlotProps) => ReactNode;
|
rightSlot: null | ((props: SlotProps) => ReactNode);
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
defaultRatio?: number;
|
defaultRatio?: number;
|
||||||
@@ -48,33 +48,37 @@ export function SplitLayout({
|
|||||||
`${name}_height::${useActiveWorkspaceId()}`,
|
`${name}_height::${useActiveWorkspaceId()}`,
|
||||||
);
|
);
|
||||||
const width = widthRaw ?? defaultRatio;
|
const width = widthRaw ?? defaultRatio;
|
||||||
const height = heightRaw ?? defaultRatio;
|
let 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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!rightSlot) {
|
||||||
|
height = 0;
|
||||||
|
minHeightPx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
useResizeObserver(containerRef.current, ({ contentRect }) => {
|
useResizeObserver(containerRef.current, ({ contentRect }) => {
|
||||||
setVertical(contentRect.width < STACK_VERTICAL_WIDTH);
|
setVertical(contentRect.width < STACK_VERTICAL_WIDTH);
|
||||||
});
|
});
|
||||||
|
|
||||||
const styles = useMemo<CSSProperties>(
|
const styles = useMemo<CSSProperties>(() => {
|
||||||
() => ({
|
return {
|
||||||
...style,
|
...style,
|
||||||
gridTemplate: vertical
|
gridTemplate: vertical
|
||||||
? `
|
? `
|
||||||
' ${areaL.gridArea}' minmax(0,${1 - height}fr)
|
' ${areaL.gridArea}' minmax(0,${1 - height}fr)
|
||||||
' ${areaD.gridArea}' 0
|
' ${areaD.gridArea}' 0
|
||||||
' ${areaR.gridArea}' minmax(${minHeightPx}px,${height}fr)
|
' ${areaR.gridArea}' minmax(${minHeightPx}px,${height}fr)
|
||||||
/ 1fr
|
/ 1fr
|
||||||
`
|
`
|
||||||
: `
|
: `
|
||||||
' ${areaL.gridArea} ${areaD.gridArea} ${areaR.gridArea}' minmax(0,1fr)
|
' ${areaL.gridArea} ${areaD.gridArea} ${areaR.gridArea}' minmax(0,1fr)
|
||||||
/ ${1 - width}fr 0 ${width}fr
|
/ ${1 - width}fr 0 ${width}fr
|
||||||
`,
|
`,
|
||||||
}),
|
};
|
||||||
[vertical, width, height, style],
|
}, [style, vertical, height, minHeightPx, width]);
|
||||||
);
|
|
||||||
|
|
||||||
const unsub = () => {
|
const unsub = () => {
|
||||||
if (moveState.current !== null) {
|
if (moveState.current !== null) {
|
||||||
@@ -142,17 +146,21 @@ export function SplitLayout({
|
|||||||
return (
|
return (
|
||||||
<div ref={containerRef} className={classNames(className, 'grid w-full h-full')} 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
|
{rightSlot && (
|
||||||
style={areaD}
|
<>
|
||||||
isResizing={isResizing}
|
<ResizeHandle
|
||||||
barClassName={'bg-red-300'}
|
style={areaD}
|
||||||
className={classNames(vertical ? 'translate-y-0.5' : 'translate-x-0.5')}
|
isResizing={isResizing}
|
||||||
onResizeStart={handleResizeStart}
|
barClassName={'bg-red-300'}
|
||||||
onReset={handleReset}
|
className={classNames(vertical ? 'translate-y-0.5' : 'translate-x-0.5')}
|
||||||
side={vertical ? 'top' : 'left'}
|
onResizeStart={handleResizeStart}
|
||||||
justify="center"
|
onReset={handleReset}
|
||||||
/>
|
side={vertical ? 'top' : 'left'}
|
||||||
{rightSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
|
justify="center"
|
||||||
|
/>
|
||||||
|
{rightSlot({ style: areaR, orientation: vertical ? 'vertical' : 'horizontal' })}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
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 { emit } from '@tauri-apps/api/event';
|
import type { UnlistenFn } from '@tauri-apps/api/event';
|
||||||
import { useState } from 'react';
|
import { emit, listen } from '@tauri-apps/api/event';
|
||||||
import { useListenToTauriEvent } from './useListenToTauriEvent';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { useKeyValue } from './useKeyValue';
|
||||||
|
|
||||||
interface ReflectResponseService {
|
interface ReflectResponseService {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -11,24 +12,23 @@ interface ReflectResponseService {
|
|||||||
|
|
||||||
export interface GrpcMessage {
|
export interface GrpcMessage {
|
||||||
message: string;
|
message: string;
|
||||||
time: Date;
|
timestamp: string;
|
||||||
type: 'server' | 'client' | 'info';
|
type: 'server' | 'client' | 'info';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGrpc(url: string | null) {
|
export function useGrpc(url: string | null, requestId: string | null) {
|
||||||
const [messages, setMessages] = useState<GrpcMessage[]>([]);
|
const messages = useKeyValue<GrpcMessage[]>({
|
||||||
|
namespace: 'debug',
|
||||||
|
key: ['grpc_msgs', requestId ?? 'n/a'],
|
||||||
|
defaultValue: [],
|
||||||
|
});
|
||||||
const [activeConnectionId, setActiveConnectionId] = useState<string | null>(null);
|
const [activeConnectionId, setActiveConnectionId] = useState<string | null>(null);
|
||||||
|
const unlisten = useRef<UnlistenFn | null>(null);
|
||||||
|
|
||||||
useListenToTauriEvent<string>(
|
useEffect(() => {
|
||||||
'grpc_message',
|
setActiveConnectionId(null);
|
||||||
(event) => {
|
unlisten.current?.();
|
||||||
setMessages((prev) => [
|
}, [requestId]);
|
||||||
...prev,
|
|
||||||
{ 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],
|
||||||
@@ -51,8 +51,12 @@ 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([
|
await messages.set([
|
||||||
{ type: 'client', message: JSON.stringify(JSON.parse(message)), time: new Date() },
|
{
|
||||||
|
type: 'client',
|
||||||
|
message: JSON.stringify(JSON.parse(message)),
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
const id: string = await invoke('cmd_grpc_server_streaming', {
|
const id: string = await invoke('cmd_grpc_server_streaming', {
|
||||||
endpoint: url,
|
endpoint: url,
|
||||||
@@ -60,6 +64,12 @@ export function useGrpc(url: string | null) {
|
|||||||
method,
|
method,
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
|
unlisten.current = await listen(`grpc_server_msg_${id}`, async (event) => {
|
||||||
|
await messages.set((prev) => [
|
||||||
|
...prev,
|
||||||
|
{ message: event.payload as string, timestamp: new Date().toISOString(), type: 'server' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
setActiveConnectionId(id);
|
setActiveConnectionId(id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -78,16 +88,27 @@ export function useGrpc(url: string | null) {
|
|||||||
method,
|
method,
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
setMessages([{ type: 'info', message: `Started connection ${id}`, time: new Date() }]);
|
messages.set([
|
||||||
|
{ type: 'info', message: `Started connection ${id}`, timestamp: new Date().toISOString() },
|
||||||
|
]);
|
||||||
setActiveConnectionId(id);
|
setActiveConnectionId(id);
|
||||||
|
unlisten.current = await listen(`grpc_server_msg_${id}`, (event) => {
|
||||||
|
messages.set((prev) => [
|
||||||
|
...prev,
|
||||||
|
{ message: event.payload as string, timestamp: new Date().toISOString(), type: 'server' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const send = useMutation({
|
const send = useMutation({
|
||||||
mutationKey: ['grpc_send', url],
|
mutationKey: ['grpc_send', url],
|
||||||
mutationFn: async ({ message }: { message: string }) => {
|
mutationFn: async ({ message }: { message: string }) => {
|
||||||
await emit('grpc_message_in', { Message: message });
|
if (activeConnectionId == null) throw new Error('No active connection');
|
||||||
setMessages((m) => [...m, { type: 'client', message, time: new Date() }]);
|
await messages.set((m) => {
|
||||||
|
return [...m, { type: 'client', message, timestamp: new Date().toISOString() }];
|
||||||
|
});
|
||||||
|
await emit(`grpc_client_msg_${activeConnectionId}`, { Message: message });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,10 +116,11 @@ export function useGrpc(url: string | null) {
|
|||||||
mutationKey: ['grpc_cancel', url],
|
mutationKey: ['grpc_cancel', url],
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
setActiveConnectionId(null);
|
setActiveConnectionId(null);
|
||||||
|
unlisten.current?.();
|
||||||
await emit('grpc_message_in', 'Cancel');
|
await emit('grpc_message_in', 'Cancel');
|
||||||
setMessages((m) => [
|
await messages.set((m) => [
|
||||||
...m,
|
...m,
|
||||||
{ type: 'info', message: 'Cancelled by client', time: new Date() },
|
{ type: 'info', message: 'Cancelled by client', timestamp: new Date().toISOString() },
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,19 +37,21 @@ export function useKeyValue<T extends Object | null>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const set = useCallback(
|
const set = useCallback(
|
||||||
(value: ((v: T) => T) | T) => {
|
async (value: ((v: T) => T) | T) => {
|
||||||
if (typeof value === 'function') {
|
if (typeof value === 'function') {
|
||||||
getKeyValue({ namespace, key, fallback: defaultValue }).then((kv) => {
|
await getKeyValue({ namespace, key, fallback: defaultValue }).then((kv) => {
|
||||||
mutate.mutate(value(kv));
|
const newV = value(kv);
|
||||||
|
if (newV === kv) return;
|
||||||
|
return mutate.mutateAsync(newV);
|
||||||
});
|
});
|
||||||
} else {
|
} else if (value !== query.data) {
|
||||||
mutate.mutate(value);
|
await mutate.mutateAsync(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[defaultValue, key, mutate, namespace],
|
[defaultValue, key, mutate, namespace, query.data],
|
||||||
);
|
);
|
||||||
|
|
||||||
const reset = useCallback(() => mutate.mutate(defaultValue), [mutate, defaultValue]);
|
const reset = useCallback(async () => mutate.mutateAsync(defaultValue), [mutate, defaultValue]);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user