From 5904b6fdedb071361ac417694bad24c9b6aef5f8 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Tue, 14 Mar 2023 19:56:02 -0700 Subject: [PATCH] Add GraphQL variables editor --- src-web/components/RequestPane.tsx | 3 -- src-web/components/Sidebar.tsx | 2 +- src-web/components/UrlBar.tsx | 10 ++-- src-web/components/Workspace.tsx | 8 +-- src-web/components/core/Divider.tsx | 2 +- src-web/components/core/Editor/Editor.css | 7 +++ src-web/components/core/Editor/Editor.tsx | 54 +++++++++---------- src-web/components/editors/GraphQLEditor.tsx | 56 +++++++++++++++----- 8 files changed, 89 insertions(+), 53 deletions(-) diff --git a/src-web/components/RequestPane.tsx b/src-web/components/RequestPane.tsx index a8c33575..a942dd4a 100644 --- a/src-web/components/RequestPane.tsx +++ b/src-web/components/RequestPane.tsx @@ -1,5 +1,4 @@ import classnames from 'classnames'; -import { act } from 'react-dom/test-utils'; import { useActiveRequest } from '../hooks/useActiveRequest'; import { useSendRequest } from '../hooks/useSendRequest'; import { useUpdateRequest } from '../hooks/useUpdateRequest'; @@ -67,7 +66,6 @@ export function RequestPane({ fullHeight, className }: Props) { key={activeRequest.id} useTemplating className="!bg-gray-50" - heightMode={fullHeight ? 'full' : 'auto'} defaultValue={activeRequest.body ?? ''} contentType="application/json" onChange={(body) => updateRequest.mutate({ body })} @@ -78,7 +76,6 @@ export function RequestPane({ fullHeight, className }: Props) { useTemplating className="!bg-gray-50" defaultValue={activeRequest?.body ?? ''} - heightMode={fullHeight ? 'full' : 'auto'} onChange={(body) => updateRequest.mutate({ body })} /> ) : ( diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index cb74577c..62da5092 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -26,7 +26,7 @@ export function Sidebar({ className }: Props) {
diff --git a/src-web/components/UrlBar.tsx b/src-web/components/UrlBar.tsx index 099a45af..cbff485e 100644 --- a/src-web/components/UrlBar.tsx +++ b/src-web/components/UrlBar.tsx @@ -1,5 +1,5 @@ import { Button } from './core/Button'; -import { DropdownMenuRadio } from './core/Dropdown'; +import { DropdownMenuRadio, DropdownMenuTrigger } from './core/Dropdown'; import { IconButton } from './core/IconButton'; import { Input } from './core/Input'; @@ -45,9 +45,11 @@ export function UrlBar({ sendRequest, loading, onMethodChange, method, onUrlChan { label: 'HEAD', value: 'HEAD' }, ]} > - + + + } rightSlot={ diff --git a/src-web/components/Workspace.tsx b/src-web/components/Workspace.tsx index f191034c..075f21d9 100644 --- a/src-web/components/Workspace.tsx +++ b/src-web/components/Workspace.tsx @@ -10,7 +10,7 @@ import { useActiveRequest } from '../hooks/useActiveRequest'; export default function Workspace() { const activeRequest = useActiveRequest(); const { width } = useWindowSize(); - const isH = width > 900; + const isSideBySide = width > 900; return (
@@ -26,12 +26,12 @@ export default function Workspace() {
- +
diff --git a/src-web/components/core/Divider.tsx b/src-web/components/core/Divider.tsx index 0e1a194e..522ca267 100644 --- a/src-web/components/core/Divider.tsx +++ b/src-web/components/core/Divider.tsx @@ -12,7 +12,7 @@ export function Divider({ className, orientation = 'horizontal', decorative }: P (onChange); + useEffect(() => { + handleChange.current = onChange; + }, [onChange]); + + // Use ref so we can update the onChange handler without re-initializing the editor + const handleFocus = useRef<_EditorProps['onFocus']>(onFocus); + useEffect(() => { + handleFocus.current = onFocus; + }, [onFocus]); + // Update language extension when contentType changes useEffect(() => { if (cm.current === null) return; @@ -69,11 +80,11 @@ export function _Editor({ langHolder.of(langExt), ...getExtensions({ container: el, + onChange: handleChange, + onFocus: handleFocus, readOnly, placeholder, singleLine, - onChange, - onFocus, contentType, useTemplating, }), @@ -91,6 +102,7 @@ export function _Editor({ return (
- {contentType?.includes('graphql') && ( - { - const doc = cm.current?.view.state.doc ?? ''; - const insert = formatSdl(doc.toString()); - cm.current?.view.dispatch({ changes: { from: 0, to: doc.length, insert } }); - }} - /> - )} -
+ /> ); } @@ -125,14 +125,12 @@ function getExtensions({ useTemplating, }: Pick< _EditorProps, - | 'singleLine' - | 'onChange' - | 'contentType' - | 'useTemplating' - | 'placeholder' - | 'readOnly' - | 'onFocus' -> & { container: HTMLDivElement | null }) { + 'singleLine' | 'contentType' | 'useTemplating' | 'placeholder' | 'readOnly' +> & { + container: HTMLDivElement | null; + onChange: MutableRefObject<_EditorProps['onChange']>; + onFocus: MutableRefObject<_EditorProps['onFocus']>; +}) { const ext = getLanguageExtension({ contentType, useTemplating }); // TODO: Ensure tooltips render inside the dialog if we are in one. @@ -172,14 +170,14 @@ function getExtensions({ // Handle onFocus EditorView.domEventHandlers({ focus: () => { - onFocus?.(); + onFocus.current?.(); }, }), // Handle onChange EditorView.updateListener.of((update) => { - if (typeof onChange === 'function' && update.docChanged) { - onChange(update.state.doc.toString()); + if (onChange && update.docChanged) { + onChange.current?.(update.state.doc.toString()); } }), ]; diff --git a/src-web/components/editors/GraphQLEditor.tsx b/src-web/components/editors/GraphQLEditor.tsx index e6955c3c..05d164c1 100644 --- a/src-web/components/editors/GraphQLEditor.tsx +++ b/src-web/components/editors/GraphQLEditor.tsx @@ -1,4 +1,5 @@ import { useMemo } from 'react'; +import { Divider } from '../core/Divider'; import type { EditorProps } from '../core/Editor'; import { Editor } from '../core/Editor'; @@ -7,26 +8,57 @@ type Props = Pick< 'heightMode' | 'onChange' | 'defaultValue' | 'className' | 'useTemplating' >; -export function GraphQLEditor({ defaultValue, onChange, ...props }: Props) { - const { query } = useMemo(() => { +interface GraphQLBody { + query: string; + variables?: Record; + operationName?: string; +} + +export function GraphQLEditor({ defaultValue, onChange, ...extraEditorProps }: Props) { + const { query, variables } = useMemo(() => { try { - const { query } = JSON.parse(defaultValue ?? '{}'); - return { query }; + const p = JSON.parse(defaultValue ?? '{}'); + const query = p.query ?? ''; + const variables = p.variables; + const operationName = p.operationName; + return { query, variables, operationName }; } catch (err) { return { query: 'failed to parse' }; } }, [defaultValue]); - const handleChange = (query: string) => { - onChange?.(JSON.stringify({ query }, null, 2)); + const handleChange = (b: GraphQLBody) => { + onChange?.(JSON.stringify(b, null, 2)); + }; + + const handleChangeQuery = (query: string) => { + handleChange({ query, variables }); + }; + + const handleChangeVariables = (variables: string) => { + handleChange({ query, variables: JSON.parse(variables) }); }; return ( - +
+ +
+ +
+

Variables

+ +
); }