Skip to content

Commit

Permalink
context menu, json5 toggle (#5)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
acao authored Nov 13, 2023
1 parent 6846f6b commit 06b7fba
Show file tree
Hide file tree
Showing 23 changed files with 972 additions and 219 deletions.
8 changes: 5 additions & 3 deletions app/api/schema/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ async function getSchema(url: string) {
}

export async function GET(request: Request) {
const {searchParams} = new URL(request.url);
const { searchParams } = new URL(request.url)

try {
const url = searchParams.get("url")
if (!url) {

return new Response("No schema key provided", {
status: 400,
})
}
const schema = await getSchema(url)
return new Response(schema, {
headers: { "content-type": "application/json" },
headers: {
"content-type": "application/json",
// "cache-control": "s-maxage=1440000",
},
})
} catch (e) {
return new Response(
Expand Down
5 changes: 4 additions & 1 deletion app/api/schemas/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ async function getSchemas() {

export async function GET(request: Request) {
return new Response(await getSchemas(), {
headers: { "content-type": "application/json" },
headers: {
"content-type": "application/json",
// "cache-control": "s-maxage=1440000",
},
})
}
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
)}
>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<div className="relative flex min-h-screen flex-col p-2">
<div className="relative flex min-h-screen flex-col">
<SiteHeader />
<div className="flex">{children}</div>
</div>
Expand Down
21 changes: 15 additions & 6 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@

import { JSONSchemaEditor } from "@/components/editor/json-schema-editor"
import { JSONValueEditor } from "@/components/editor/json-value-editor"

export default function IndexPage() {
export default function IndexPage({
searchParams,
}: {
searchParams: Record<string, string>
}) {
return (
<section className="grid h-[90vh] w-full grid-cols-2 gap-2 pb-8">
<div id="json-schema-editor" className="flex h-full flex-col overflow-scroll">
<JSONSchemaEditor />
<section className="grid h-[92vh] w-full grid-cols-2 gap-2 pb-8">
<div
id="json-schema-editor"
className="flex h-full flex-col overflow-scroll "
>
<JSONSchemaEditor url={searchParams.url} />
</div>
<div id="json-value-editor" className="flex h-full flex-col overflow-scroll">
<div
id="json-value-editor"
className="flex h-full flex-col overflow-scroll "
>
<JSONValueEditor />
</div>
</section>
Expand Down
51 changes: 51 additions & 0 deletions components/editor/editor-pane.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import dynamic from "next/dynamic"
import { SchemaState } from "@/store/main"

import { EditorMenu } from "./menu"

export interface EditorPane {
heading: string
editorKey: keyof SchemaState["editors"]
schema?: Record<string, unknown>
value?: string
setValueString: (val: string) => void
}

const JSONEditor = dynamic(
async () => (await import("./json-editor")).JSONEditor,
{ ssr: false }
)

export const EditorPane = ({
schema,
editorKey,
heading,
value,
setValueString,
...props
}: EditorPane) => {
return (
<>
<div className="flex items-center justify-between rounded-lg">
<h3 className="text-md pl-2 font-medium w-full">{heading}</h3>
<EditorMenu
heading={heading}
editorKey={editorKey}
value={value}
setValueString={setValueString}
/>
</div>
<JSONEditor
onValueChange={setValueString}
// value={editorValue}
// json schema spec v? allow spec selection
schema={schema}
editorKey={editorKey}
className="flex-1 overflow-auto"
height="100%"
value={value}
{...props}
/>
</>
)
}
64 changes: 35 additions & 29 deletions components/editor/json-editor.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,65 @@
"use client"

import { useEffect, useRef, useState } from "react"
import { autocompletion, closeBrackets } from "@codemirror/autocomplete"
import { useEffect, useRef } from "react"
import { SchemaState, useMainStore } from "@/store/main"
import { autocompletion } from "@codemirror/autocomplete"
import { history } from "@codemirror/commands"
import { bracketMatching, syntaxHighlighting } from "@codemirror/language"
import { lintGutter } from "@codemirror/lint"
import { syntaxHighlighting } from "@codemirror/language"
import { EditorState } from "@codemirror/state"
import { oneDark, oneDarkHighlightStyle } from "@codemirror/theme-one-dark"
import { EditorView, ViewUpdate, gutter, lineNumbers } from "@codemirror/view"
import CodeMirror, { ReactCodeMirrorProps, ReactCodeMirrorRef } from "@uiw/react-codemirror"
import { basicSetup } from "codemirror"
import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark"
import { EditorView } from "@codemirror/view"
import CodeMirror, {
ReactCodeMirrorProps,
ReactCodeMirrorRef,
} from "@uiw/react-codemirror"
import { jsonSchema, updateSchema } from "codemirror-json-schema"
// @ts-expect-error TODO: fix this in the lib!
import { json5Schema } from "codemirror-json-schema/json5"
import json5 from "json5"

import { JSONModes } from "@/types/editor"
import { serialize } from "@/lib/json"

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

const jsonText = `{
"example": true
}`

/**
* none of these are required for json4 or 5
* but they will improve the DX
*/
const commonExtensions = [
bracketMatching(),
closeBrackets(),
history(),
autocompletion(),
lineNumbers(),
lintGutter(),
jsonDark,
EditorView.lineWrapping,
EditorState.tabSize.of(2),
syntaxHighlighting(oneDarkHighlightStyle),
]

interface JSONEditorProps extends ReactCodeMirrorProps {
value: string;
onValueChange?: (newValue: string) => void;
schema?: Record<string, unknown>;
mode?: "json5" | "json4";
const languageExtensions = {
json4: jsonSchema,
json5: json5Schema,
}

export interface JSONEditorProps extends Omit<ReactCodeMirrorProps, 'value'> {
onValueChange?: (newValue: string) => void
schema?: Record<string, unknown>
editorKey: keyof SchemaState["editors"]
value?: string
}
export const JSONEditor = ({
value,
schema,
onValueChange = () => {},
mode = "json4",
editorKey,
value,
...rest
}: JSONEditorProps) => {
const isJson5 = mode === "json5"
const defaultExtensions = [
...commonExtensions,
isJson5 ? json5Schema(schema) : jsonSchema(schema),
]
const editorMode = useMainStore(
(state) =>
state.editors[editorKey as keyof SchemaState["editors"]].mode ??
state.userSettings.mode
)
const languageExtension = languageExtensions[editorMode](schema)
const editorRef = useRef<ReactCodeMirrorRef>(null)

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


return (
<CodeMirror
value={value ?? "{}"}
extensions={defaultExtensions}
extensions={[...commonExtensions, languageExtension]}
onChange={onValueChange}
theme={jsonDarkTheme}
ref={editorRef}
contextMenu="true"
{...rest}
/>
)
Expand Down
56 changes: 24 additions & 32 deletions components/editor/json-schema-editor.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,38 @@
"use client"

import { useEffect } from "react"
import dynamic from "next/dynamic"
import { useMainStore } from "@/store/main"

import { Icons } from "../icons"
import { Button } from "../ui/button"
import { EditorPane } from "./editor-pane"

const JSONEditor = dynamic(
async () => (await import("./json-editor")).JSONEditor,
{ ssr: false }
)

export const JSONSchemaEditor = () => {
export const JSONSchemaEditor = ({ url }: { url: string | null }) => {
const schemaSpec = useMainStore((state) => state.schemaSpec)
const pristineSchema = useMainStore((state) => state.pristineSchema)
const [loadIndex, setSchema] = useMainStore((state) => [
state.loadIndex,
state.setSchema,
])
const loadIndex = useMainStore((state) => state.loadIndex)

const setValueString = useMainStore((state) => state.setSchemaString)
const value = useMainStore((state) => state.schemaString)
const setSelectedSchema = useMainStore(
(state) => state.setSelectedSchemaFromUrl
)

useEffect(() => {
loadIndex()
}, [loadIndex])

useEffect(() => {
if (url && url?.length && url.startsWith("http")) {
setSelectedSchema(url)
}
}, [url])

return (
<>
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold">Schema</h3>
<div>
<Button variant="ghost">
<Icons.Hamburger className="h-5 w-5" />
</Button>
</div>
</div>
<JSONEditor
onValueChange={(val) => setSchema(JSON.parse(val))}
value={JSON.stringify(pristineSchema, null, 2)}
// json schema spec v? allow spec selection
schema={schemaSpec}
className="flex-1 overflow-auto"
height="100%"
/>
</>
<EditorPane
editorKey="schema"
heading="Schema"
// json schema spec v? allow spec selection
schema={schemaSpec}
setValueString={setValueString}
value={value}
/>
)
}
36 changes: 11 additions & 25 deletions components/editor/json-value-editor.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
"use client"

import dynamic from "next/dynamic"
import { useMainStore } from "@/store/main"

import { Icons } from "../icons"
import { Button } from "../ui/button"

const JSONEditor = dynamic(
async () => (await import("./json-editor")).JSONEditor,
{ ssr: false }
)
import { EditorPane } from "./editor-pane"

export const JSONValueEditor = () => {
const schema = useMainStore((state) => state.schema)
return (
<>
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold">Value</h3>
<div>
<Button variant="ghost">
<Icons.Hamburger className="h-5 w-5" />
</Button>
</div>
</div>
const setValueString = useMainStore((state) => state.setTestValueString)
const value = useMainStore((state) => state.testValueString)

<JSONEditor
value={"{ }"}
schema={schema}
className="flex-1 overflow-auto"
height="100%"
/>
</>
return (
<EditorPane
editorKey="testValue"
heading={"Test Value"}
schema={schema}
setValueString={setValueString}
value={value}
/>
)
}
Loading

0 comments on commit 06b7fba

Please sign in to comment.