Split out editor, add to theme editing

This commit is contained in:
Robin van der Linde 2024-02-13 11:44:09 +01:00
parent 2c018d7af3
commit a8313022a0
No known key found for this signature in database
GPG key ID: 53956B3252478F0D
3 changed files with 116 additions and 72 deletions

View file

@ -13,24 +13,15 @@
import SchemaBasedInput from "./SchemaBasedInput.svelte" import SchemaBasedInput from "./SchemaBasedInput.svelte"
import FloatOver from "../Base/FloatOver.svelte" import FloatOver from "../Base/FloatOver.svelte"
import TagRenderingInput from "./TagRenderingInput.svelte" import TagRenderingInput from "./TagRenderingInput.svelte"
import FromHtml from "../Base/FromHtml.svelte"
import AllTagsPanel from "../Popup/AllTagsPanel.svelte" import AllTagsPanel from "../Popup/AllTagsPanel.svelte"
import QuestionPreview from "./QuestionPreview.svelte" import QuestionPreview from "./QuestionPreview.svelte"
import ShowConversionMessages from "./ShowConversionMessages.svelte" import ShowConversionMessages from "./ShowConversionMessages.svelte"
import loader from "@monaco-editor/loader" import RawEditor from "./RawEditor.svelte"
import type * as Monaco from "monaco-editor/esm/vs/editor/editor.api"
import { onMount } from "svelte"
import layerSchemaJSON from "../../../Docs/Schemas/LayerConfigJson.schema.json"
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw const layerSchema: ConfigMeta[] = <any>layerSchemaRaw
export let state: EditLayerState export let state: EditLayerState
// Throw error if we don't have a state
if (!state) {
throw new Error("No state provided")
}
export let backToStudio: () => void export let backToStudio: () => void
let messages = state.messages let messages = state.messages
let hasErrors = messages.mapD( let hasErrors = messages.mapD(
@ -69,7 +60,7 @@
} }
let requiredFields = ["id", "name", "description", "source"] let requiredFields = ["id", "name", "description", "source"]
let currentlyMissing = state.configuration.map((config) => { let currentlyMissing = configuration.map((config) => {
if (!config) { if (!config) {
return [] return []
} }
@ -88,61 +79,6 @@
state.delete() state.delete()
backToStudio() backToStudio()
} }
let tabbedGroup: TabbedGroup
let openTab: UIEventSource<number> = new UIEventSource<number>(0)
let monaco: typeof Monaco
let editorContainer: HTMLDivElement
let layerEditor: Monaco.editor.IStandaloneCodeEditor
let model: Monaco.editor.ITextModel
onMount(async () => {
openTab = tabbedGroup.getTab()
const monacoEditor = await import("monaco-editor")
loader.config({ monaco: monacoEditor.default })
monaco = await loader.init()
// Prepare the Monaco editor (language settings)
// A.K.A. The schemas for the Monaco editor
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri: "https://mapcomplete.org/schemas/layerconfig.json",
fileMatch: ["layer.json"],
schema: layerSchemaJSON,
},
],
})
let modelUri = monaco.Uri.parse("inmemory://inmemory/layer.json")
model = monaco.editor.createModel(
JSON.stringify(state.configuration.data, null, " "),
"json",
modelUri
)
layerEditor = monaco.editor.create(editorContainer, {
model: 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
layerEditor.onDidChangeModelContent(() => {
clearTimeout(timeout)
timeout = setTimeout(() => {
try {
const newConfig = JSON.parse(layerEditor.getValue())
state.configuration.setData(newConfig)
} catch (e) {
console.error(e)
}
}, 500)
})
})
</script> </script>
<div class="flex h-screen flex-col"> <div class="flex h-screen flex-col">
@ -191,7 +127,7 @@
{/each} {/each}
{:else} {:else}
<div class="m4 h-full overflow-y-auto"> <div class="m4 h-full overflow-y-auto">
<TabbedGroup bind:this={tabbedGroup}> <TabbedGroup>
<div slot="title0" class="flex"> <div slot="title0" class="flex">
General properties General properties
<ErrorIndicatorForRegion firstPaths={firstPathsFor("Basic")} {state} /> <ErrorIndicatorForRegion firstPaths={firstPathsFor("Basic")} {state} />
@ -254,7 +190,9 @@
Below, you'll find the raw configuration file in `.json`-format. This is mostly for Below, you'll find the raw configuration file in `.json`-format. This is mostly for
debugging purposes, but you can also edit the file directly if you want. debugging purposes, but you can also edit the file directly if you want.
</div> </div>
<div class="literal-code h-64 w-full" bind:this={editorContainer} /> <div class="literal-code h-64 w-full">
<RawEditor {state} />
</div>
<ShowConversionMessages messages={$messages} /> <ShowConversionMessages messages={$messages} />
<div> <div>

View file

@ -6,6 +6,7 @@
import TabbedGroup from "../Base/TabbedGroup.svelte" import TabbedGroup from "../Base/TabbedGroup.svelte"
import ShowConversionMessages from "./ShowConversionMessages.svelte" import ShowConversionMessages from "./ShowConversionMessages.svelte"
import Region from "./Region.svelte" import Region from "./Region.svelte"
import RawEditor from "./RawEditor.svelte"
export let state: EditThemeState export let state: EditThemeState
let schema: ConfigMeta[] = state.schema.filter((schema) => schema.path.length > 0) let schema: ConfigMeta[] = state.schema.filter((schema) => schema.path.length > 0)
@ -20,7 +21,7 @@
const perRegion: Record<string, ConfigMeta[]> = {} const perRegion: Record<string, ConfigMeta[]> = {}
for (const schemaElement of schema) { for (const schemaElement of schema) {
if(schemaElement.path.length > 1 && schemaElement.path[0] === "layers"){ if (schemaElement.path.length > 1 && schemaElement.path[0] === "layers") {
continue continue
} }
const key = schemaElement.hints.group ?? "no-group" const key = schemaElement.hints.group ?? "no-group"
@ -73,9 +74,13 @@
</div> </div>
<div slot="title4">Configuration file</div> <div slot="title4">Configuration file</div>
<div slot="content4"> <div slot="content4" class="h-full">
<div class="literal-code"> <div>
{JSON.stringify($config)} Below, you'll find the raw configuration file in `.json`-format. This is mostly for
debugging purposes, but you can also edit the file directly if you want.
</div>
<div class="literal-code h-full w-full">
<RawEditor {state} />
</div> </div>
<ShowConversionMessages messages={$messages} /> <ShowConversionMessages messages={$messages} />

View file

@ -0,0 +1,101 @@
<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"
export let state: EditLayerState | EditThemeState
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)
})
onMount(async () => {
const monacoEditor = await import("monaco-editor")
loader.config({
monaco: monacoEditor.default,
})
monaco = await loader.init()
// 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
model = monaco.editor.createModel(
JSON.stringify(state.configuration.data, null, " "),
"json",
modelUri
)
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)
})
})
onDestroy(() => {
if (editor) {
editor.dispose()
}
if (model) {
model.dispose()
}
})
</script>
<div bind:this={container} class="h-full w-full" />