Split out editor, add to theme editing
This commit is contained in:
parent
2c018d7af3
commit
a8313022a0
3 changed files with 116 additions and 72 deletions
|
@ -13,24 +13,15 @@
|
|||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import FloatOver from "../Base/FloatOver.svelte"
|
||||
import TagRenderingInput from "./TagRenderingInput.svelte"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import AllTagsPanel from "../Popup/AllTagsPanel.svelte"
|
||||
import QuestionPreview from "./QuestionPreview.svelte"
|
||||
import ShowConversionMessages from "./ShowConversionMessages.svelte"
|
||||
import loader from "@monaco-editor/loader"
|
||||
import type * as Monaco from "monaco-editor/esm/vs/editor/editor.api"
|
||||
import { onMount } from "svelte"
|
||||
import layerSchemaJSON from "../../../Docs/Schemas/LayerConfigJson.schema.json"
|
||||
import RawEditor from "./RawEditor.svelte"
|
||||
|
||||
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw
|
||||
|
||||
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
|
||||
let messages = state.messages
|
||||
let hasErrors = messages.mapD(
|
||||
|
@ -69,7 +60,7 @@
|
|||
}
|
||||
|
||||
let requiredFields = ["id", "name", "description", "source"]
|
||||
let currentlyMissing = state.configuration.map((config) => {
|
||||
let currentlyMissing = configuration.map((config) => {
|
||||
if (!config) {
|
||||
return []
|
||||
}
|
||||
|
@ -88,61 +79,6 @@
|
|||
state.delete()
|
||||
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>
|
||||
|
||||
<div class="flex h-screen flex-col">
|
||||
|
@ -191,7 +127,7 @@
|
|||
{/each}
|
||||
{:else}
|
||||
<div class="m4 h-full overflow-y-auto">
|
||||
<TabbedGroup bind:this={tabbedGroup}>
|
||||
<TabbedGroup>
|
||||
<div slot="title0" class="flex">
|
||||
General properties
|
||||
<ErrorIndicatorForRegion firstPaths={firstPathsFor("Basic")} {state} />
|
||||
|
@ -254,7 +190,9 @@
|
|||
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-64 w-full" bind:this={editorContainer} />
|
||||
<div class="literal-code h-64 w-full">
|
||||
<RawEditor {state} />
|
||||
</div>
|
||||
|
||||
<ShowConversionMessages messages={$messages} />
|
||||
<div>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import TabbedGroup from "../Base/TabbedGroup.svelte"
|
||||
import ShowConversionMessages from "./ShowConversionMessages.svelte"
|
||||
import Region from "./Region.svelte"
|
||||
import RawEditor from "./RawEditor.svelte"
|
||||
|
||||
export let state: EditThemeState
|
||||
let schema: ConfigMeta[] = state.schema.filter((schema) => schema.path.length > 0)
|
||||
|
@ -20,7 +21,7 @@
|
|||
|
||||
const perRegion: Record<string, ConfigMeta[]> = {}
|
||||
for (const schemaElement of schema) {
|
||||
if(schemaElement.path.length > 1 && schemaElement.path[0] === "layers"){
|
||||
if (schemaElement.path.length > 1 && schemaElement.path[0] === "layers") {
|
||||
continue
|
||||
}
|
||||
const key = schemaElement.hints.group ?? "no-group"
|
||||
|
@ -73,9 +74,13 @@
|
|||
</div>
|
||||
|
||||
<div slot="title4">Configuration file</div>
|
||||
<div slot="content4">
|
||||
<div class="literal-code">
|
||||
{JSON.stringify($config)}
|
||||
<div slot="content4" class="h-full">
|
||||
<div>
|
||||
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>
|
||||
|
||||
<ShowConversionMessages messages={$messages} />
|
||||
|
|
101
src/UI/Studio/RawEditor.svelte
Normal file
101
src/UI/Studio/RawEditor.svelte
Normal 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" />
|
Loading…
Reference in a new issue