Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions apps/yaak-client/components/CurlViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useQuery } from "@tanstack/react-query";
import type { HttpRequest } from "@yaakapp-internal/models";
import type { CSSProperties } from "react";
import { requestToCurl } from "../lib/curl";
import { CopyIconButton } from "./CopyIconButton";
import { Editor } from "./core/Editor/LazyEditor";

interface Props {
request: HttpRequest;
style?: CSSProperties;
}

export function CurlViewer({ request, style }: Props) {
const curl = useQuery({
queryKey: ["request_curl", request.id, request.updatedAt],
queryFn: () => requestToCurl(request, "send"),
});

const value = curl.data ?? "";

return (
<div
style={style}
className="x-theme-responsePane relative h-full max-h-full min-w-0 overflow-hidden rounded-md border border-border-subtle bg-surface"
>
{curl.isError ? (
<div className="p-3 text-sm text-danger">Failed to render curl command.</div>
) : (
<>
<div className="absolute inset-0">
<Editor
readOnly
forceUpdateKey={value}
stateKey={`curl.${request.id}`}
defaultValue={curl.isPending ? "Rendering curl..." : value}
language="shell"
heightMode="full"
wrapLines
/>
</div>
<div className="pointer-events-none absolute right-2 top-2 z-20">
<CopyIconButton
text={value}
size="xs"
title="Copy Curl command"
disabled={!value}
className="pointer-events-auto bg-surface/80 opacity-75 shadow-sm backdrop-blur-sm hover:!opacity-100 focus-visible:!opacity-100"
/>
</div>
</>
)}
</div>
);
}
77 changes: 66 additions & 11 deletions apps/yaak-client/components/HttpRequestLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import type { SlotProps } from "@yaakapp-internal/ui";
import { SplitLayout } from "@yaakapp-internal/ui";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import type { CSSProperties } from "react";
import type { CSSProperties, ReactNode } from "react";
import { useCurrentGraphQLSchema } from "../hooks/useIntrospectGraphQL";
import { activeWorkspaceAtom } from "../hooks/useActiveWorkspace";
import { workspaceLayoutAtom } from "../lib/atoms";
import { curlPanelRequestIdAtom } from "../lib/curlPanel";
import { CurlViewer } from "./CurlViewer";
import { GraphQLDocsExplorer } from "./graphql/GraphQLDocsExplorer";
import { showGraphQLDocExplorerAtom } from "./graphql/graphqlAtoms";
import { HttpRequestPane } from "./HttpRequestPane";
Expand All @@ -22,7 +24,9 @@ export function HttpRequestLayout({ activeRequest, style }: Props) {
const graphQLSchema = useCurrentGraphQLSchema(activeRequest);
const workspaceLayout = useAtomValue(workspaceLayoutAtom);
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
const curlPanelRequestId = useAtomValue(curlPanelRequestIdAtom);
const wsId = activeWorkspace?.id ?? "n/a";
const showCurlPanelForRequest = curlPanelRequestId === activeRequest.id;

const requestResponseSplit = ({ style }: Pick<SlotProps, "style">) => (
<SplitLayout
Expand All @@ -49,21 +53,72 @@ export function HttpRequestLayout({ activeRequest, style }: Props) {
graphQLSchema != null
) {
return (
<SplitLayout
storageKey={`graphql_layout::${wsId}`}
defaultRatio={1 / 3}
firstSlot={requestResponseSplit}
secondSlot={({ style, orientation }) => (
<GraphQLDocsExplorer
requestId={activeRequest.id}
schema={graphQLSchema}
className={classNames(orientation === "horizontal" && "!ml-0")}
<CurlSplit
activeRequest={activeRequest}
style={style}
showCurlPanel={showCurlPanelForRequest}
workspaceId={wsId}
mainSlot={({ style }) => (
<SplitLayout
storageKey={`graphql_layout::${wsId}`}
defaultRatio={1 / 3}
style={style}
firstSlot={requestResponseSplit}
secondSlot={({ style, orientation }) => (
<GraphQLDocsExplorer
requestId={activeRequest.id}
schema={graphQLSchema}
className={classNames(orientation === "horizontal" && "!ml-0")}
style={style}
/>
)}
/>
)}
/>
);
}

return requestResponseSplit({ style });
return (
<CurlSplit
activeRequest={activeRequest}
style={style}
showCurlPanel={showCurlPanelForRequest}
workspaceId={wsId}
mainSlot={requestResponseSplit}
/>
);
}

function CurlSplit({
activeRequest,
mainSlot,
showCurlPanel,
style,
workspaceId,
}: {
activeRequest: HttpRequest;
mainSlot: (props: Pick<SlotProps, "style">) => ReactNode;
showCurlPanel: boolean;
style: CSSProperties;
workspaceId: string;
}) {
if (!showCurlPanel) {
return mainSlot({ style });
}

return (
<SplitLayout
storageKey={`curl_layout::${workspaceId}`}
style={style}
defaultRatio={0.28}
minWidthPx={280}
layout="horizontal"
firstSlot={mainSlot}
secondSlot={({ style }) => (
<div style={style} className="p-3 pl-0">
<CurlViewer request={activeRequest} />
</div>
)}
/>
);
}
17 changes: 16 additions & 1 deletion apps/yaak-client/components/RecentRequestsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import classNames from "classnames";
import { useMemo, useRef } from "react";
import type { MouseEvent } from "react";
import { useCallback, useMemo, useRef } from "react";
import { useActiveRequest } from "../hooks/useActiveRequest";
import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import { allRequestsAtom } from "../hooks/useAllRequests";
import { useHotKey } from "../hooks/useHotKey";
import { useKeyboardEvent } from "../hooks/useKeyboardEvent";
import { useRecentRequests } from "../hooks/useRecentRequests";
import { jotaiStore } from "../lib/jotai";
import { renameModelWithPrompt } from "../lib/renameModelWithPrompt";
import { resolvedModelName } from "../lib/resolvedModelName";
import { router } from "../lib/router";
import { Button } from "./core/Button";
Expand Down Expand Up @@ -46,6 +48,18 @@ export function RecentRequestsDropdown({ className }: Props) {
dropdownRef.current?.prev?.();
});

const handleDoubleClick = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
if (activeRequest == null) return;

dropdownRef.current?.close?.();
void renameModelWithPrompt(activeRequest);
},
[activeRequest],
);

const items = useMemo(() => {
const activeWorkspaceId = jotaiStore.get(activeWorkspaceIdAtom);
if (activeWorkspaceId === null) return [];
Expand Down Expand Up @@ -88,6 +102,7 @@ export function RecentRequestsDropdown({ className }: Props) {
<Button
size="sm"
hotkeyAction="switcher.toggle"
onDoubleClick={handleDoubleClick}
className={classNames(
className,
"truncate pointer-events-auto",
Expand Down
3 changes: 2 additions & 1 deletion apps/yaak-client/components/RedirectToLatestWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getRecentRequests } from "../hooks/useRecentRequests";
import { useRecentWorkspaces } from "../hooks/useRecentWorkspaces";
import { fireAndForget } from "../lib/fireAndForget";
import { router } from "../lib/router";
import { setWorkspaceSearchParams } from "../lib/setWorkspaceSearchParams";

export function RedirectToLatestWorkspace() {
const workspaces = useAtomValue(workspacesAtom);
Expand All @@ -31,11 +32,11 @@ export function RedirectToLatestWorkspace() {
const search = {
cookie_jar_id: cookieJarId,
environment_id: environmentId,
request_id: requestId,
};

console.log("Redirecting to workspace", params, search);
await router.navigate({ to: "/workspaces/$workspaceId", params, search });
setWorkspaceSearchParams({ request_id: requestId, folder_id: null });
})(),
);
}, [recentWorkspaces, workspaces, workspaces.length]);
Expand Down
Loading