diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 07a42991..040f94f0 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -76,7 +76,7 @@ async fn send_request( headers.insert("x-foo-bar", HeaderValue::from_static("hi mom")); headers.insert( HeaderName::from_static("x-api-key"), - HeaderValue::from_static("123-123-123"), + HeaderValue::from_str(models::generate_id("x").as_str()).expect("Failed to create header"), ); let m = Method::from_bytes(req.method.to_uppercase().as_bytes()).unwrap(); diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 7c2832a9..0085945f 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -306,7 +306,7 @@ pub async fn delete_all_responses( Ok(()) } -fn generate_id(prefix: &str) -> String { +pub fn generate_id(prefix: &str) -> String { format!( "{prefix}_{}", Alphanumeric.sample_string(&mut rand::thread_rng(), 10) diff --git a/src-web/components/Editor/Editor.tsx b/src-web/components/Editor/Editor.tsx index 884ccbea..cc02c41a 100644 --- a/src-web/components/Editor/Editor.tsx +++ b/src-web/components/Editor/Editor.tsx @@ -1,14 +1,15 @@ import './Editor.css'; -import { HTMLAttributes, useEffect, useMemo, useRef } from 'react'; +import { HTMLAttributes, useEffect, useMemo, useRef, useState } from 'react'; import { EditorView } from 'codemirror'; -import { baseExtensions, multiLineExtensions, syntaxExtension } from './extensions'; -import { EditorState, Transaction, EditorSelection } from '@codemirror/state'; +import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions'; import type { TransactionSpec } from '@codemirror/state'; +import { Compartment, EditorSelection, EditorState, Transaction } from '@codemirror/state'; import classnames from 'classnames'; import { autocompletion } from '@codemirror/autocomplete'; interface Props extends Omit, 'onChange'> { contentType: string; + valueKey?: string; useTemplating?: boolean; onChange?: (value: string) => void; onSubmit?: () => void; @@ -17,6 +18,7 @@ interface Props extends Omit, 'onChange'> { export default function Editor({ contentType, + valueKey, useTemplating, defaultValue, onChange, @@ -25,84 +27,53 @@ export default function Editor({ singleLine, ...props }: Props) { + const [cm, setCm] = useState<{ view: EditorView; langHolder: Compartment } | null>(null); const ref = useRef(null); - const extensions = useMemo(() => { - const ext = syntaxExtension({ contentType, useTemplating }); - return [ - autocompletion(), - ...(singleLine - ? [ - EditorView.domEventHandlers({ - keydown: (e) => { - // TODO: Figure out how to not have this mess up autocomplete - if (e.key === 'Enter') { - e.preventDefault(); - onSubmit?.(); - } - }, - }), - EditorState.transactionFilter.of( - (tr: Transaction): TransactionSpec | TransactionSpec[] => { - if (!tr.isUserEvent('input.paste')) { - return tr; - } - console.log('GOT PASTE', tr); + const extensions = useMemo( + () => getExtensions({ onSubmit, singleLine, onChange, contentType, useTemplating }), + [contentType], + ); - // let addedNewline = false; - const trs: TransactionSpec[] = []; - tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { - // console.log('CHANGE', { fromA, toA }, { fromB, toB }, inserted); - let insert = ''; - for (const line of inserted) { - insert += line.replace('\n', ''); - } - trs.push({ - ...tr, - selection: undefined, - changes: [{ from: fromB, to: toA, insert }], - }); - }); - - // selection: EditorSelection.create([EditorSelection.cursor(8)], 1), - // console.log('TRS', trs, tr); - trs.push({ - selection: EditorSelection.create([EditorSelection.cursor(8)], 1), - }); - return trs; - // return addedNewline ? [] : tr; - }, - ), - ] - : []), - ...baseExtensions, - ...(!singleLine ? [multiLineExtensions] : []), - ...(ext ? [ext] : []), - EditorView.updateListener.of((update) => { - if (typeof onChange === 'function') { - onChange(update.state.doc.toString()); - } - }), - ]; - }, [contentType]); + const newState = (langHolder: Compartment) => { + const langExt = getLanguageExtension({ contentType, useTemplating }); + return EditorState.create({ + doc: `${defaultValue ?? ''}`, + extensions: [...extensions, langHolder.of(langExt)], + }); + }; + // Create codemirror instance when ref initializes useEffect(() => { if (ref.current === null) return; - - let view: EditorView; + let view: EditorView | null = null; try { + const langHolder = new Compartment(); view = new EditorView({ - state: EditorState.create({ - doc: `${defaultValue ?? ''}`, - extensions: extensions, - }), + state: newState(langHolder), parent: ref.current, }); + setCm({ view, langHolder }); } catch (e) { - console.log(e); + console.log('Failed to initialize Codemirror', e); } return () => view?.destroy(); }, [ref.current]); + // Update value when valueKey changes + useEffect(() => { + if (cm === null) return; + cm.view.dispatch({ + changes: { from: 0, to: cm.view.state.doc.length, insert: `${defaultValue ?? ''}` }, + }); + }, [valueKey]); + + // Update language extension when contentType changes + useEffect(() => { + if (cm === null) return; + const ext = getLanguageExtension({ contentType, useTemplating }); + cm.view.dispatch({ effects: cm.langHolder.reconfigure(ext) }); + }, [contentType]); + return (
); } + +function getExtensions({ + singleLine, + onChange, + onSubmit, + contentType, + useTemplating, +}: Pick) { + const ext = getLanguageExtension({ contentType, useTemplating }); + return [ + autocompletion(), + ...(singleLine + ? [ + EditorView.domEventHandlers({ + keydown: (e) => { + // TODO: Figure out how to not have this mess up autocomplete + if (e.key === 'Enter') { + e.preventDefault(); + onSubmit?.(); + } + }, + }), + EditorState.transactionFilter.of( + (tr: Transaction): TransactionSpec | TransactionSpec[] => { + if (!tr.isUserEvent('input.paste')) { + return tr; + } + + // let addedNewline = false; + const trs: TransactionSpec[] = []; + tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => { + // console.log('CHANGE', { fromA, toA }, { fromB, toB }, inserted); + let insert = ''; + for (const line of inserted) { + insert += line.replace('\n', ''); + } + trs.push({ + ...tr, + selection: undefined, + changes: [{ from: fromB, to: toA, insert }], + }); + }); + + // selection: EditorSelection.create([EditorSelection.cursor(8)], 1), + // console.log('TRS', trs, tr); + trs.push({ + selection: EditorSelection.create([EditorSelection.cursor(8)], 1), + }); + return trs; + // return addedNewline ? [] : tr; + }, + ), + ] + : []), + ...baseExtensions, + ...(!singleLine ? [multiLineExtensions] : []), + ...(ext ? [ext] : []), + EditorView.updateListener.of((update) => { + if (typeof onChange === 'function') { + onChange(update.state.doc.toString()); + } + }), + ]; +} diff --git a/src-web/components/Editor/extensions.ts b/src-web/components/Editor/extensions.ts index 54c2a75b..4d88fcc2 100644 --- a/src-web/components/Editor/extensions.ts +++ b/src-web/components/Editor/extensions.ts @@ -88,7 +88,7 @@ const syntaxExtensions: Record = url: { base: url(), ext: [] }, }; -export function syntaxExtension({ +export function getLanguageExtension({ contentType, useTemplating, }: { diff --git a/src-web/components/ResponsePane.tsx b/src-web/components/ResponsePane.tsx index a29646a9..be98365d 100644 --- a/src-web/components/ResponsePane.tsx +++ b/src-web/components/ResponsePane.tsx @@ -101,7 +101,7 @@ export function ResponsePane({ requestId, error }: Props) { /> ) : response?.body ? (