import { type GrpcMetadataEntry, type GrpcRequest, patchModel } from '@yaakapp-internal/models'; import classNames from 'classnames'; import type { CSSProperties } from 'react'; import React, { useCallback, useMemo, useRef } from 'react'; import { useContainerSize } from '../hooks/useContainerQuery'; import type { ReflectResponseService } from '../hooks/useGrpc'; import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication'; import { useKeyValue } from '../hooks/useKeyValue'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { resolvedModelName } from '../lib/resolvedModelName'; import { Button } from './core/Button'; import { CountBadge } from './core/CountBadge'; import { Icon } from './core/Icon'; import { IconButton } from './core/IconButton'; import { PairOrBulkEditor } from './core/PairOrBulkEditor'; import { PlainInput } from './core/PlainInput'; import { RadioDropdown } from './core/RadioDropdown'; import { HStack, VStack } from './core/Stacks'; import type { TabItem } from './core/Tabs/Tabs'; import { TabContent, Tabs } from './core/Tabs/Tabs'; import { GrpcEditor } from './GrpcEditor'; import { HttpAuthenticationEditor } from './HttpAuthenticationEditor'; import { MarkdownEditor } from './MarkdownEditor'; import { UrlBar } from './UrlBar'; interface Props { style?: CSSProperties; className?: string; activeRequest: GrpcRequest; protoFiles: string[]; reflectionError?: string; reflectionLoading?: boolean; methodType: | 'unary' | 'client_streaming' | 'server_streaming' | 'streaming' | 'no-schema' | 'no-method'; isStreaming: boolean; onCommit: () => void; onCancel: () => void; onSend: (v: { message: string }) => void; onGo: () => void; services: ReflectResponseService[] | null; } const TAB_MESSAGE = 'message'; const TAB_METADATA = 'metadata'; const TAB_AUTH = 'auth'; const TAB_DESCRIPTION = 'description'; export function GrpcRequestPane({ style, services, methodType, activeRequest, protoFiles, reflectionError, reflectionLoading, isStreaming, onGo, onCommit, onCancel, onSend, }: Props) { const authentication = useHttpAuthenticationSummaries(); const { value: activeTabs, set: setActiveTabs } = useKeyValue>({ namespace: 'no_sync', key: 'grpcRequestActiveTabs', fallback: {}, }); const forceUpdateKey = useRequestUpdateKey(activeRequest.id ?? null); const urlContainerEl = useRef(null); const { width: paneWidth } = useContainerSize(urlContainerEl); const handleChangeUrl = useCallback( (url: string) => patchModel(activeRequest, { url }), [activeRequest], ); const handleChangeMessage = useCallback( (message: string) => patchModel(activeRequest, { message }), [activeRequest], ); const select = useMemo(() => { const options = services?.flatMap((s) => s.methods.map((m) => ({ label: `${s.name.split('.').pop() ?? s.name}/${m.name}`, value: `${s.name}/${m.name}`, })), ) ?? []; const value = `${activeRequest?.service ?? ''}/${activeRequest?.method ?? ''}`; return { value, options }; }, [activeRequest?.method, activeRequest?.service, services]); const handleChangeService = useCallback( async (v: string) => { const [serviceName, methodName] = v.split('/', 2); if (serviceName == null || methodName == null) throw new Error('Should never happen'); await patchModel(activeRequest, { service: serviceName, method: methodName, }); }, [activeRequest], ); const handleConnect = useCallback(async () => { if (activeRequest == null) return; if (activeRequest.service == null || activeRequest.method == null) { alert({ id: 'grpc-invalid-service-method', title: 'Error', body: 'Service or method not selected', }); } onGo(); }, [activeRequest, onGo]); const handleSend = useCallback(async () => { if (activeRequest == null) return; onSend({ message: activeRequest.message }); }, [activeRequest, onSend]); const tabs: TabItem[] = useMemo( () => [ { value: TAB_MESSAGE, label: 'Message' }, { value: TAB_AUTH, label: 'Auth', options: { value: activeRequest.authenticationType, items: [ ...authentication.map((a) => ({ label: a.label || 'UNKNOWN', shortLabel: a.shortLabel, value: a.name, })), { type: 'separator' }, { label: 'No Authentication', shortLabel: 'Auth', value: null }, ], onChange: async (authenticationType) => { let authentication: GrpcRequest['authentication'] = activeRequest.authentication; if (activeRequest.authenticationType !== authenticationType) { authentication = { // Reset auth if changing types }; } await patchModel(activeRequest, { authenticationType, authentication, }); }, }, }, { value: TAB_METADATA, label: 'Metadata' }, { value: TAB_DESCRIPTION, label: 'Info', rightSlot: activeRequest.description && , }, ], [activeRequest, authentication], ); const activeTab = activeTabs?.[activeRequest.id]; const setActiveTab = useCallback( async (tab: string) => { await setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab })); }, [activeRequest.id, setActiveTabs], ); const handleMetadataChange = useCallback( (metadata: GrpcMetadataEntry[]) => patchModel(activeRequest, { metadata }), [activeRequest], ); const handleDescriptionChange = useCallback( (description: string) => patchModel(activeRequest, { description }), [activeRequest], ); return (
0 && paneWidth < 400 && '!grid-cols-1', )} > ({ label: o.label, value: o.value, type: 'default', shortLabel: o.label, }))} itemsAfter={[ { label: 'Refresh', type: 'default', leftSlot: , }, ]} > {methodType === 'client_streaming' || methodType === 'streaming' ? ( <> {isStreaming && ( <> )} ) : ( )}
patchModel(activeRequest, { name })} />
); }