-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #101 from gridaco/forms
Grida Forms Playground
- Loading branch information
Showing
52 changed files
with
8,875 additions
and
7,359 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { createServerComponentClient } from "@/lib/supabase/server"; | ||
import { cookies } from "next/headers"; | ||
import { notFound, redirect } from "next/navigation"; | ||
import Playground from "@/scaffolds/playground"; | ||
|
||
export default async function SharedPlaygroundPage({ | ||
params, | ||
}: { | ||
params: { | ||
slug: string; | ||
}; | ||
}) { | ||
const { slug } = params; | ||
const cookieStore = cookies(); | ||
const supabase = createServerComponentClient(cookieStore); | ||
|
||
const { data: _gist } = await supabase | ||
.from("gist") | ||
.select() | ||
.eq("slug", slug) | ||
.single(); | ||
|
||
if (!_gist) { | ||
return redirect("/playground"); | ||
} | ||
|
||
const { data, prompt } = _gist; | ||
|
||
return ( | ||
<main> | ||
<Playground | ||
initial={{ | ||
src: (data as any)?.["form.json"], | ||
prompt: prompt || undefined, | ||
slug: slug, | ||
}} | ||
/> | ||
</main> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,261 +1,9 @@ | ||
"use client"; | ||
|
||
import { | ||
Select, | ||
SelectContent, | ||
SelectItem, | ||
SelectTrigger, | ||
SelectValue, | ||
} from "@/components/ui/select"; | ||
import { FormView } from "@/scaffolds/e/form"; | ||
import { Editor as MonacoEditor, useMonaco } from "@monaco-editor/react"; | ||
import { useEffect, useMemo, useState } from "react"; | ||
import { nanoid } from "nanoid"; | ||
import { JSONForm } from "@/types/schema"; | ||
import resources from "@/k/i18n"; | ||
import Ajv from "ajv"; | ||
import { FormRenderer } from "@/lib/forms"; | ||
import { Label } from "@/components/ui/label"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Button } from "@/components/ui/button"; | ||
import { GridaLogo } from "@/components/grida-logo"; | ||
import { FormFieldAutocompleteType } from "@/types"; | ||
|
||
const HOST = process.env.NEXT_PUBLIC_HOST_NAME || "http://localhost:3000"; | ||
|
||
const examples = [ | ||
{ | ||
id: "001-hello-world", | ||
name: "Hello World", | ||
template: { | ||
schema: { | ||
src: `${HOST}/schema/examples/001-hello-world/form.json`, | ||
}, | ||
}, | ||
}, | ||
{ | ||
id: "002-iphone-pre-order", | ||
name: "iPhone Pre-Order", | ||
template: { | ||
schema: { | ||
src: `${HOST}/schema/examples/002-iphone-pre-order/form.json`, | ||
}, | ||
}, | ||
}, | ||
] as const; | ||
|
||
type MaybeArray<T> = T | T[]; | ||
|
||
function toArrayOf<T>(value: MaybeArray<T>, nofalsy = true): NonNullable<T>[] { | ||
return ( | ||
Array.isArray(value) ? value : nofalsy && value ? [value] : [] | ||
) as NonNullable<T>[]; | ||
} | ||
|
||
function parse(txt?: string): JSONForm | null { | ||
try { | ||
return txt ? JSON.parse(txt) : null; | ||
} catch (error) { | ||
return null; | ||
} | ||
} | ||
|
||
function compile(txt?: string) { | ||
const schema = parse(txt); | ||
if (!schema) { | ||
return; | ||
} | ||
|
||
const renderer = new FormRenderer( | ||
nanoid(), | ||
schema.fields?.map((f, i) => ({ | ||
...f, | ||
id: f.name, | ||
autocomplete: toArrayOf<FormFieldAutocompleteType | undefined>( | ||
f.autocomplete | ||
), | ||
required: f.required || false, | ||
local_index: i, | ||
options: | ||
f.options?.map((o) => ({ | ||
...o, | ||
id: o.value, | ||
})) || [], | ||
})) || [], | ||
[] | ||
); | ||
|
||
return renderer; | ||
} | ||
import Playground from "@/scaffolds/playground"; | ||
|
||
export default function FormsPlayground() { | ||
const [action, setAction] = useState<string>(""); | ||
const [method, setMethod] = useState<string>("get"); | ||
const [exampleId, setExampleId] = useState<string>(examples[0].id); | ||
const [__schema_txt, __set_schema_txt] = useState<string | undefined>(); | ||
|
||
const renderer: FormRenderer | undefined = useMemo( | ||
() => compile(__schema_txt), | ||
[__schema_txt] | ||
); | ||
|
||
useEffect(() => { | ||
if (exampleId) { | ||
fetch(examples.find((e) => e.id === exampleId)!.template.schema.src) | ||
.then((res) => res.text()) | ||
.then((schema) => { | ||
__set_schema_txt(schema); | ||
}); | ||
} | ||
}, [exampleId]); | ||
|
||
return ( | ||
<main className="w-screen h-screen flex flex-col overflow-hidden"> | ||
<header className="p-4 flex justify-between"> | ||
<div className="flex gap-4"> | ||
<h1 className="text-xl font-black flex items-center gap-2"> | ||
<GridaLogo /> | ||
Forms | ||
<span className="font-mono text-sm px-3 py-1 rounded-md bg-black/45 text-white"> | ||
Playground | ||
</span> | ||
</h1> | ||
<div className="ms-10"> | ||
<Select | ||
value={exampleId} | ||
onValueChange={(value) => setExampleId(value)} | ||
> | ||
<SelectTrigger id="method" aria-label="select method"> | ||
<SelectValue placeholder="Select method" /> | ||
</SelectTrigger> | ||
<SelectContent> | ||
{examples.map((example) => ( | ||
<SelectItem key={example.id} value={example.id}> | ||
{example.name} | ||
</SelectItem> | ||
))} | ||
</SelectContent> | ||
</Select> | ||
</div> | ||
</div> | ||
</header> | ||
<div className="flex-1 flex"> | ||
<section className="flex-1"> | ||
<div className="w-full h-full p-4"> | ||
<div className="w-full h-full rounded-md overflow-hidden shadow"> | ||
<Editor value={__schema_txt} onChange={__set_schema_txt} /> | ||
</div> | ||
<details className="bg-white absolute bottom-0 left-0 max-h-96 overflow-scroll z-10"> | ||
<summary>Renderer JSON</summary> | ||
<pre> | ||
<code>{JSON.stringify(renderer, null, 2)}</code> | ||
</pre> | ||
</details> | ||
</div> | ||
</section> | ||
<section className="flex-1"> | ||
<div className="px-4"> | ||
<header className="py-4 flex flex-col"> | ||
<div className="flex items-end gap-2"> | ||
<Label> | ||
Method | ||
<Select | ||
value={method} | ||
onValueChange={(value) => setMethod(value)} | ||
> | ||
<SelectTrigger id="method" aria-label="select method"> | ||
<SelectValue placeholder="Select method" /> | ||
</SelectTrigger> | ||
<SelectContent> | ||
<SelectItem value="get">GET</SelectItem> | ||
<SelectItem value="post">POST</SelectItem> | ||
<SelectItem value="put">PUT</SelectItem> | ||
<SelectItem value="delete">DELETE</SelectItem> | ||
</SelectContent> | ||
</Select> | ||
</Label> | ||
<Label> | ||
Action | ||
<Input | ||
type="text" | ||
placeholder="https://forms.grida.co/submit/..." | ||
value={action} | ||
onChange={(e) => setAction(e.target.value)} | ||
/> | ||
</Label> | ||
<Button>Submit</Button> | ||
</div> | ||
</header> | ||
<div className="w-full min-h-40 rounded-lg shadow-md border-dashed flex flex-col items-center"> | ||
{renderer ? ( | ||
<FormView | ||
title={"Form"} | ||
form_id={renderer.id} | ||
fields={renderer.fields()} | ||
blocks={renderer.blocks()} | ||
tree={renderer.tree()} | ||
translation={resources.en.translation as any} | ||
options={{ | ||
is_powered_by_branding_enabled: false, | ||
}} | ||
/> | ||
) : ( | ||
<div className="grow flex items-center justify-center p-4 text-center text-gray-500"> | ||
Invalid schema | ||
</div> | ||
)} | ||
</div> | ||
<form action={action} method={method}> | ||
{/* */} | ||
</form> | ||
</div> | ||
</section> | ||
</div> | ||
<main> | ||
<Playground /> | ||
</main> | ||
); | ||
} | ||
|
||
const schema = { | ||
uri: "https://forms.grida.co/schema/form.schema.json", | ||
fileMatch: ["*"], // Associate with all JSON files | ||
}; | ||
|
||
function Editor({ | ||
value, | ||
onChange, | ||
}: { | ||
value?: string; | ||
onChange?: (value?: string) => void; | ||
}) { | ||
const monaco = useMonaco(); | ||
|
||
useEffect(() => { | ||
monaco?.languages.json.jsonDefaults.setDiagnosticsOptions({ | ||
validate: true, | ||
enableSchemaRequest: true, | ||
schemas: [schema], | ||
}); | ||
}, [monaco]); | ||
|
||
return ( | ||
<div className="font-mono flex-1 flex flex-col w-full h-full"> | ||
<header className="p-2"> | ||
<h2 className="">form.json</h2> | ||
</header> | ||
<MonacoEditor | ||
height={"100%"} | ||
defaultLanguage="json" | ||
onChange={onChange} | ||
value={value} | ||
options={{ | ||
padding: { | ||
top: 16, | ||
}, | ||
minimap: { | ||
enabled: false, | ||
}, | ||
}} | ||
/> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.