MapComplete/src/UI/Studio/RawEditor.svelte

138 lines
3.5 KiB
Svelte

<script lang="ts">
import { onDestroy, onMount } from "svelte"
import EditLayerState, { EditThemeState } from "./EditLayerState"
import loader from "@monaco-editor/loader"
import type * as Monaco from "monaco-editor/esm/vs/editor/editor.api"
import layerSchemaJSON from "../../../Docs/Schemas/LayerConfigJson.schema.json"
import layoutSchemaJSON from "../../../Docs/Schemas/LayoutConfigJson.schema.json"
import Loading from "../Base/Loading.svelte"
export let state: EditLayerState | EditThemeState
let rawConfig = state.configuration.sync(
(f) => JSON.stringify(f, null, " "),
[],
(json) => {
try {
return JSON.parse(json)
} catch (e) {
console.error("Could not parse", json)
return undefined
}
}
)
let container: HTMLDivElement
let monaco: typeof Monaco
let editor: Monaco.editor.IStandaloneCodeEditor
let model: Monaco.editor.ITextModel
function save() {
try {
const newConfig = JSON.parse(editor.getValue())
state.configuration.setData(newConfig)
} catch (e) {
console.error(e)
}
}
// Catch keyboard shortcuts
onMount(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "s" && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
save()
}
}
window.addEventListener("keydown", handler)
return () => window.removeEventListener("keydown", handler)
})
let useFallback = false
let isLoaded = false
onMount(async () => {
const monacoEditor = await import("monaco-editor")
loader.config({
monaco: monacoEditor.default,
})
try {
monaco = await loader.init()
} catch (e) {
console.error("Could not load Monaco Editor, falling back", e)
useFallback = true
}
// Determine schema based on the state
let schemaUri: string
if (state instanceof EditLayerState) {
schemaUri = "https://mapcomplete.org/schemas/layerconfig.json"
} else {
schemaUri = "https://mapcomplete.org/schemas/layoutconfig.json"
}
monaco?.languages?.json?.jsonDefaults?.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri: schemaUri,
fileMatch: ["file.json"],
schema:
schemaUri === "https://mapcomplete.org/schemas/layerconfig.json"
? layerSchemaJSON
: layoutSchemaJSON,
},
],
})
let modelUri = monaco?.Uri?.parse("inmemory://inmemory/file.json")
// Create a new model
try {
model = monaco?.editor?.createModel(
JSON.stringify(state.configuration.data, null, " "),
"json",
modelUri
)
} catch (e) {
console.error("Could not create model in MOnaco Editor", e)
useFallback = true
}
editor = monaco?.editor?.create(container, {
model,
automaticLayout: true,
})
// When the editor is changed, update the configuration, but only if the user hasn't typed for 500ms and the JSON is valid
let timeout: number
editor?.onDidChangeModelContent(() => {
clearTimeout(timeout)
timeout = setTimeout(() => {
save()
}, 500)
})
isLoaded = true
})
onDestroy(() => {
if (editor) {
editor.dispose()
}
if (model) {
model.dispose()
}
})
</script>
{#if useFallback}
<textarea class="w-full" rows="25" bind:value={$rawConfig} />
{:else}
<div bind:this={container} class="h-full w-full">
{#if !isLoaded}
<div class="align-center flex h-full w-full items-center">
<Loading />
</div>
{/if}
</div>
{/if}