Skip to content

Commit 06b7fba

Browse files
authored
context menu, json5 toggle (#5)
* context menu, json5 toggle * cleanup file import UX * more import cases supported, fix toggle for value pane * cleanup menus and state * cleanup log * json4 to 5 and vv for schema as well * persist schema, show to users * handle query params, fix serialization bugs
1 parent 6846f6b commit 06b7fba

23 files changed

+972
-219
lines changed

app/api/schema/route.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,21 @@ async function getSchema(url: string) {
1212
}
1313

1414
export async function GET(request: Request) {
15-
const {searchParams} = new URL(request.url);
15+
const { searchParams } = new URL(request.url)
1616

1717
try {
1818
const url = searchParams.get("url")
1919
if (!url) {
20-
2120
return new Response("No schema key provided", {
2221
status: 400,
2322
})
2423
}
2524
const schema = await getSchema(url)
2625
return new Response(schema, {
27-
headers: { "content-type": "application/json" },
26+
headers: {
27+
"content-type": "application/json",
28+
// "cache-control": "s-maxage=1440000",
29+
},
2830
})
2931
} catch (e) {
3032
return new Response(

app/api/schemas/route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ async function getSchemas() {
1313

1414
export async function GET(request: Request) {
1515
return new Response(await getSchemas(), {
16-
headers: { "content-type": "application/json" },
16+
headers: {
17+
"content-type": "application/json",
18+
// "cache-control": "s-maxage=1440000",
19+
},
1720
})
1821
}

app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
4242
)}
4343
>
4444
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
45-
<div className="relative flex min-h-screen flex-col p-2">
45+
<div className="relative flex min-h-screen flex-col">
4646
<SiteHeader />
4747
<div className="flex">{children}</div>
4848
</div>

app/page.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
21
import { JSONSchemaEditor } from "@/components/editor/json-schema-editor"
32
import { JSONValueEditor } from "@/components/editor/json-value-editor"
43

5-
export default function IndexPage() {
4+
export default function IndexPage({
5+
searchParams,
6+
}: {
7+
searchParams: Record<string, string>
8+
}) {
69
return (
7-
<section className="grid h-[90vh] w-full grid-cols-2 gap-2 pb-8">
8-
<div id="json-schema-editor" className="flex h-full flex-col overflow-scroll">
9-
<JSONSchemaEditor />
10+
<section className="grid h-[92vh] w-full grid-cols-2 gap-2 pb-8">
11+
<div
12+
id="json-schema-editor"
13+
className="flex h-full flex-col overflow-scroll "
14+
>
15+
<JSONSchemaEditor url={searchParams.url} />
1016
</div>
11-
<div id="json-value-editor" className="flex h-full flex-col overflow-scroll">
17+
<div
18+
id="json-value-editor"
19+
className="flex h-full flex-col overflow-scroll "
20+
>
1221
<JSONValueEditor />
1322
</div>
1423
</section>

components/editor/editor-pane.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import dynamic from "next/dynamic"
2+
import { SchemaState } from "@/store/main"
3+
4+
import { EditorMenu } from "./menu"
5+
6+
export interface EditorPane {
7+
heading: string
8+
editorKey: keyof SchemaState["editors"]
9+
schema?: Record<string, unknown>
10+
value?: string
11+
setValueString: (val: string) => void
12+
}
13+
14+
const JSONEditor = dynamic(
15+
async () => (await import("./json-editor")).JSONEditor,
16+
{ ssr: false }
17+
)
18+
19+
export const EditorPane = ({
20+
schema,
21+
editorKey,
22+
heading,
23+
value,
24+
setValueString,
25+
...props
26+
}: EditorPane) => {
27+
return (
28+
<>
29+
<div className="flex items-center justify-between rounded-lg">
30+
<h3 className="text-md pl-2 font-medium w-full">{heading}</h3>
31+
<EditorMenu
32+
heading={heading}
33+
editorKey={editorKey}
34+
value={value}
35+
setValueString={setValueString}
36+
/>
37+
</div>
38+
<JSONEditor
39+
onValueChange={setValueString}
40+
// value={editorValue}
41+
// json schema spec v? allow spec selection
42+
schema={schema}
43+
editorKey={editorKey}
44+
className="flex-1 overflow-auto"
45+
height="100%"
46+
value={value}
47+
{...props}
48+
/>
49+
</>
50+
)
51+
}

components/editor/json-editor.tsx

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,65 @@
11
"use client"
22

3-
import { useEffect, useRef, useState } from "react"
4-
import { autocompletion, closeBrackets } from "@codemirror/autocomplete"
3+
import { useEffect, useRef } from "react"
4+
import { SchemaState, useMainStore } from "@/store/main"
5+
import { autocompletion } from "@codemirror/autocomplete"
56
import { history } from "@codemirror/commands"
6-
import { bracketMatching, syntaxHighlighting } from "@codemirror/language"
7-
import { lintGutter } from "@codemirror/lint"
7+
import { syntaxHighlighting } from "@codemirror/language"
88
import { EditorState } from "@codemirror/state"
9-
import { oneDark, oneDarkHighlightStyle } from "@codemirror/theme-one-dark"
10-
import { EditorView, ViewUpdate, gutter, lineNumbers } from "@codemirror/view"
11-
import CodeMirror, { ReactCodeMirrorProps, ReactCodeMirrorRef } from "@uiw/react-codemirror"
12-
import { basicSetup } from "codemirror"
9+
import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark"
10+
import { EditorView } from "@codemirror/view"
11+
import CodeMirror, {
12+
ReactCodeMirrorProps,
13+
ReactCodeMirrorRef,
14+
} from "@uiw/react-codemirror"
1315
import { jsonSchema, updateSchema } from "codemirror-json-schema"
1416
// @ts-expect-error TODO: fix this in the lib!
1517
import { json5Schema } from "codemirror-json-schema/json5"
18+
import json5 from "json5"
19+
20+
import { JSONModes } from "@/types/editor"
21+
import { serialize } from "@/lib/json"
1622

1723
// import { debounce } from "@/lib/utils"
1824
import { jsonDark, jsonDarkTheme } from "./theme"
1925

20-
const jsonText = `{
21-
"example": true
22-
}`
23-
2426
/**
2527
* none of these are required for json4 or 5
2628
* but they will improve the DX
2729
*/
2830
const commonExtensions = [
29-
bracketMatching(),
30-
closeBrackets(),
3131
history(),
3232
autocompletion(),
33-
lineNumbers(),
34-
lintGutter(),
3533
jsonDark,
3634
EditorView.lineWrapping,
3735
EditorState.tabSize.of(2),
3836
syntaxHighlighting(oneDarkHighlightStyle),
3937
]
4038

41-
interface JSONEditorProps extends ReactCodeMirrorProps {
42-
value: string;
43-
onValueChange?: (newValue: string) => void;
44-
schema?: Record<string, unknown>;
45-
mode?: "json5" | "json4";
39+
const languageExtensions = {
40+
json4: jsonSchema,
41+
json5: json5Schema,
42+
}
43+
44+
export interface JSONEditorProps extends Omit<ReactCodeMirrorProps, 'value'> {
45+
onValueChange?: (newValue: string) => void
46+
schema?: Record<string, unknown>
47+
editorKey: keyof SchemaState["editors"]
48+
value?: string
4649
}
4750
export const JSONEditor = ({
48-
value,
4951
schema,
5052
onValueChange = () => {},
51-
mode = "json4",
53+
editorKey,
54+
value,
5255
...rest
5356
}: JSONEditorProps) => {
54-
const isJson5 = mode === "json5"
55-
const defaultExtensions = [
56-
...commonExtensions,
57-
isJson5 ? json5Schema(schema) : jsonSchema(schema),
58-
]
57+
const editorMode = useMainStore(
58+
(state) =>
59+
state.editors[editorKey as keyof SchemaState["editors"]].mode ??
60+
state.userSettings.mode
61+
)
62+
const languageExtension = languageExtensions[editorMode](schema)
5963
const editorRef = useRef<ReactCodeMirrorRef>(null)
6064

6165
useEffect(() => {
@@ -65,13 +69,15 @@ export const JSONEditor = ({
6569
updateSchema(editorRef?.current.view, schema)
6670
}, [schema])
6771

72+
6873
return (
6974
<CodeMirror
7075
value={value ?? "{}"}
71-
extensions={defaultExtensions}
76+
extensions={[...commonExtensions, languageExtension]}
7277
onChange={onValueChange}
7378
theme={jsonDarkTheme}
7479
ref={editorRef}
80+
contextMenu="true"
7581
{...rest}
7682
/>
7783
)
Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,38 @@
11
"use client"
22

33
import { useEffect } from "react"
4-
import dynamic from "next/dynamic"
54
import { useMainStore } from "@/store/main"
65

7-
import { Icons } from "../icons"
8-
import { Button } from "../ui/button"
6+
import { EditorPane } from "./editor-pane"
97

10-
const JSONEditor = dynamic(
11-
async () => (await import("./json-editor")).JSONEditor,
12-
{ ssr: false }
13-
)
14-
15-
export const JSONSchemaEditor = () => {
8+
export const JSONSchemaEditor = ({ url }: { url: string | null }) => {
169
const schemaSpec = useMainStore((state) => state.schemaSpec)
17-
const pristineSchema = useMainStore((state) => state.pristineSchema)
18-
const [loadIndex, setSchema] = useMainStore((state) => [
19-
state.loadIndex,
20-
state.setSchema,
21-
])
10+
const loadIndex = useMainStore((state) => state.loadIndex)
11+
12+
const setValueString = useMainStore((state) => state.setSchemaString)
13+
const value = useMainStore((state) => state.schemaString)
14+
const setSelectedSchema = useMainStore(
15+
(state) => state.setSelectedSchemaFromUrl
16+
)
2217

2318
useEffect(() => {
2419
loadIndex()
2520
}, [loadIndex])
21+
22+
useEffect(() => {
23+
if (url && url?.length && url.startsWith("http")) {
24+
setSelectedSchema(url)
25+
}
26+
}, [url])
27+
2628
return (
27-
<>
28-
<div className="flex items-center justify-between">
29-
<h3 className="text-lg font-semibold">Schema</h3>
30-
<div>
31-
<Button variant="ghost">
32-
<Icons.Hamburger className="h-5 w-5" />
33-
</Button>
34-
</div>
35-
</div>
36-
<JSONEditor
37-
onValueChange={(val) => setSchema(JSON.parse(val))}
38-
value={JSON.stringify(pristineSchema, null, 2)}
39-
// json schema spec v? allow spec selection
40-
schema={schemaSpec}
41-
className="flex-1 overflow-auto"
42-
height="100%"
43-
/>
44-
</>
29+
<EditorPane
30+
editorKey="schema"
31+
heading="Schema"
32+
// json schema spec v? allow spec selection
33+
schema={schemaSpec}
34+
setValueString={setValueString}
35+
value={value}
36+
/>
4537
)
4638
}
Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,21 @@
11
"use client"
22

3-
import dynamic from "next/dynamic"
43
import { useMainStore } from "@/store/main"
54

6-
import { Icons } from "../icons"
7-
import { Button } from "../ui/button"
8-
9-
const JSONEditor = dynamic(
10-
async () => (await import("./json-editor")).JSONEditor,
11-
{ ssr: false }
12-
)
5+
import { EditorPane } from "./editor-pane"
136

147
export const JSONValueEditor = () => {
158
const schema = useMainStore((state) => state.schema)
16-
return (
17-
<>
18-
<div className="flex items-center justify-between">
19-
<h3 className="text-lg font-semibold">Value</h3>
20-
<div>
21-
<Button variant="ghost">
22-
<Icons.Hamburger className="h-5 w-5" />
23-
</Button>
24-
</div>
25-
</div>
9+
const setValueString = useMainStore((state) => state.setTestValueString)
10+
const value = useMainStore((state) => state.testValueString)
2611

27-
<JSONEditor
28-
value={"{ }"}
29-
schema={schema}
30-
className="flex-1 overflow-auto"
31-
height="100%"
32-
/>
33-
</>
12+
return (
13+
<EditorPane
14+
editorKey="testValue"
15+
heading={"Test Value"}
16+
schema={schema}
17+
setValueString={setValueString}
18+
value={value}
19+
/>
3420
)
3521
}

0 commit comments

Comments
 (0)