mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-23 09:51:10 +01:00
Url parameters for websocket URLs
This commit is contained in:
20
src-web/commands/duplicateWebsocketRequest.ts
Normal file
20
src-web/commands/duplicateWebsocketRequest.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import { duplicateWebsocketRequest as cmdDuplicateWebsocketRequest } from '@yaakapp-internal/ws';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import { router } from '../lib/router';
|
||||
|
||||
export const duplicateWebsocketRequest = createFastMutation({
|
||||
mutationKey: ['delete_websocket_connection'],
|
||||
mutationFn: async function (request: WebsocketRequest) {
|
||||
return cmdDuplicateWebsocketRequest(request.id);
|
||||
},
|
||||
onSuccess: async (request) => {
|
||||
trackEvent('websocket_request', 'duplicate');
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: request.workspaceId },
|
||||
search: (prev) => ({ ...prev, request_id: request.id }),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -81,13 +81,15 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
||||
/>
|
||||
</div>
|
||||
</HStack>
|
||||
{activeConnection.error && (
|
||||
<Banner color="danger" className="m-3">
|
||||
{activeConnection.error}
|
||||
</Banner>
|
||||
)}
|
||||
<AutoScroller
|
||||
data={events}
|
||||
header={
|
||||
activeConnection.error && (
|
||||
<Banner color="danger" className="m-3">
|
||||
{activeConnection.error}
|
||||
</Banner>
|
||||
)
|
||||
}
|
||||
render={(event) => (
|
||||
<EventRow
|
||||
key={event.id}
|
||||
|
||||
@@ -85,13 +85,15 @@ export function WebsocketResponsePane({ activeRequest }: Props) {
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
{activeConnection.error && (
|
||||
<Banner color="danger" className="m-3">
|
||||
{activeConnection.error}
|
||||
</Banner>
|
||||
)}
|
||||
<AutoScroller
|
||||
data={events}
|
||||
header={
|
||||
activeConnection.error && (
|
||||
<Banner color="danger" className="m-3">
|
||||
{activeConnection.error}
|
||||
</Banner>
|
||||
)
|
||||
}
|
||||
render={(event) => (
|
||||
<EventRow
|
||||
key={event.id}
|
||||
|
||||
@@ -2,6 +2,7 @@ import classNames from 'classnames';
|
||||
import { motion } from 'framer-motion';
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import {duplicateWebsocketRequest} from "../commands/duplicateWebsocketRequest";
|
||||
import {
|
||||
useEnsureActiveCookieJar,
|
||||
useSubscribeActiveCookieJarId,
|
||||
@@ -253,10 +254,17 @@ function useGlobalWorkspaceHooks() {
|
||||
|
||||
useHotKey('http_request.duplicate', async () => {
|
||||
const activeRequest = getActiveRequest();
|
||||
if (activeRequest?.model === 'http_request') {
|
||||
if (activeRequest == null) {
|
||||
// Nothing
|
||||
} else if (activeRequest.model === 'http_request') {
|
||||
await duplicateHttpRequest.mutateAsync();
|
||||
} else {
|
||||
} else if (activeRequest.model === 'grpc_request') {
|
||||
await duplicateGrpcRequest.mutateAsync();
|
||||
} else if (activeRequest.model === 'websocket_request') {
|
||||
await duplicateWebsocketRequest.mutateAsync(activeRequest);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
throw new Error('Failed to duplicate invalid request model: ' + (activeRequest as any).model);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import type { ReactElement, UIEvent } from 'react';
|
||||
import type { ReactElement, ReactNode, UIEvent } from 'react';
|
||||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { IconButton } from './IconButton';
|
||||
|
||||
interface Props<T> {
|
||||
data: T[];
|
||||
render: (item: T, index: number) => ReactElement<HTMLElement>;
|
||||
header?: ReactNode;
|
||||
}
|
||||
|
||||
export function AutoScroller<T>({ data, render }: Props<T>) {
|
||||
export function AutoScroller<T>({ data, render, header }: Props<T>) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [autoScroll, setAutoScroll] = useState<boolean>(true);
|
||||
|
||||
@@ -45,7 +46,7 @@ export function AutoScroller<T>({ data, render }: Props<T>) {
|
||||
}, [autoScroll, data.length]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full relative">
|
||||
<div className="h-full w-full relative grid grid-rows-[auto_minmax(0,1fr)]">
|
||||
{!autoScroll && (
|
||||
<div className="absolute bottom-0 right-0 m-2">
|
||||
<IconButton
|
||||
@@ -59,6 +60,7 @@ export function AutoScroller<T>({ data, render }: Props<T>) {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{header ?? <span aria-hidden/>}
|
||||
<div ref={containerRef} className="h-full w-full overflow-y-auto" onScroll={handleScroll}>
|
||||
<div
|
||||
style={{
|
||||
@@ -75,7 +77,7 @@ export function AutoScroller<T>({ data, render }: Props<T>) {
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
// height: `${virtualItem.size}px`,
|
||||
height: `${virtualItem.size * 1000}px`,
|
||||
transform: `translateY(${virtualItem.start}px)`,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -9,19 +9,17 @@ interface Props {
|
||||
|
||||
export function Banner({ children, className, color }: Props) {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
`x-theme-banner--${color}`,
|
||||
'whitespace-pre-wrap',
|
||||
'border border-dashed border-border bg-surface',
|
||||
'px-3 py-2 rounded select-auto',
|
||||
'overflow-x-auto text-text',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
`x-theme-banner--${color}`,
|
||||
'whitespace-pre-wrap',
|
||||
'border border-dashed border-border bg-surface',
|
||||
'px-3 py-2 rounded select-auto',
|
||||
'overflow-auto text-text',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useFormatText } from '../../hooks/useFormatText';
|
||||
import { useResponseBodyEventSource } from '../../hooks/useResponseBodyEventSource';
|
||||
import { isJSON } from '../../lib/contentType';
|
||||
import { AutoScroller } from '../core/AutoScroller';
|
||||
import { Banner } from '../core/Banner';
|
||||
import { Button } from '../core/Button';
|
||||
import type { EditorProps } from '../core/Editor/Editor';
|
||||
import { Editor } from '../core/Editor/Editor';
|
||||
@@ -51,10 +52,26 @@ function ActualEventStreamViewer({ response }: Props) {
|
||||
defaultRatio={0.4}
|
||||
minHeightPx={20}
|
||||
firstSlot={() => (
|
||||
<EventStreamEvents
|
||||
events={events.data ?? []}
|
||||
activeEventIndex={activeEventIndex}
|
||||
setActiveEventIndex={setActiveEventIndex}
|
||||
<AutoScroller
|
||||
data={events.data ?? []}
|
||||
header={
|
||||
events.error && (
|
||||
<Banner color="danger" className="m-3">
|
||||
{String(events.error)}
|
||||
</Banner>
|
||||
)
|
||||
}
|
||||
render={(event, i) => (
|
||||
<EventRow
|
||||
event={event}
|
||||
isActive={i === activeEventIndex}
|
||||
index={i}
|
||||
onClick={() => {
|
||||
if (i === activeEventIndex) setActiveEventIndex(null);
|
||||
else setActiveEventIndex(i);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
secondSlot={
|
||||
@@ -112,34 +129,7 @@ function FormattedEditor({ text, language }: { text: string; language: EditorPro
|
||||
return <Editor readOnly defaultValue={formatted.data} language={language} stateKey={null} />;
|
||||
}
|
||||
|
||||
function EventStreamEvents({
|
||||
events,
|
||||
activeEventIndex,
|
||||
setActiveEventIndex,
|
||||
}: {
|
||||
events: ServerSentEvent[];
|
||||
activeEventIndex: number | null;
|
||||
setActiveEventIndex: (eventId: number | null) => void;
|
||||
}) {
|
||||
return (
|
||||
<AutoScroller
|
||||
data={events}
|
||||
render={(event, i) => (
|
||||
<EventStreamEvent
|
||||
event={event}
|
||||
isActive={i === activeEventIndex}
|
||||
index={i}
|
||||
onClick={() => {
|
||||
if (i === activeEventIndex) setActiveEventIndex(null);
|
||||
else setActiveEventIndex(i);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function EventStreamEvent({
|
||||
function EventRow({
|
||||
onClick,
|
||||
isActive,
|
||||
event,
|
||||
|
||||
Reference in New Issue
Block a user