Codemirror initial value support

This commit is contained in:
Gregory Schier
2023-02-24 16:43:47 -08:00
parent 7dea1b7870
commit 72486b448c
4 changed files with 71 additions and 57 deletions

View File

@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { invoke } from '@tauri-apps/api/tauri'; import { invoke } from '@tauri-apps/api/tauri';
import Editor from './components/Editor/Editor'; import Editor from './components/Editor/Editor';
import { HStack, VStack } from './components/Stacks'; import { HStack, VStack } from './components/Stacks';
@@ -8,6 +8,7 @@ import { IconButton } from './components/IconButton';
import { Sidebar } from './components/Sidebar'; import { Sidebar } from './components/Sidebar';
import { UrlBar } from './components/UrlBar'; import { UrlBar } from './components/UrlBar';
import { Grid } from './components/Grid'; import { Grid } from './components/Grid';
import { motion } from 'framer-motion';
interface Response { interface Response {
url: string; url: string;
@@ -20,16 +21,18 @@ interface Response {
} }
function App() { function App() {
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [response, setResponse] = useState<Response | null>(null); const [response, setResponse] = useState<Response | null>(null);
const [url, setUrl] = useState<string>('https://go-server.schier.dev/debug'); const [url, setUrl] = useState<string>('https://go-server.schier.dev/debug');
const [body, setBody] = useState<string>(''); const [body, setBody] = useState<string>(`{\n "foo": "bar"\n}`);
const [method, setMethod] = useState<{ label: string; value: string }>({ const [method, setMethod] = useState<{ label: string; value: string }>({
label: 'GET', label: 'GET',
value: 'GET', value: 'GET',
}); });
async function sendRequest() { async function sendRequest() {
setLoading(true);
setError(null); setError(null);
try { try {
@@ -44,11 +47,23 @@ function App() {
setResponse(resp); setResponse(resp);
} catch (err) { } catch (err) {
setError(`${err}`); setError(`${err}`);
} finally {
setLoading(false);
} }
} }
const contentType = response?.headers['content-type']?.split(';')[0] ?? 'text/plain'; const contentType = response?.headers['content-type']?.split(';')[0] ?? 'text/plain';
useEffect(() => {
const listener = async (e: KeyboardEvent) => {
if (e.metaKey && (e.key === 'Enter' || e.key === 'r')) {
await sendRequest();
}
};
document.documentElement.addEventListener('keypress', listener);
return () => document.documentElement.removeEventListener('keypress', listener);
}, []);
return ( return (
<> <>
<div className="grid grid-cols-[auto_1fr] h-full"> <div className="grid grid-cols-[auto_1fr] h-full">
@@ -60,15 +75,12 @@ function App() {
<UrlBar <UrlBar
method={method} method={method}
url={url} url={url}
loading={loading}
onMethodChange={setMethod} onMethodChange={setMethod}
onUrlChange={setUrl} onUrlChange={setUrl}
sendRequest={sendRequest} sendRequest={sendRequest}
/> />
<Editor <Editor initialValue={body} contentType="application/json" onChange={setBody} />
value={`{\n "foo": "bar"\n}`}
contentType="application/json"
onChange={setBody}
/>
</VStack> </VStack>
</VStack> </VStack>
<VStack className="w-full"> <VStack className="w-full">
@@ -88,32 +100,40 @@ function App() {
<IconButton icon="gear" className="ml-auto" size="sm" /> <IconButton icon="gear" className="ml-auto" size="sm" />
</Dropdown> </Dropdown>
</HStack> </HStack>
<VStack className="pr-3 pl-1.5 py-3" space={3}> {(response || error) && (
{error && <div className="text-white bg-red-500 px-3 py-1 rounded">{error}</div>} <motion.div
{response !== null && ( animate={{ opacity: 1 }}
<> initial={{ opacity: 0 }}
<HStack className="w-full h-full"
items="center" >
className="italic text-gray-500 text-sm w-full pointer-events-none h-10 mb-3 flex-shrink-0" <VStack className="pr-3 pl-1.5 py-3" space={3}>
> {error && <div className="text-white bg-red-500 px-3 py-1 rounded">{error}</div>}
{response.status} {response && (
&nbsp;&bull;&nbsp; <>
{response.elapsed}ms &nbsp;&bull;&nbsp; <HStack
{response.elapsed2}ms items="center"
</HStack> className="italic text-gray-500 text-sm w-full pointer-events-none h-10 mb-3 flex-shrink-0"
{contentType.includes('html') ? ( >
<iframe {response.status}
title="Response preview" &nbsp;&bull;&nbsp;
srcDoc={response.body} {response.elapsed}ms &nbsp;&bull;&nbsp;
sandbox="allow-scripts allow-same-origin" {response.elapsed2}ms
className="h-full w-full rounded-lg" </HStack>
/> {contentType.includes('html') ? (
) : response?.body ? ( <iframe
<Editor value={response?.body} contentType={contentType} /> title="Response preview"
) : null} srcDoc={response.body}
</> sandbox="allow-scripts allow-same-origin"
)} className="h-full w-full rounded-lg"
</VStack> />
) : response?.body ? (
<Editor value={response?.body} contentType={contentType} />
) : null}
</>
)}
</VStack>
</motion.div>
)}
</VStack> </VStack>
</Grid> </Grid>
</div> </div>

View File

@@ -3,15 +3,12 @@ import './Editor.css';
interface Props { interface Props {
contentType: string; contentType: string;
value: string; initialValue?: string;
value?: string;
onChange?: (value: string) => void; onChange?: (value: string) => void;
} }
export default function Editor(props: Props) { export default function Editor(props: Props) {
const { ref } = useCodeMirror({ const { ref } = useCodeMirror(props);
value: props.value,
contentType: props.contentType,
onChange: props.onChange,
});
return <div ref={ref} className="cm-wrapper" />; return <div ref={ref} className="cm-wrapper" />;
} }

View File

@@ -1,30 +1,22 @@
import { HStack } from './Stacks';
import { DropdownMenuRadio } from './Dropdown'; import { DropdownMenuRadio } from './Dropdown';
import { Button } from './Button'; import { Button } from './Button';
import { Input } from './Input'; import { Input } from './Input';
import { FormEvent, useState } from 'react'; import { FormEvent } from 'react';
import { IconButton } from './IconButton'; import { IconButton } from './IconButton';
interface Props { interface Props {
sendRequest: () => Promise<void>; sendRequest: () => void;
loading: boolean;
method: { label: string; value: string }; method: { label: string; value: string };
url: string; url: string;
onMethodChange: (method: { label: string; value: string }) => void; onMethodChange: (method: { label: string; value: string }) => void;
onUrlChange: (url: string) => void; onUrlChange: (url: string) => void;
} }
export function UrlBar({ sendRequest, onMethodChange, method, onUrlChange, url }: Props) { export function UrlBar({ sendRequest, loading, onMethodChange, method, onUrlChange, url }: Props) {
const [loading, setLoading] = useState(false);
const handleSendRequest = async (e: FormEvent<HTMLFormElement>) => { const handleSendRequest = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
if (loading) return; sendRequest();
setLoading(true);
try {
await sendRequest();
} finally {
setLoading(false);
}
}; };
return ( return (

View File

@@ -96,11 +96,13 @@ const extensions = [
]; ];
export default function useCodeMirror({ export default function useCodeMirror({
initialValue,
value, value,
contentType, contentType,
onChange, onChange,
}: { }: {
value: string; initialValue?: string;
value?: string;
contentType: string; contentType: string;
onChange?: (value: string) => void; onChange?: (value: string) => void;
}) { }) {
@@ -108,9 +110,12 @@ export default function useCodeMirror({
const ref = useRef(null); const ref = useRef(null);
useEffect(() => { useEffect(() => {
if (ref.current === null) return; if (ref.current === null) return;
const state = EditorState.create({
const view = new EditorView({ doc: initialValue,
extensions: getExtensions({ contentType, onChange }), extensions: getExtensions({ contentType, onChange }),
});
const view = new EditorView({
state,
parent: ref.current, parent: ref.current,
}); });
@@ -123,11 +128,11 @@ export default function useCodeMirror({
if (cm === null) return; if (cm === null) return;
const newState = EditorState.create({ const newState = EditorState.create({
doc: value, doc: value ?? cm.state.doc,
extensions: getExtensions({ contentType, onChange }), extensions: getExtensions({ contentType, onChange }),
}); });
cm.setState(newState); cm.setState(newState);
}, [cm, value]); }, [cm, contentType, value, onChange]);
return { ref, cm }; return { ref, cm };
} }