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 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>
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
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