import type { GrpcMetadataEntry, GrpcRequest } from '@yaakapp-internal/models'; import classNames from 'classnames'; import { useAtom } from 'jotai'; import { atomWithStorage } from 'jotai/utils'; 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 { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest'; 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'; const tabsAtom = atomWithStorage>('grpcRequestPaneActiveTabs', {}); export function GrpcConnectionSetupPane({ style, services, methodType, activeRequest, protoFiles, reflectionError, reflectionLoading, isStreaming, onGo, onCommit, onCancel, onSend, }: Props) { const updateRequest = useUpdateAnyGrpcRequest(); const authentication = useHttpAuthenticationSummaries(); const [activeTabs, setActiveTabs] = useAtom(tabsAtom); const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null); const urlContainerEl = useRef(null); const { width: paneWidth } = useContainerSize(urlContainerEl); const handleChangeUrl = useCallback( (url: string) => updateRequest.mutateAsync({ id: activeRequest.id, update: { url } }), [activeRequest.id, updateRequest], ); const handleChangeMessage = useCallback( (message: string) => { return updateRequest.mutateAsync({ id: activeRequest.id, update: { message } }); }, [activeRequest.id, updateRequest], ); const select = useMemo(() => { const options = services?.flatMap((s) => s.methods.map((m) => ({ label: `${s.name.split('.', 2).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 updateRequest.mutateAsync({ id: activeRequest.id, update: { service: serviceName, method: methodName, }, }); }, [activeRequest.id, updateRequest], ); 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: (authenticationType) => { let authentication: GrpcRequest['authentication'] = activeRequest.authentication; if (activeRequest.authenticationType !== authenticationType) { authentication = { // Reset auth if changing types }; } updateRequest.mutate({ id: activeRequest.id, update: { authenticationType, authentication }, }); }, }, }, { value: TAB_METADATA, label: 'Metadata' }, { value: TAB_DESCRIPTION, label: 'Info', rightSlot: activeRequest.description && , }, ], [ activeRequest.authentication, activeRequest.authenticationType, activeRequest.description, activeRequest.id, authentication, updateRequest, ], ); const activeTab = activeTabs?.[activeRequest.id]; const setActiveTab = useCallback( (tab: string) => { setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab })); }, [activeRequest.id, setActiveTabs], ); const handleMetadataChange = useCallback( (metadata: GrpcMetadataEntry[]) => updateRequest.mutate({ id: activeRequest.id, update: { metadata } }), [activeRequest.id, updateRequest], ); const handleDescriptionChange = useCallback( (description: string) => updateRequest.mutate({ id: activeRequest.id, update: { description } }), [activeRequest.id, updateRequest], ); return (
({ label: o.label, value: o.value, type: 'default', shortLabel: o.label, }))} extraItems={[ { label: 'Refresh', type: 'default', leftSlot: , }, ]} > {methodType === 'client_streaming' || methodType === 'streaming' ? ( <> {isStreaming && ( <> )} ) : ( )}
updateRequest.mutate({ id: activeRequest.id, update: { name } })} />
); }