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 Editor from './components/Editor/Editor';
import { HStack, VStack } from './components/Stacks';
@@ -8,6 +8,7 @@ import { IconButton } from './components/IconButton';
import { Sidebar } from './components/Sidebar';
import { UrlBar } from './components/UrlBar';
import { Grid } from './components/Grid';
import { motion } from 'framer-motion';
interface Response {
url: string;
@@ -20,16 +21,18 @@ interface Response {
}
function App() {
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [response, setResponse] = useState<Response | null>(null);
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 }>({
label: 'GET',
value: 'GET',
});
async function sendRequest() {
setLoading(true);
setError(null);
try {
@@ -44,11 +47,23 @@ function App() {
setResponse(resp);
} catch (err) {
setError(`${err}`);
} finally {
setLoading(false);
}
}
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 (
<>
<div className="grid grid-cols-[auto_1fr] h-full">
@@ -60,15 +75,12 @@ function App() {
<UrlBar
method={method}
url={url}
loading={loading}
onMethodChange={setMethod}
onUrlChange={setUrl}
sendRequest={sendRequest}
/>
<Editor
value={`{\n "foo": "bar"\n}`}
contentType="application/json"
onChange={setBody}
/>
<Editor initialValue={body} contentType="application/json" onChange={setBody} />
</VStack>
</VStack>
<VStack className="w-full">
@@ -88,32 +100,40 @@ function App() {
<IconButton icon="gear" className="ml-auto" size="sm" />
</Dropdown>
</HStack>
<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 !== null && (
<>
<HStack
items="center"
className="italic text-gray-500 text-sm w-full pointer-events-none h-10 mb-3 flex-shrink-0"
>
{response.status}
&nbsp;&bull;&nbsp;
{response.elapsed}ms &nbsp;&bull;&nbsp;
{response.elapsed2}ms
</HStack>
{contentType.includes('html') ? (
<iframe
title="Response preview"
srcDoc={response.body}
sandbox="allow-scripts allow-same-origin"
className="h-full w-full rounded-lg"
/>
) : response?.body ? (
<Editor value={response?.body} contentType={contentType} />
) : null}
</>
)}
</VStack>
{(response || error) && (
<motion.div
animate={{ opacity: 1 }}
initial={{ opacity: 0 }}
className="w-full h-full"
>
<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 && (
<>
<HStack
items="center"
className="italic text-gray-500 text-sm w-full pointer-events-none h-10 mb-3 flex-shrink-0"
>
{response.status}
&nbsp;&bull;&nbsp;
{response.elapsed}ms &nbsp;&bull;&nbsp;
{response.elapsed2}ms
</HStack>
{contentType.includes('html') ? (
<iframe
title="Response preview"
srcDoc={response.body}
sandbox="allow-scripts allow-same-origin"
className="h-full w-full rounded-lg"
/>
) : response?.body ? (
<Editor value={response?.body} contentType={contentType} />
) : null}
</>
)}
</VStack>
</motion.div>
)}
</VStack>
</Grid>
</div>

View File

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

View File

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

View File

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