diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 7a904b81..ace5b184 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -215,7 +215,9 @@ async fn actually_send_ephemeral_request( response = models::update_response_if_id(response, pool) .await .expect("Failed to update response"); - emit_side_effect(app_handle, "updated_model", &response); + if request.id != "" { + emit_side_effect(app_handle, "updated_model", &response); + } Ok(response) } Err(e) => response_err(response, e.to_string(), app_handle, pool).await, diff --git a/src-web/components/GraphQLEditor.tsx b/src-web/components/GraphQLEditor.tsx index 355d66f9..ded05629 100644 --- a/src-web/components/GraphQLEditor.tsx +++ b/src-web/components/GraphQLEditor.tsx @@ -1,15 +1,10 @@ -import type { Extension } from '@codemirror/state'; -import { useEffect, useMemo, useState } from 'react'; +import { updateSchema } from 'cm6-graphql'; +import type { EditorView } from 'codemirror'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; import type { HttpRequest } from '../lib/models'; import { sendEphemeralRequest } from '../lib/sendEphemeralRequest'; import type { EditorProps } from './core/Editor'; -import { - buildClientSchema, - Editor, - formatGraphQL, - getIntrospectionQuery, - graphql, -} from './core/Editor'; +import { buildClientSchema, Editor, formatGraphQL, getIntrospectionQuery } from './core/Editor'; import { Separator } from './core/Separator'; type Props = Pick< @@ -41,23 +36,28 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi } }, [defaultValue]); - const handleChange = (b: GraphQLBody) => { - onChange?.(JSON.stringify(b, null, 2)); - }; + const handleChange = useCallback( + (b: GraphQLBody) => onChange?.(JSON.stringify(b, null, 2)), + [onChange], + ); - const handleChangeQuery = (query: string) => { - handleChange({ query, variables }); - }; + const handleChangeQuery = useCallback( + (query: string) => handleChange({ query, variables }), + [handleChange], + ); - const handleChangeVariables = (variables: string) => { - try { - handleChange({ query, variables: JSON.parse(variables) }); - } catch (e) { - // Meh, not much we can do here - } - }; + const handleChangeVariables = useCallback( + (variables: string) => { + try { + handleChange({ query, variables: JSON.parse(variables) }); + } catch (e) { + // Meh, not much we can do here + } + }, + [handleChange], + ); - const [graphqlExtension, setGraphqlExtension] = useState(); + const editorViewRef = useRef(null); useEffect(() => { const body = JSON.stringify({ @@ -67,9 +67,11 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi const req: HttpRequest = { ...baseRequest, body, id: '' }; sendEphemeralRequest(req).then((response) => { try { - const { data } = JSON.parse(response.body); - const schema = buildClientSchema(data); - setGraphqlExtension(graphql(schema, {})); + if (editorViewRef.current) { + const { data } = JSON.parse(response.body); + const schema = buildClientSchema(data); + updateSchema(editorViewRef.current, schema); + } } catch (err) { console.log('Failed to parse introspection query', err); return; @@ -80,9 +82,9 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi return (
defaultValue, [forceUpdateKey, props.type]); - return <_Editor defaultValue={defaultValue} forceUpdateKey={forceUpdateKey} {...props} />; -} - -const _Editor = memo(function _Editor({ - readOnly, - type = 'text', - heightMode, - contentType, - autoFocus, - placeholder, - useTemplating, - defaultValue, - forceUpdateKey, - languageExtension, - onChange, - onFocus, - className, - singleLine, - format, - autocomplete, -}: EditorProps) { +export const Editor = forwardRef(function Editor( + { + readOnly, + type = 'text', + heightMode, + contentType, + autoFocus, + placeholder, + useTemplating, + defaultValue, + forceUpdateKey, + onChange, + onFocus, + className, + singleLine, + format, + autocomplete, + }: EditorProps, + ref, +) { const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null); - const wrapperRef = useRef(null); - - // Unmount editor when we're done - useUnmount(() => { - cm.current?.view.destroy(); - cm.current = null; - }); + useImperativeHandle(ref, () => cm.current?.view); // Use ref so we can update the onChange handler without re-initializing the editor const handleChange = useRef(onChange); @@ -117,13 +97,18 @@ const _Editor = memo(function _Editor({ }, [forceUpdateKey]); // Initialize the editor when ref mounts - useEffect(() => { - if (wrapperRef.current === null || cm.current !== null) return; + const initEditorRef = useCallback((container: HTMLDivElement | null) => { + if (container === null) { + cm.current?.view.destroy(); + cm.current = null; + return; + } + let view: EditorView; try { const languageCompartment = new Compartment(); - const langExt = - languageExtension ?? getLanguageExtension({ contentType, useTemplating, autocomplete }); + const langExt = getLanguageExtension({ contentType, useTemplating, autocomplete }); + const state = EditorState.create({ doc: `${defaultValue ?? ''}`, extensions: [ @@ -132,7 +117,7 @@ const _Editor = memo(function _Editor({ placeholderExt(placeholderElFromText(placeholder ?? '')), ), ...getExtensions({ - container: wrapperRef.current, + container, onChange: handleChange, onFocus: handleFocus, readOnly, @@ -140,18 +125,19 @@ const _Editor = memo(function _Editor({ }), ], }); - view = new EditorView({ state, parent: wrapperRef.current }); + + view = new EditorView({ state, parent: container }); cm.current = { view, languageCompartment }; - syncGutterBg({ parent: wrapperRef.current, className }); + syncGutterBg({ parent: container, className }); if (autoFocus) view.focus(); } catch (e) { console.log('Failed to initialize Codemirror', e); } - }, [wrapperRef.current, languageExtension]); + }, []); const cmContainer = (
{children}