forked from MapComplete/MapComplete
Add various improvements and fixes to studio, should fix #2055
This commit is contained in:
parent
b19d9ef077
commit
d1ec9a43fc
19 changed files with 532 additions and 419 deletions
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<Accordion>
|
||||
<AccordionItem open={expanded} paddingDefault="p-0" inactiveClass="text-black">
|
||||
<span slot="header" class="p-2 text-base">
|
||||
<span slot="header" class="p-2 text-base w-full">
|
||||
<slot name="header" />
|
||||
</span>
|
||||
<div class="low-interaction rounded-b p-2">
|
||||
|
|
|
|||
|
|
@ -373,7 +373,6 @@
|
|||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
{extraTags}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
unvalidatedText={freeformInputUnvalidated}
|
||||
|
|
@ -418,7 +417,6 @@
|
|||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
{extraTags}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
unvalidatedText={freeformInputUnvalidated}
|
||||
|
|
@ -464,7 +462,6 @@
|
|||
{feedback}
|
||||
{unit}
|
||||
{state}
|
||||
{extraTags}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
unvalidatedText={freeformInputUnvalidated}
|
||||
|
|
|
|||
207
src/UI/Studio/CollapsedTagRenderingPreview.svelte
Normal file
207
src/UI/Studio/CollapsedTagRenderingPreview.svelte
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<script lang="ts">
|
||||
|
||||
import Translations from "../i18n/Translations"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import Icon from "../Map/Icon.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import QuestionPreview from "./QuestionPreview.svelte"
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte"
|
||||
import { EditJsonState } from "./EditLayerState"
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson,
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import { AccordionItem } from "flowbite-svelte"
|
||||
|
||||
export let state: EditJsonState<any>
|
||||
|
||||
export let isTagRenderingBlock: boolean
|
||||
export let schema: ConfigMeta
|
||||
export let currentValue: UIEventSource<(string | QuestionableTagRenderingConfigJson)[]>
|
||||
|
||||
export let value: any
|
||||
export let singular: string
|
||||
export let i: number
|
||||
export let path: (string | number)[] = []
|
||||
|
||||
export let expanded = new UIEventSource(false)
|
||||
|
||||
const subparts: ConfigMeta[] = state
|
||||
.getSchemaStartingWith(schema.path)
|
||||
.filter((part) => part.path.length - 1 === schema.path.length)
|
||||
|
||||
function schemaForMultitype() {
|
||||
const sch = { ...schema }
|
||||
sch.hints.typehint = undefined
|
||||
return sch
|
||||
}
|
||||
|
||||
function fusePath(i: number, subpartPath: string[]): (string | number)[] {
|
||||
const newPath = [...path, i]
|
||||
const toAdd = [...subpartPath]
|
||||
for (const part of path) {
|
||||
if (toAdd[0] === part) {
|
||||
toAdd.splice(0, 1)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
newPath.push(...toAdd)
|
||||
return newPath
|
||||
}
|
||||
|
||||
function del(i: number) {
|
||||
expanded.setData(false)
|
||||
currentValue.data.splice(i, 1)
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
function swap(i: number, j: number) {
|
||||
expanded.setData(false)
|
||||
const x = currentValue.data[i]
|
||||
currentValue.data[i] = currentValue.data[j]
|
||||
currentValue.data[j] = x
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
function moveTo(source: number, target: number) {
|
||||
expanded.setData(false)
|
||||
const x = currentValue.data[source]
|
||||
currentValue.data.splice(source, 1)
|
||||
currentValue.data.splice(target, 0, x)
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
function genTitle(value: any, singular: string, i: number): Translation {
|
||||
try {
|
||||
if (schema.hints.title) {
|
||||
const v = Function("value", "return " + schema.hints.title)(value)
|
||||
return Translations.T(v)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(
|
||||
"Warning: could not translate a title for " +
|
||||
`${singular} ${i} with function ` +
|
||||
schema.hints.title +
|
||||
" and value " +
|
||||
JSON.stringify(value),
|
||||
)
|
||||
}
|
||||
return Translations.T(`${singular} ${i}`)
|
||||
}
|
||||
|
||||
let genIconF: (x: any) => { icon: string; color: string } = <any>(
|
||||
Function("value", "return " + schema.hints.icon)
|
||||
)
|
||||
|
||||
function genIcon(value: any): string {
|
||||
return genIconF(value)?.icon
|
||||
}
|
||||
|
||||
function genColor(value: any): string {
|
||||
if (!schema.hints.icon) {
|
||||
return undefined
|
||||
}
|
||||
return genIconF(value)?.color
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<AccordionItem open={$expanded} paddingDefault="p-0" inactiveClass="text-black m-0" >
|
||||
<div slot="header" class="p-1 text-base w-full m-0 text-black">
|
||||
{#if !isTagRenderingBlock}
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="m-0 flex">
|
||||
{#if schema.hints.icon}
|
||||
<Icon clss="w-6 h-6" icon={genIcon(value)} color={genColor(value)} />
|
||||
{/if}
|
||||
{#if schema.hints.title}
|
||||
<Tr t={genTitle(value, singular, i)} />
|
||||
<div class="subtle ml-2">
|
||||
{singular}
|
||||
{i}
|
||||
</div>
|
||||
{:else}
|
||||
{singular}
|
||||
{i}
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="h-fit w-fit rounded-full border border-black p-1"
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{:else if typeof value === "string"}
|
||||
Builtin: <b>{value}</b>
|
||||
{:else if value["builtin"]}
|
||||
reused tagrendering <span class="font-bold">{JSON.stringify(value["builtin"])}</span>
|
||||
{:else}
|
||||
<Tr cls="font-bold" t={Translations.T(value?.question ?? value?.render)} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="normal-background p-2">
|
||||
{#if isTagRenderingBlock}
|
||||
<QuestionPreview {state} {path} {schema}>
|
||||
<button
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
Delete this question
|
||||
</button>
|
||||
|
||||
{#if i > 0}
|
||||
<button
|
||||
on:click={() => {
|
||||
moveTo(i, 0)
|
||||
}}
|
||||
>
|
||||
Move to front
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={() => {
|
||||
swap(i, i - 1)
|
||||
}}
|
||||
>
|
||||
Move up
|
||||
</button>
|
||||
{/if}
|
||||
{#if i + 1 < $currentValue.length}
|
||||
<button
|
||||
on:click={() => {
|
||||
swap(i, i + 1)
|
||||
}}
|
||||
>
|
||||
Move down
|
||||
</button>
|
||||
<button
|
||||
on:click={() => {
|
||||
moveTo(i, $currentValue.length - 1)
|
||||
}}
|
||||
>
|
||||
Move to back
|
||||
</button>
|
||||
{/if}
|
||||
</QuestionPreview>
|
||||
{:else if schema.hints.types}
|
||||
<SchemaBasedMultiType {state} path={fusePath(i, [])} schema={schemaForMultitype()} />
|
||||
{:else}
|
||||
{#each subparts as subpart}
|
||||
<SchemaBasedInput
|
||||
{state}
|
||||
path={fusePath(i, [subpart.path.at(-1)])}
|
||||
schema={subpart}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</AccordionItem>
|
||||
|
|
@ -8,7 +8,7 @@ import {
|
|||
Pipe,
|
||||
} from "../../Models/ThemeConfig/Conversion/Conversion"
|
||||
import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer"
|
||||
import { ValidateLayer, ValidateTheme } from "../../Models/ThemeConfig/Conversion/Validation"
|
||||
import { PrevalidateTheme, ValidateLayer, ValidateTheme } from "../../Models/ThemeConfig/Conversion/Validation"
|
||||
import { AllSharedLayers } from "../../Customizations/AllSharedLayers"
|
||||
import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils"
|
||||
|
|
@ -33,6 +33,8 @@ export abstract class EditJsonState<T> {
|
|||
public readonly schema: ConfigMeta[]
|
||||
public readonly category: "layers" | "themes"
|
||||
public readonly server: StudioServer
|
||||
public readonly osmConnection: OsmConnection
|
||||
|
||||
public readonly showIntro: UIEventSource<"no" | "intro" | "tagrenderings"> = <any>(
|
||||
LocalStorageSource.Get("studio-show-intro", "intro")
|
||||
)
|
||||
|
|
@ -51,7 +53,7 @@ export abstract class EditJsonState<T> {
|
|||
* The EditLayerUI shows a 'schemaBasedInput' for this path to pop advanced questions out
|
||||
*/
|
||||
public readonly highlightedItem: UIEventSource<HighlightedTagRendering> = new UIEventSource(
|
||||
undefined
|
||||
undefined,
|
||||
)
|
||||
private sendingUpdates = false
|
||||
private readonly _stores = new Map<string, UIEventSource<any>>()
|
||||
|
|
@ -60,10 +62,12 @@ export abstract class EditJsonState<T> {
|
|||
schema: ConfigMeta[],
|
||||
server: StudioServer,
|
||||
category: "layers" | "themes",
|
||||
osmConnection: OsmConnection,
|
||||
options?: {
|
||||
expertMode?: UIEventSource<boolean>
|
||||
}
|
||||
},
|
||||
) {
|
||||
this.osmConnection = osmConnection
|
||||
this.schema = schema
|
||||
this.server = server
|
||||
this.category = category
|
||||
|
|
@ -88,6 +92,10 @@ export abstract class EditJsonState<T> {
|
|||
await this.server.update(id, config, this.category)
|
||||
})
|
||||
this.messages = this.createMessagesStore()
|
||||
this.register(["credits"], this.osmConnection.userDetails.mapD(u => u.name), false)
|
||||
this.register(["credits:uid"], this.osmConnection.userDetails.mapD(u => u.uid), false)
|
||||
|
||||
|
||||
}
|
||||
|
||||
public startSavingUpdates(enabled = true) {
|
||||
|
|
@ -132,7 +140,7 @@ export abstract class EditJsonState<T> {
|
|||
public register(
|
||||
path: ReadonlyArray<string | number>,
|
||||
value: Store<any>,
|
||||
noInitialSync: boolean = true
|
||||
noInitialSync: boolean = true,
|
||||
): () => void {
|
||||
const unsync = value.addCallback((v) => {
|
||||
this.setValueAt(path, v)
|
||||
|
|
@ -146,7 +154,7 @@ export abstract class EditJsonState<T> {
|
|||
public getSchemaStartingWith(path: string[]) {
|
||||
return this.schema.filter(
|
||||
(sch) =>
|
||||
!path.some((part, i) => !(sch.path.length > path.length && sch.path[i] === part))
|
||||
!path.some((part, i) => !(sch.path.length > path.length && sch.path[i] === part)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +175,7 @@ export abstract class EditJsonState<T> {
|
|||
const schemas = this.schema.filter(
|
||||
(sch) =>
|
||||
sch !== undefined &&
|
||||
!path.some((part, i) => !(sch.path.length == path.length && sch.path[i] === part))
|
||||
!path.some((part, i) => !(sch.path.length == path.length && sch.path[i] === part)),
|
||||
)
|
||||
if (schemas.length == 0) {
|
||||
console.warn("No schemas found for path", path.join("."))
|
||||
|
|
@ -257,12 +265,12 @@ class ContextRewritingStep<T> extends Conversion<LayerConfigJson, T> {
|
|||
constructor(
|
||||
state: DesugaringContext,
|
||||
step: Conversion<LayerConfigJson, T>,
|
||||
getTagRenderings: (t: T) => TagRenderingConfigJson[]
|
||||
getTagRenderings: (t: T) => TagRenderingConfigJson[],
|
||||
) {
|
||||
super(
|
||||
"When validating a layer, the tagRenderings are first expanded. Some builtin tagRendering-calls (e.g. `contact`) will introduce _multiple_ tagRenderings, causing the count to be off. This class rewrites the error messages to fix this",
|
||||
[],
|
||||
"ContextRewritingStep"
|
||||
"ContextRewritingStep",
|
||||
)
|
||||
this._state = state
|
||||
this._step = step
|
||||
|
|
@ -272,7 +280,7 @@ class ContextRewritingStep<T> extends Conversion<LayerConfigJson, T> {
|
|||
convert(json: LayerConfigJson, context: ConversionContext): T {
|
||||
const converted = this._step.convert(json, context)
|
||||
const originalIds = json.tagRenderings?.map(
|
||||
(tr) => (<QuestionableTagRenderingConfigJson>tr)["id"]
|
||||
(tr) => (<QuestionableTagRenderingConfigJson>tr)["id"],
|
||||
)
|
||||
if (!originalIds) {
|
||||
return converted
|
||||
|
|
@ -307,7 +315,6 @@ class ContextRewritingStep<T> extends Conversion<LayerConfigJson, T> {
|
|||
|
||||
export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
||||
// Needed for the special visualisations
|
||||
public readonly osmConnection: OsmConnection
|
||||
public readonly imageUploadManager = {
|
||||
getCountsFor() {
|
||||
return 0
|
||||
|
|
@ -335,10 +342,9 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
|||
schema: ConfigMeta[],
|
||||
server: StudioServer,
|
||||
osmConnection: OsmConnection,
|
||||
options: { expertMode: UIEventSource<boolean> }
|
||||
options: { expertMode: UIEventSource<boolean> },
|
||||
) {
|
||||
super(schema, server, "layers", options)
|
||||
this.osmConnection = osmConnection
|
||||
super(schema, server, "layers", osmConnection, options)
|
||||
this.layout = {
|
||||
getMatchingLayer: () => {
|
||||
try {
|
||||
|
|
@ -393,7 +399,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
|||
return new ContextRewritingStep(
|
||||
state,
|
||||
new Pipe(new PrepareLayer(state), new ValidateLayer("dynamic", false, undefined, true)),
|
||||
(t) => <TagRenderingConfigJson[]>t.raw.tagRenderings
|
||||
(t) => <TagRenderingConfigJson[]>t.raw.tagRenderings,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -427,7 +433,7 @@ export default class EditLayerState extends EditJsonState<LayerConfigJson> {
|
|||
}
|
||||
|
||||
protected async validate(
|
||||
configuration: Partial<LayerConfigJson>
|
||||
configuration: Partial<LayerConfigJson>,
|
||||
): Promise<ConversionMessage[]> {
|
||||
const layers = AllSharedLayers.getSharedLayersConfigs()
|
||||
|
||||
|
|
@ -456,16 +462,19 @@ export class EditThemeState extends EditJsonState<LayoutConfigJson> {
|
|||
constructor(
|
||||
schema: ConfigMeta[],
|
||||
server: StudioServer,
|
||||
options: { expertMode: UIEventSource<boolean> }
|
||||
osmConnection: OsmConnection,
|
||||
options: { expertMode: UIEventSource<boolean> },
|
||||
) {
|
||||
super(schema, server, "themes", options)
|
||||
super(schema, server, "themes", osmConnection, options)
|
||||
this.setupFixers()
|
||||
}
|
||||
|
||||
protected buildValidation(state: DesugaringContext): Conversion<LayoutConfigJson, any> {
|
||||
return new Pipe(
|
||||
new PrepareTheme(state),
|
||||
new ValidateTheme(undefined, "", false, new Set(state.tagRenderings.keys()))
|
||||
return new Pipe(new PrevalidateTheme(),
|
||||
new Pipe(
|
||||
new PrepareTheme(state),
|
||||
new ValidateTheme(undefined, "", false, new Set(state.tagRenderings.keys())),
|
||||
), true,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
<script lang="ts">
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import EditLayerState, { EditJsonState } from "./EditLayerState"
|
||||
import * as questions from "../../assets/generated/layers/questions.json"
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.js"
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson,
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.js"
|
||||
import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson"
|
||||
import FromHtml from "../Base/FromHtml.svelte"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
|
|
@ -31,15 +33,25 @@
|
|||
if (typeof x === "string") {
|
||||
return perId[x]
|
||||
} else {
|
||||
return [x]
|
||||
return <any>[x]
|
||||
}
|
||||
})
|
||||
let configs: Store<TagRenderingConfig[]> = configJson.map((configs) => {
|
||||
if (!configs) {
|
||||
return [{ error: "No configuartions found" }]
|
||||
}
|
||||
console.log("Regenerating configs")
|
||||
return configs.map((config) => {
|
||||
if (config["builtin"]) {
|
||||
let override = ""
|
||||
if (config["override"]) {
|
||||
override = ". Some items are changed with an override. Editing this is not yet supported with Studio."
|
||||
}
|
||||
return new TagRenderingConfig({
|
||||
render: {
|
||||
"en": "This question reuses <b>" + JSON.stringify(config["builtin"]) + "</b>" + override,
|
||||
},
|
||||
})
|
||||
}
|
||||
try {
|
||||
return new TagRenderingConfig(config)
|
||||
} catch (e) {
|
||||
|
|
@ -47,15 +59,6 @@
|
|||
}
|
||||
})
|
||||
})
|
||||
let id: Store<string> = value.mapD((c) => {
|
||||
if (c?.id) {
|
||||
return c.id
|
||||
}
|
||||
if (typeof c === "string") {
|
||||
return c
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
let tags = state.testTags
|
||||
|
||||
|
|
@ -66,11 +69,18 @@
|
|||
|
||||
<div class="flex">
|
||||
<div class="m-4 flex w-full flex-col">
|
||||
<NextButton clss="primary" on:click={() => state.highlightedItem.setData({ path, schema })}>
|
||||
{#if schema.hints.question}
|
||||
{schema.hints.question}
|
||||
{/if}
|
||||
</NextButton>
|
||||
{#if $configJson.some(config => config["builtin"] !== undefined)}
|
||||
<div class="interactive p-2 rounded-2xl">
|
||||
This question uses an advanced 'builtin'+'override' construction in the source code.
|
||||
Editing this with Studio is not supported.
|
||||
</div>
|
||||
{:else}
|
||||
<NextButton clss="primary" on:click={() => state.highlightedItem.setData({ path, schema })}>
|
||||
{#if schema.hints.question}
|
||||
{schema.hints.question}
|
||||
{/if}
|
||||
</NextButton>
|
||||
{/if}
|
||||
{#if description}
|
||||
<Markdown src={description} />
|
||||
{/if}
|
||||
|
|
@ -86,6 +96,7 @@
|
|||
{#each $configs as config}
|
||||
{#if config.error !== undefined}
|
||||
<div class="alert">Could not create a preview of this tagRendering: {config.error}</div>
|
||||
{JSON.stringify($value)}
|
||||
{:else if config.condition && !config.condition.matchesProperties($tags)}
|
||||
This tagRendering is currently not shown. It will appear if the feature matches the
|
||||
condition
|
||||
|
|
|
|||
|
|
@ -8,6 +8,15 @@
|
|||
|
||||
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
|
||||
|
|
@ -34,13 +43,19 @@
|
|||
return () => window.removeEventListener("keydown", handler)
|
||||
})
|
||||
|
||||
let useFallback = false
|
||||
onMount(async () => {
|
||||
const monacoEditor = await import("monaco-editor")
|
||||
loader.config({
|
||||
monaco: monacoEditor.default,
|
||||
})
|
||||
|
||||
monaco = await loader.init()
|
||||
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
|
||||
|
|
@ -50,7 +65,7 @@
|
|||
schemaUri = "https://mapcomplete.org/schemas/layoutconfig.json"
|
||||
}
|
||||
|
||||
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
monaco?.languages?.json?.jsonDefaults?.setDiagnosticsOptions({
|
||||
validate: true,
|
||||
schemas: [
|
||||
{
|
||||
|
|
@ -64,23 +79,28 @@
|
|||
],
|
||||
})
|
||||
|
||||
let modelUri = monaco.Uri.parse("inmemory://inmemory/file.json")
|
||||
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
|
||||
)
|
||||
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, {
|
||||
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(() => {
|
||||
editor?.onDidChangeModelContent(() => {
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
save()
|
||||
|
|
@ -98,4 +118,8 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={container} class="h-full w-full" />
|
||||
{#if useFallback}
|
||||
<textarea class="w-full" rows="25" bind:value={$rawConfig} />
|
||||
{:else}
|
||||
<div bind:this={container} class="h-full w-full" />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
* They will typically be a subset of some properties
|
||||
*/
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import EditLayerState, { EditJsonState } from "./EditLayerState"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let state: EditLayerState
|
||||
export let configs: ConfigMeta[]
|
||||
|
|
@ -30,8 +31,8 @@
|
|||
<div slot="header">{title}</div>
|
||||
<div class="flex w-full flex-col gap-y-1 pl-2">
|
||||
<slot name="description" />
|
||||
{#each configsFiltered as config}
|
||||
<SchemaBasedInput {state} path={config.path} schema={config} />
|
||||
{#each configsFiltered as config (config.path)}
|
||||
<SchemaBasedInput {state} path={config.path} } />
|
||||
{/each}
|
||||
</div>
|
||||
</AccordionSingle>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
<script lang="ts">
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { EditJsonState } from "./EditLayerState"
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import SchemaBasedInput from "./SchemaBasedInput.svelte"
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte"
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import QuestionPreview from "./QuestionPreview.svelte"
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
import Markdown from "../Base/Markdown.svelte"
|
||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
import Icon from "../Map/Icon.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { Utils } from "../../Utils"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import CollapsedTagRenderingPreview from "./CollapsedTagRenderingPreview.svelte"
|
||||
import { Accordion } from "flowbite-svelte"
|
||||
|
||||
export let state: EditLayerState
|
||||
export let state: EditJsonState<any>
|
||||
export let path: (string | number)[] = []
|
||||
export let schema: ConfigMeta
|
||||
|
||||
schema = Utils.Clone(schema)
|
||||
let title = schema.path.at(-1)
|
||||
console.log(">>>", schema)
|
||||
let singular = title
|
||||
if (title?.endsWith("s")) {
|
||||
singular = title.slice(0, title.length - 1)
|
||||
|
|
@ -27,7 +24,6 @@
|
|||
if (singular?.match(/^[aeoui]/)) {
|
||||
article = "an"
|
||||
}
|
||||
export let path: (string | number)[] = []
|
||||
|
||||
const isTagRenderingBlock = path.length === 1 && path[0] === "tagRenderings"
|
||||
|
||||
|
|
@ -41,12 +37,12 @@
|
|||
.filter((part) => part.path.length - 1 === schema.path.length)
|
||||
let messages = state.messagesFor(path)
|
||||
|
||||
const currentValue: UIEventSource<any[]> = state.getStoreFor(path)
|
||||
const currentValue = state.getStoreFor<(string | QuestionableTagRenderingConfigJson)[]>(path)
|
||||
if (currentValue.data === undefined) {
|
||||
currentValue.setData([])
|
||||
}
|
||||
|
||||
function createItem(valueToSet?: any) {
|
||||
function createItem(valueToSet?: string | QuestionableTagRenderingConfigJson) {
|
||||
if (currentValue.data === undefined) {
|
||||
currentValue.setData([])
|
||||
}
|
||||
|
|
@ -72,64 +68,13 @@
|
|||
return newPath
|
||||
}
|
||||
|
||||
function schemaForMultitype() {
|
||||
const sch = { ...schema }
|
||||
sch.hints.typehint = undefined
|
||||
return sch
|
||||
}
|
||||
|
||||
function del(i: number) {
|
||||
currentValue.data.splice(i, 1)
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
function swap(i: number, j: number) {
|
||||
const x = currentValue.data[i]
|
||||
currentValue.data[i] = currentValue.data[j]
|
||||
currentValue.data[j] = x
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
function moveTo(source: number, target: number) {
|
||||
const x = currentValue.data[source]
|
||||
currentValue.data.splice(source, 1)
|
||||
currentValue.data.splice(target, 0, x)
|
||||
currentValue.ping()
|
||||
}
|
||||
|
||||
function genTitle(value: any, singular: string, i: number): Translation {
|
||||
try {
|
||||
if (schema.hints.title) {
|
||||
const v = Function("value", "return " + schema.hints.title)(value)
|
||||
return Translations.T(v)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(
|
||||
"Warning: could not translate a title for " +
|
||||
`${singular} ${i} with function ` +
|
||||
schema.hints.title +
|
||||
" and value " +
|
||||
JSON.stringify(value)
|
||||
)
|
||||
}
|
||||
return Translations.T(`${singular} ${i}`)
|
||||
}
|
||||
|
||||
let genIconF: (x: any) => { icon: string; color: string } = <any>(
|
||||
Function("value", "return " + schema.hints.icon)
|
||||
)
|
||||
console.log("Icon lambda is", schema.hints.icon, path, genIconF("test"))
|
||||
|
||||
function genIcon(value: any): string {
|
||||
return genIconF(value)?.icon
|
||||
}
|
||||
|
||||
function genColor(value: any): string {
|
||||
if (!schema.hints.icon) {
|
||||
return undefined
|
||||
}
|
||||
return genIconF(value)?.color
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="pl-2">
|
||||
|
|
@ -163,99 +108,11 @@
|
|||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each $currentValue as value, i}
|
||||
<AccordionSingle expanded={false}>
|
||||
<span slot="header">
|
||||
{#if !isTagRenderingBlock}
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="m-0 flex">
|
||||
{#if schema.hints.icon}
|
||||
<Icon clss="w-6 h-6" icon={genIcon(value)} color={genColor(value)} />
|
||||
{/if}
|
||||
{singular}
|
||||
{i}
|
||||
|
||||
{#if schema.hints.title}
|
||||
<div class="subtle ml-2">
|
||||
<Tr t={genTitle(value, singular, i)} />
|
||||
</div>
|
||||
{/if}
|
||||
</h3>
|
||||
<button
|
||||
class="h-fit w-fit rounded-full border border-black p-1"
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{:else if typeof value === "string"}
|
||||
Builtin: <b>{value}</b>
|
||||
{:else}
|
||||
<Tr cls="font-bold" t={Translations.T(value?.question ?? value?.render)} />
|
||||
{/if}
|
||||
</span>
|
||||
<div class="normal-background p-2">
|
||||
{#if isTagRenderingBlock}
|
||||
<QuestionPreview {state} path={fusePath(i, [])} {schema}>
|
||||
<button
|
||||
on:click={() => {
|
||||
del(i)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="h-4 w-4" />
|
||||
Delete this question
|
||||
</button>
|
||||
|
||||
{#if i > 0}
|
||||
<button
|
||||
on:click={() => {
|
||||
moveTo(i, 0)
|
||||
}}
|
||||
>
|
||||
Move to front
|
||||
</button>
|
||||
|
||||
<button
|
||||
on:click={() => {
|
||||
swap(i, i - 1)
|
||||
}}
|
||||
>
|
||||
Move up
|
||||
</button>
|
||||
{/if}
|
||||
{#if i + 1 < $currentValue.length}
|
||||
<button
|
||||
on:click={() => {
|
||||
swap(i, i + 1)
|
||||
}}
|
||||
>
|
||||
Move down
|
||||
</button>
|
||||
<button
|
||||
on:click={() => {
|
||||
moveTo(i, $currentValue.length - 1)
|
||||
}}
|
||||
>
|
||||
Move to back
|
||||
</button>
|
||||
{/if}
|
||||
</QuestionPreview>
|
||||
{:else if schema.hints.types}
|
||||
<SchemaBasedMultiType {state} path={fusePath(i, [])} schema={schemaForMultitype()} />
|
||||
{:else}
|
||||
{#each subparts as subpart}
|
||||
<SchemaBasedInput
|
||||
{state}
|
||||
path={fusePath(i, [subpart.path.at(-1)])}
|
||||
schema={subpart}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</AccordionSingle>
|
||||
<Accordion>
|
||||
{#each $currentValue as value, i (value)}
|
||||
<CollapsedTagRenderingPreview {state} {isTagRenderingBlock} {schema} {currentValue} {value} {i} {singular} path={fusePath(i, [])}/>
|
||||
{/each}
|
||||
</Accordion>
|
||||
{/if}
|
||||
<div class="flex">
|
||||
<button on:click={() => createItem()}>Add {article} {singular}</button>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { EditJsonState } from "./EditLayerState"
|
||||
import { onDestroy } from "svelte"
|
||||
import type { JsonSchemaType } from "./jsonSchema"
|
||||
import { ConfigMetaUtils } from "./configMeta"
|
||||
import ShowConversionMessage from "./ShowConversionMessage.svelte"
|
||||
|
||||
export let state: EditLayerState
|
||||
export let state: EditJsonState<any>
|
||||
export let path: (string | number)[] = []
|
||||
export let schema: ConfigMeta
|
||||
export let startInEditModeIfUnset: boolean = schema.hints && !schema.hints.ifunset
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
<script lang="ts">
|
||||
import type { ConfigMeta } from "./configMeta"
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte"
|
||||
import EditLayerState from "./EditLayerState"
|
||||
import { EditJsonState } from "./EditLayerState"
|
||||
import SchemaBasedArray from "./SchemaBasedArray.svelte"
|
||||
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte"
|
||||
import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte"
|
||||
|
||||
export let schema: ConfigMeta
|
||||
export let state: EditLayerState
|
||||
export let state: EditJsonState<any>
|
||||
export let path: (string | number)[] = []
|
||||
console.log("Fetched schema:", path, state.getSchema(<any> path))
|
||||
let schema: ConfigMeta = state.getSchema(<any> path)[0]
|
||||
let expertMode = state.expertMode
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -204,9 +204,10 @@
|
|||
</script>
|
||||
|
||||
<div class="m-1 flex flex-col gap-y-2 border-2 border-dashed border-gray-300 p-2">
|
||||
{#if schema.hints.title !== undefined}
|
||||
{#if schema.hints.title !== undefined && typeof path.at(-1) !== "number"}
|
||||
<h3>{schema.hints.title}</h3>
|
||||
<div>{schema.description}</div>
|
||||
{path.join(".")}
|
||||
{/if}
|
||||
{#if hasOverride}
|
||||
This object refers to {existingValue.builtin} and overrides some properties. This cannot be edited
|
||||
|
|
@ -221,7 +222,6 @@
|
|||
{#if $expertMode || subschema.hints?.group !== "expert"}
|
||||
<SchemaBasedInput
|
||||
{state}
|
||||
schema={subschema}
|
||||
path={[...subpath, subschema?.path?.at(-1) ?? "???"]}
|
||||
/>
|
||||
{:else if window.location.hostname === "127.0.0.1"}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,11 @@
|
|||
undefined,
|
||||
"Used to complete the login"
|
||||
)
|
||||
const fakeUser = UIEventSource.asBoolean( QueryParameters.GetQueryParameter("fake-user", "Test switch for fake login"))
|
||||
let osmConnection = new OsmConnection({
|
||||
oauth_token,
|
||||
checkOnlineRegularly: true,
|
||||
fakeUser: fakeUser.data
|
||||
})
|
||||
const expertMode = UIEventSource.asBoolean(
|
||||
osmConnection.GetPreference("studio-expert-mode", "false", {
|
||||
|
|
@ -129,7 +131,7 @@
|
|||
let showIntro = editLayerState.showIntro
|
||||
|
||||
const layoutSchema: ConfigMeta[] = <any>layoutSchemaRaw
|
||||
let editThemeState = new EditThemeState(layoutSchema, studio, { expertMode })
|
||||
let editThemeState = new EditThemeState(layoutSchema, studio, osmConnection, { expertMode })
|
||||
|
||||
const version = meta.version
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue