mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-01 23:13:16 +02:00
Codemirror initial value support
This commit is contained in:
@@ -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 && (
|
||||||
•
|
<>
|
||||||
{response.elapsed}ms •
|
<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"
|
•
|
||||||
srcDoc={response.body}
|
{response.elapsed}ms •
|
||||||
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>
|
||||||
|
|||||||
@@ -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" />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user