diff options
Diffstat (limited to 'source/library/components/Editor.svelte')
| -rw-r--r-- | source/library/components/Editor.svelte | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/source/library/components/Editor.svelte b/source/library/components/Editor.svelte new file mode 100644 index 0000000..f2bf82d --- /dev/null +++ b/source/library/components/Editor.svelte @@ -0,0 +1,136 @@ +<script lang="ts"> + /*** IMPORT ------------------------------------------- ***/ + + import { browser } from "$app/environment"; + import { onMount } from "svelte"; + import type { Extension } from "@codemirror/state"; + import type { EditorView } from "@codemirror/view"; + import type { GraphQLSchema } from "graphql"; + + /*** UTILITY ------------------------------------------ ***/ + + type Props = { + language?: "graphql" | "json"; + onChange: (value: string) => void; + readOnly?: boolean; + schema?: string; + theme?: Extension; + value: string; + }; + + let { + language = "graphql", + onChange, + readOnly = false, + schema, + theme, + value + }: Props = $props(); + + let buildSchemaFn = $state<((sdl: string) => GraphQLSchema) | null>(null); + let container: HTMLDivElement; + let updateSchemaFn = $state<((v: EditorView, s: GraphQLSchema) => void) | null>(null); + let view = $state<EditorView | null>(null); + + onMount(() => { + if (!browser) + return; + + let disposed = false; + + (async () => { + const [ + { EditorView: EV, keymap, lineNumbers, highlightActiveLine }, + { EditorState }, + { defaultKeymap, history, historyKeymap }, + { syntaxHighlighting, defaultHighlightStyle, bracketMatching, indentOnInput }, + { closeBrackets, closeBracketsKeymap }, + { graphql, updateSchema }, + { json }, + { buildSchema } + ] = await Promise.all([ + import("@codemirror/view"), + import("@codemirror/state"), + import("@codemirror/commands"), + import("@codemirror/language"), + import("@codemirror/autocomplete"), + import("cm6-graphql"), + import("@codemirror/lang-json"), + import("graphql") + ]); + + if (disposed) + return; + + const themeExt: Extension = theme ?? (await import("@codemirror/theme-one-dark")).oneDark; + + const languageExt = language === "graphql" ? + graphql(schema ? buildSchema(schema) : undefined) : + json(); + + const instance = new EV({ + parent: container, + state: EditorState.create({ + doc: value, + extensions: [ + lineNumbers(), + highlightActiveLine(), + history(), + bracketMatching(), + closeBrackets(), + indentOnInput(), + syntaxHighlighting(defaultHighlightStyle, { fallback: true }), + keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...historyKeymap]), + languageExt, + themeExt, + EV.editable.of(!readOnly), + EV.updateListener.of((u) => { + if (u.docChanged) + onChange(u.state.doc.toString()); + }) + ] + }) + }); + + view = instance; + updateSchemaFn = updateSchema; + buildSchemaFn = buildSchema; + })(); + + return () => { + disposed = true; + view?.destroy(); + }; + }); + + $effect(() => { + if (!view || !updateSchemaFn || !buildSchemaFn) + return; + + if (language !== "graphql" || !schema) + return; + + try { + updateSchemaFn(view, buildSchemaFn(schema)); + } catch { + // Invalid SDL — silently skip; editor keeps working without schema awareness + } + }); +</script> + +<style lang="scss"> + .editor { + height: 100%; + width: 100%; + + :global(.cm-editor) { + height: 100%; + } + + :global(.cm-scroller) { + font-family: inherit; + } + } +</style> + +<div bind:this={container} class="editor"></div> |