From 8bc555fbe06b44a107ee08caf4f3842f859451f3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 24 Oct 2023 22:01:10 +0200 Subject: [PATCH] Studio: add slideshow, add useability tweaks --- scripts/studioServer.ts | 36 ++- src/Logic/UIEventSource.ts | 1 - .../ThemeConfig/Conversion/Validation.ts | 45 +++- .../ThemeConfig/Json/LayerConfigJson.ts | 15 +- src/UI/Base/LoginToggle.svelte | 5 +- .../Helpers/SimpleTagInput.svelte | 2 +- src/UI/InputElement/Helpers/TagInput.svelte | 2 +- .../Helpers/TranslationInput.svelte | 20 +- src/UI/InputElement/InputHelper.svelte | 9 +- src/UI/InputElement/ValidatedInput.svelte | 2 +- .../TagRendering/TagRenderingQuestion.svelte | 1 - src/UI/SpecialVisualizations.ts | 1 + src/UI/Studio/ChooseLayerToEdit.svelte | 28 +++ src/UI/Studio/EditLayer.svelte | 173 +++++++------ src/UI/Studio/EditLayerState.ts | 67 +++-- src/UI/Studio/SchemaBasedArray.svelte | 2 +- src/UI/Studio/SchemaBasedField.svelte | 25 +- src/UI/Studio/SchemaBasedInput.svelte | 2 +- src/UI/Studio/SchemaBasedMultiType.svelte | 24 +- .../Studio/SchemaBasedTranslationInput.svelte | 2 +- src/UI/Studio/StudioServer.ts | 53 +++- src/UI/Studio/TagExpression.svelte | 2 +- src/UI/Studio/TagInput/BasicTagInput.svelte | 4 +- src/UI/Studio/TagInput/FullTagInput.svelte | 2 +- src/UI/Studio/TagRenderingInput.svelte | 5 - src/UI/StudioGUI.svelte | 228 ++++++++---------- 26 files changed, 440 insertions(+), 316 deletions(-) diff --git a/scripts/studioServer.ts b/scripts/studioServer.ts index 1854f59524..048937d2d2 100644 --- a/scripts/studioServer.ts +++ b/scripts/studioServer.ts @@ -28,6 +28,18 @@ async function prepareFile(url: string): Promise { if (fs.existsSync(filePath)) { return fs.readFileSync(filePath, "utf8") } + while (url.startsWith("/")) { + url = url.slice(1) + } + const sliced = url.split("/").slice(1) + if (!sliced) { + return + } + const backupFile = path.join(STATIC_PATH, ...sliced) + console.log("Using bakcup path", backupFile) + if (fs.existsSync(backupFile)) { + return fs.readFileSync(backupFile, "utf8") + } return null } @@ -51,7 +63,9 @@ http.createServer(async (req, res) => { for (let i = 1; i < paths.length; i++) { const p = paths.slice(0, i) const dir = STATIC_PATH + p.join("/") + console.log("Checking if", dir, "exists...") if (!fs.existsSync(dir)) { + console.log("Creating new directory", dir) fs.mkdirSync(dir) } } @@ -61,22 +75,28 @@ http.createServer(async (req, res) => { res.end() return } - if (req.url.endsWith("/overview")) { + + const url = new URL(`http://127.0.0.1/` + req.url) + if (url.pathname.endsWith("overview")) { console.log("Giving overview") + let userId = url.searchParams.get("userId") const allFiles = ScriptUtils.readDirRecSync(STATIC_PATH) - .filter((p) => p.endsWith(".json") && !p.endsWith("license_info.json")) + .filter( + (p) => + p.endsWith(".json") && + !p.endsWith("license_info.json") && + (p.startsWith("layers") || + p.startsWith("themes") || + userId !== undefined || + p.startsWith(userId)) + ) .map((p) => p.substring(STATIC_PATH.length + 1)) res.writeHead(200, { "Content-Type": MIME_TYPES.json }) res.write(JSON.stringify({ allFiles })) res.end() return } - if (!fs.existsSync(STATIC_PATH + req.url)) { - res.writeHead(404, { "Content-Type": MIME_TYPES.html }) - res.write("

Not found...

") - res.end() - return - } + const file = await prepareFile(req.url) if (file === null) { res.writeHead(404, { "Content-Type": MIME_TYPES.html }) diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index 35f0f0ef85..7e16d96e6b 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -435,7 +435,6 @@ class MappedStore extends Store { * const mapped = src.map(i => i * 2) * src.setData(3) * mapped.data // => 6 - * */ get data(): T { if (!this._callbacksAreRegistered) { diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index 3888ff8639..432804afac 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -666,25 +666,29 @@ class MiscTagRenderingChecks extends DesugaringStep { } if (json.freeform) { - const c = context.enters("freeform", "render") if (json.render === undefined) { - c.err( - "This tagRendering allows to set a freeform, but does not define a way to `render` this value" - ) + context + .enter("render") + .err( + "This tagRendering allows to set a value to key " + + json.freeform.key + + ", but does not define a `render`. Please, add a value here which contains `{" + + json.freeform.key + + "}`" + ) } else { const render = new Translation(json.render) - for (const ln in render.translations) { if (ln.startsWith("_")) { continue } const txt: string = render.translations[ln] if (txt === "") { - c.err(" Rendering for language " + ln + " is empty") + context.enter("render").err(" Rendering for language " + ln + " is empty") } if ( txt.indexOf("{" + json.freeform.key + "}") >= 0 || - txt.indexOf("&LBRACE" + json.freeform.key + "&RBRACE") + txt.indexOf("&LBRACE" + json.freeform.key + "&RBRACE") >= 0 ) { continue } @@ -721,9 +725,11 @@ class MiscTagRenderingChecks extends DesugaringStep { ) { continue } - c.err( - `The rendering for language ${ln} does not contain the freeform key {${json.freeform.key}}. This is a bug, as this rendering should show exactly this freeform key!\nThe rendering is ${txt} ` - ) + context + .enter("render") + .err( + `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!` + ) } } } @@ -783,7 +789,7 @@ export class ValidateLayer extends Conversion< private readonly _path?: string private readonly _isBuiltin: boolean private readonly _doesImageExist: DoesImageExist - private _studioValidations: boolean + private readonly _studioValidations: boolean constructor( path: string, @@ -816,7 +822,7 @@ export class ValidateLayer extends Conversion< } if (json.id === undefined) { - context.err(`Not a valid layer: id is undefined: ${JSON.stringify(json)}`) + context.enter("id").err(`Not a valid layer: id is undefined`) } if (json.source === undefined) { @@ -922,6 +928,21 @@ export class ValidateLayer extends Conversion< "Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set." ) } + + { + // Check for multiple, identical builtin questions - usability for studio users + const duplicates = Utils.Duplicates( + json.tagRenderings.filter((tr) => typeof tr === "string") + ) + for (let i = 0; i < json.tagRenderings.length; i++) { + const tagRendering = json.tagRenderings[i] + if (typeof tagRendering === "string" && duplicates.indexOf(tagRendering) > 0) { + context + .enters("tagRenderings", i) + .err(`This builtin question is used multiple times (${tagRendering})`) + } + } + } } if (json["builtin"] !== undefined) { diff --git a/src/Models/ThemeConfig/Json/LayerConfigJson.ts b/src/Models/ThemeConfig/Json/LayerConfigJson.ts index 422530cfe0..fe0927711c 100644 --- a/src/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/src/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -15,11 +15,18 @@ import { Translatable } from "./Translatable" */ export interface LayerConfigJson { /** - * The id of this layer. - * This should be a simple, lowercase, human readable string that is used to identify the layer. - * - * group: Basic * question: What is the identifier of this layer? + * + * This should be a simple, lowercase, human readable string that is used to identify the layer. + * A good ID is: + * - a noun + * - written in singular + * - describes the object + * - in english + * - only has lowercase letters, numbers or underscores. Do not use a space or a dash + * + * type: id + * group: Basic */ id: string diff --git a/src/UI/Base/LoginToggle.svelte b/src/UI/Base/LoginToggle.svelte index 472110a4a4..24d6b0e4a8 100644 --- a/src/UI/Base/LoginToggle.svelte +++ b/src/UI/Base/LoginToggle.svelte @@ -26,8 +26,7 @@ } const apiState = state.osmConnection.apiIsOnline - - +{/if} diff --git a/src/UI/InputElement/Helpers/SimpleTagInput.svelte b/src/UI/InputElement/Helpers/SimpleTagInput.svelte index 56ce35c860..ddb99e2ed5 100644 --- a/src/UI/InputElement/Helpers/SimpleTagInput.svelte +++ b/src/UI/InputElement/Helpers/SimpleTagInput.svelte @@ -22,7 +22,7 @@ $: documentation = TagUtils.modeDocumentation[mode]; - + {#if $dropdownFocussed}
{documentation.name} diff --git a/src/UI/InputElement/Helpers/TagInput.svelte b/src/UI/InputElement/Helpers/TagInput.svelte index d3ab1f0935..7144268b84 100644 --- a/src/UI/InputElement/Helpers/TagInput.svelte +++ b/src/UI/InputElement/Helpers/TagInput.svelte @@ -19,4 +19,4 @@ let tag: UIEventSource = value - + diff --git a/src/UI/InputElement/Helpers/TranslationInput.svelte b/src/UI/InputElement/Helpers/TranslationInput.svelte index 235fc8162d..821eea8015 100644 --- a/src/UI/InputElement/Helpers/TranslationInput.svelte +++ b/src/UI/InputElement/Helpers/TranslationInput.svelte @@ -5,20 +5,14 @@ import { createEventDispatcher, onDestroy } from "svelte"; import ValidatedInput from "../ValidatedInput.svelte"; - export let value: UIEventSource = new UIEventSource(""); + export let value: UIEventSource> = new UIEventSource>({}); export let args: string[] = [] - let prefix = args[0] - let postfix = args[1] + let prefix = args[0] ?? "" + let postfix = args[1] ?? "" - let translations: UIEventSource> = value.sync((s) => { - try { - return JSON.parse(s); - } catch (e) { - return {}; - } - }, [], v => JSON.stringify(v)); + let translations: UIEventSource> = value const allLanguages: string[] = LanguageUtils.usedLanguagesSorted; let currentLang = new UIEventSource("en"); @@ -28,6 +22,9 @@ function update() { const v = currentVal.data; const l = currentLang.data; + if(translations.data === "" || translations.data === undefined){ + translations.data = {} + } if (translations.data[l] === v) { return; } @@ -37,6 +34,9 @@ onDestroy(currentLang.addCallbackAndRunD(currentLang => { console.log("Applying current lang:", currentLang); + if(!translations.data){ + translations.data = {} + } translations.data[currentLang] = translations.data[currentLang] ?? ""; currentVal.setData(translations.data[currentLang]); })); diff --git a/src/UI/InputElement/InputHelper.svelte b/src/UI/InputElement/InputHelper.svelte index 007417de8e..0d5906698c 100644 --- a/src/UI/InputElement/InputHelper.svelte +++ b/src/UI/InputElement/InputHelper.svelte @@ -27,14 +27,13 @@ let properties = { feature, args: args ?? [] }; let dispatch = createEventDispatcher<{ - selected, - submit + selected }>(); {#if type === "translation" } - dispatch("submit")} {args} /> + {:else if type === "direction"} {:else if type === "date"} @@ -44,9 +43,9 @@ {:else if type === "image"} {:else if type === "tag"} - + {:else if type === "simple_tag"} - + {:else if type === "opening_hours"} {:else if type === "wikidata"} diff --git a/src/UI/InputElement/ValidatedInput.svelte b/src/UI/InputElement/ValidatedInput.svelte index 7cd13d1a46..2c34301029 100644 --- a/src/UI/InputElement/ValidatedInput.svelte +++ b/src/UI/InputElement/ValidatedInput.svelte @@ -109,7 +109,7 @@ * Dispatches the submit, but only if the value is valid */ function sendSubmit(){ - if(feedback.data){ + if(feedback?.data){ console.log("Not sending a submit as there is feedback") } dispatch("submit") diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index e191ad1c70..094cbf4df7 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -259,7 +259,6 @@ value={freeformInput} on:selected={() => (selectedMapping = config.mappings?.length)} on:submit={onSave} - submit={onSave} /> {/if} diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index 50714c56c2..98df80f706 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -1336,6 +1336,7 @@ export default class SpecialVisualizations { const tr = typeof v === "string" ? JSON.parse(v) : v return new Translation(tr).SetClass("font-bold") } catch (e) { + console.error("Cannot create a translation for", v, "due to", e) return JSON.stringify(v) } }) diff --git a/src/UI/Studio/ChooseLayerToEdit.svelte b/src/UI/Studio/ChooseLayerToEdit.svelte index e69de29bb2..14c0267aa0 100644 --- a/src/UI/Studio/ChooseLayerToEdit.svelte +++ b/src/UI/Studio/ChooseLayerToEdit.svelte @@ -0,0 +1,28 @@ + + +{#if layerIds.length > 0} + +
+ {#each Array.from(layerIds) as layer} + dispatch("layerSelected", layer.id)}> +
+ +
+ {layer.id} +
+ {/each} +
+ {/if} diff --git a/src/UI/Studio/EditLayer.svelte b/src/UI/Studio/EditLayer.svelte index e14c0b3a12..5283ed9dd7 100644 --- a/src/UI/Studio/EditLayer.svelte +++ b/src/UI/Studio/EditLayer.svelte @@ -11,6 +11,7 @@ import type { ConversionMessage } from "../../Models/ThemeConfig/Conversion/Conversion"; import ErrorIndicatorForRegion from "./ErrorIndicatorForRegion.svelte"; import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid"; + import SchemaBasedInput from "./SchemaBasedInput.svelte"; const layerSchema: ConfigMeta[] = layerSchemaRaw; @@ -25,7 +26,6 @@ * Blacklist of regions for the general area tab * These are regions which are handled by a different tab */ - const regionBlacklist = ["hidden", undefined, "infobox", "tagrenderings", "maprendering", "editing", "title", "linerendering", "pointrendering"]; const allNames = Utils.Dedup(layerSchema.map(meta => meta.hints.group)); const perRegion: Record = {}; @@ -33,12 +33,7 @@ perRegion[region] = layerSchema.filter(meta => meta.hints.group === region); } - const baselayerRegions: string[] = ["Basic", "presets", "filters"]; - for (const baselayerRegion of baselayerRegions) { - if (perRegion[baselayerRegion] === undefined) { - console.error("BaseLayerRegions in editLayer: no items have group '" + baselayerRegion + "\""); - } - } + const title: Store = state.getStoreFor(["id"]); const wl = window.location; const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout="; @@ -46,80 +41,122 @@ function firstPathsFor(...regionNames: string[]): Set { const pathNames = new Set(); for (const regionName of regionNames) { - const region: ConfigMeta[] = perRegion[regionName] + const region: ConfigMeta[] = perRegion[regionName]; for (const configMeta of region) { - pathNames.add(configMeta.path[0]) + pathNames.add(configMeta.path[0]); } } return pathNames; } + function configForRequiredField(id: string): ConfigMeta{ + let config = layerSchema.find(config => config.path.length === 1 && config.path[0] === id) + config = Utils.Clone(config) + config.required = true + console.log(">>>", config) + config.hints.ifunset = undefined + return config + } + + let requiredFields = ["id", "name", "description"]; + let currentlyMissing = state.configuration.map(config => { + const missing = []; + for (const requiredField of requiredFields) { + if (!config[requiredField]) { + missing.push(requiredField); + } + } + return missing; + }); + -
- -

Editing layer {$title}

- {#if $hasErrors > 0} -
{$hasErrors} errors detected
- {:else} - - Try it out - - - {/if} -
-
- -
General properties - -
-
- {#each baselayerRegions as region} - - {/each} -
-
Information panel (questions and answers) -
-
- - - -
+{#if $currentlyMissing.length > 0} -
Rendering on the map -
-
- - -
+ {#each requiredFields as required} + + {/each} +{:else} +
+ +

Editing layer {$title}

+ {#if $hasErrors > 0} +
{$hasErrors} errors detected
+ {:else} + + Try it out + + + {/if} +
+
+ +
General properties + +
+
+ -
Advanced functionality -
-
- - -
-
Configuration file
-
-
- Below, you'll find the raw configuration file in `.json`-format. - This is mostly for debugging purposes
-
- {JSON.stringify($configuration, null, " ")} + + +
Information panel (questions and answers) +
- {#each $messages as message} -
  • - {message.level} - {message.context.path.join(".")} - {message.message} - +
    + + + +
    + +
    + + Creating a new point +
    + +
    + +
    + +
    Rendering on the map + +
    +
    + + +
    + +
    Advanced functionality + +
    +
    + + +
    +
    Configuration file
    +
    +
    + Below, you'll find the raw configuration file in `.json`-format. + This is mosSendertly for debugging purposes +
    +
    + {JSON.stringify($configuration, null, " ")} +
    + {#each $messages as message} +
  • + {message.level} + {message.context.path.join(".")} + {message.message} + {message.context.operation.join(".")} -
  • - {/each} -
    - + + {/each} +
    + -
    +
    +{/if} diff --git a/src/UI/Studio/EditLayerState.ts b/src/UI/Studio/EditLayerState.ts index 1e08cbdf10..93709e0044 100644 --- a/src/UI/Studio/EditLayerState.ts +++ b/src/UI/Studio/EditLayerState.ts @@ -1,8 +1,6 @@ -import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { ConfigMeta } from "./configMeta" import { Store, UIEventSource } from "../../Logic/UIEventSource" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" -import { QueryParameters } from "../../Logic/Web/QueryParameters" import { ConversionContext, ConversionMessage, @@ -16,25 +14,29 @@ import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Jso import { TagUtils } from "../../Logic/Tags/TagUtils" import StudioServer from "./StudioServer" import { Utils } from "../../Utils" +import { OsmConnection } from "../../Logic/Osm/OsmConnection" /** * Sends changes back to the server */ export class LayerStateSender { constructor(layerState: EditLayerState) { - layerState.configuration.addCallback(async (config) => { - const id = config.id - if (id === undefined) { - console.warn("No id found in layer, not updating") - return - } - await layerState.server.updateLayer(config) - }) + const layerId = layerState.configuration.map((config) => config.id) + layerState.configuration + .mapD((config) => JSON.stringify(config, null, " ")) + .stabilized(100) + .addCallbackD(async (config) => { + const id = layerId.data + if (id === undefined) { + console.warn("No id found in layer, not updating") + return + } + await layerState.server.updateLayer(id, config) + }) } } export default class EditLayerState { - public readonly osmConnection: OsmConnection public readonly schema: ConfigMeta[] public readonly featureSwitches: { featureSwitchIsDebugging: UIEventSource } @@ -44,17 +46,14 @@ export default class EditLayerState { >({}) public readonly messages: Store public readonly server: StudioServer + // Needed for the special visualisations + public readonly osmConnection: OsmConnection + private readonly _stores = new Map>() - constructor(schema: ConfigMeta[], server: StudioServer) { + constructor(schema: ConfigMeta[], server: StudioServer, osmConnection: OsmConnection) { this.schema = schema this.server = server - this.osmConnection = new OsmConnection({ - oauth_token: QueryParameters.GetQueryParameter( - "oauth_token", - undefined, - "Used to complete the login" - ), - }) + this.osmConnection = osmConnection this.featureSwitches = { featureSwitchIsDebugging: new UIEventSource(true), } @@ -118,7 +117,6 @@ export default class EditLayerState { return entry } - private readonly _stores = new Map>() public getStoreFor(path: ReadonlyArray): UIEventSource { const key = path.join(".") @@ -139,7 +137,9 @@ export default class EditLayerState { value: Store, noInitialSync: boolean = false ): () => void { - const unsync = value.addCallback((v) => this.setValueAt(path, v)) + const unsync = value.addCallback((v) => { + this.setValueAt(path, v) + }) if (!noInitialSync) { this.setValueAt(path, value.data) } @@ -180,6 +180,7 @@ export default class EditLayerState { public setValueAt(path: ReadonlyArray, v: any) { let entry = this.configuration.data + console.log("Setting value at", path, v) const isUndefined = v === undefined || v === null || @@ -197,15 +198,35 @@ export default class EditLayerState { } entry = entry[breadcrumb] } + const lastBreadcrumb = path.at(-1) if (isUndefined) { if (entry && entry[lastBreadcrumb]) { console.log("Deleting", lastBreadcrumb, "of", path.join(".")) delete entry[lastBreadcrumb] + this.configuration.ping() } - } else { + } else if (entry[lastBreadcrumb] !== v) { + console.log("Assigning and pinging at", path) entry[lastBreadcrumb] = v + this.configuration.ping() } - this.configuration.ping() + } + + public messagesFor(path: ReadonlyArray): Store { + return this.messages.map((msgs) => { + if (!msgs) { + return [] + } + return msgs.filter((msg) => { + const pth = msg.context.path + for (let i = 0; i < Math.min(pth.length, path.length); i++) { + if (pth[i] !== path[i]) { + return false + } + } + return true + }) + }) } } diff --git a/src/UI/Studio/SchemaBasedArray.svelte b/src/UI/Studio/SchemaBasedArray.svelte index a603c449ba..e53ea6425d 100644 --- a/src/UI/Studio/SchemaBasedArray.svelte +++ b/src/UI/Studio/SchemaBasedArray.svelte @@ -110,7 +110,7 @@ {/if}
    {#if isTagRenderingBlock} - +
    {/if} diff --git a/src/UI/Studio/SchemaBasedInput.svelte b/src/UI/Studio/SchemaBasedInput.svelte index 77d823673b..be4efdc4c0 100644 --- a/src/UI/Studio/SchemaBasedInput.svelte +++ b/src/UI/Studio/SchemaBasedInput.svelte @@ -14,7 +14,7 @@ {#if schema.hints.typehint === "tagrendering[]"} - {:else if schema.type === "array" && schema.hints.multianswer === "true"} +{:else if schema.type === "array" && schema.hints.multianswer === "true"} {:else if schema.type === "array"} diff --git a/src/UI/Studio/SchemaBasedMultiType.svelte b/src/UI/Studio/SchemaBasedMultiType.svelte index 0f32073b76..5882226911 100644 --- a/src/UI/Studio/SchemaBasedMultiType.svelte +++ b/src/UI/Studio/SchemaBasedMultiType.svelte @@ -13,7 +13,6 @@ import type { JsonSchemaType } from "./jsonSchema"; // @ts-ignore import nmd from "nano-markdown"; - import { writable } from "svelte/store"; /** * If 'types' is defined: allow the user to pick one of the types to input. @@ -30,7 +29,7 @@ types.splice(hasBooleanOption); } - + let lastIsString = false; { const types: string | string[] = Array.isArray(schema.type) ? schema.type[schema.type.length - 1].type : []; @@ -42,7 +41,7 @@ } const configJson: QuestionableTagRenderingConfigJson = { id: "TYPE_OF:" + path.join("_"), - question: "Which subcategory is needed for "+schema.path.at(-1)+"?", + question: "Which subcategory is needed for " + schema.path.at(-1) + "?", questionHint: nmd(schema.description), mappings: types.map(opt => opt.trim()).filter(opt => opt.length > 0).map((opt, i) => ({ if: "chosen_type_index=" + i, @@ -127,14 +126,14 @@ possibleTypes.sort((a, b) => b.optionalMatches - a.optionalMatches); possibleTypes.sort((a, b) => b.matchingPropertiesCount - a.matchingPropertiesCount); if (possibleTypes.length > 0) { - chosenOption = possibleTypes[0].index - tags.setData({ chosen_type_index: "" + chosenOption}); - + chosenOption = possibleTypes[0].index; + tags.setData({ chosen_type_index: "" + chosenOption }); + } } else if (defaultOption !== undefined) { tags.setData({ chosen_type_index: "" + defaultOption }); - }else{ - chosenOption = defaultOption + } else { + chosenOption = defaultOption; } if (hasBooleanOption >= 0 || lastIsString) { @@ -154,7 +153,7 @@ let subSchemas: ConfigMeta[] = []; let subpath = path; - const store = state.getStoreFor(path) + const store = state.getStoreFor(path); onDestroy(tags.addCallbackAndRun(tags => { if (tags["value"] !== undefined && tags["value"] !== "") { chosenOption = undefined; @@ -170,7 +169,7 @@ for (const key of type?.required ?? []) { o[key] ??= {}; } - store.setData(o) + store.setData(o); } if (!type) { return; @@ -191,6 +190,7 @@ subSchemas.push(...(state.getSchema([...cleanPath, crumble]))); } })); + let messages = state.messagesFor(path); @@ -209,5 +209,9 @@ {/each} + {:else if $messages.length > 0} + {#each $messages as msg} +
    {msg.message}
    + {/each} {/if}
    diff --git a/src/UI/Studio/SchemaBasedTranslationInput.svelte b/src/UI/Studio/SchemaBasedTranslationInput.svelte index a6e692ee8d..158cd7626b 100644 --- a/src/UI/Studio/SchemaBasedTranslationInput.svelte +++ b/src/UI/Studio/SchemaBasedTranslationInput.svelte @@ -8,7 +8,7 @@ export let path: (string | number)[] = []; export let schema: ConfigMeta; - let value = new UIEventSource("{}"); + let value = new UIEventSource({}); console.log("Registering translation to path", path) state.register(path, value.mapD(v => JSON.parse(value.data ))); diff --git a/src/UI/Studio/StudioServer.ts b/src/UI/Studio/StudioServer.ts index 52e737e490..e074dbeff3 100644 --- a/src/UI/Studio/StudioServer.ts +++ b/src/UI/Studio/StudioServer.ts @@ -1,23 +1,51 @@ import { Utils } from "../../Utils" import Constants from "../../Models/Constants" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" +import { Store } from "../../Logic/UIEventSource" export default class StudioServer { private readonly url: string + private readonly _userId: Store - constructor(url: string) { + constructor(url: string, userId: Store) { this.url = url + this._userId = userId } - public async fetchLayerOverview(): Promise> { + public async fetchLayerOverview(): Promise< + { + id: string + owner: number + }[] + > { + const uid = this._userId.data + let uidQueryParam = "" + if (this._userId.data !== undefined) { + uidQueryParam = "?userId=" + uid + } const { allFiles } = <{ allFiles: string[] }>( - await Utils.downloadJson(this.url + "/overview") + await Utils.downloadJson(this.url + "/overview" + uidQueryParam) ) - const layers = allFiles - .filter((f) => f.startsWith("layers/")) - .map((l) => l.substring(l.lastIndexOf("/") + 1, l.length - ".json".length)) - .filter((layerId) => Constants.priviliged_layers.indexOf(layerId) < 0) - return new Set(layers) + const layerOverview: { + id: string + owner: number | undefined + }[] = [] + for (let file of allFiles) { + let owner = undefined + if (file.startsWith("" + uid)) { + owner = uid + file = file.substring(file.indexOf("/") + 1) + } + if (!file.startsWith("layers/")) { + continue + } + const id = file.substring(file.lastIndexOf("/") + 1, file.length - ".json".length) + if (Constants.priviliged_layers.indexOf(id) > 0) { + continue + } + layerOverview.push({ id, owner }) + } + return layerOverview } async fetchLayer(layerId: string): Promise { @@ -28,8 +56,7 @@ export default class StudioServer { } } - async updateLayer(config: LayerConfigJson) { - const id = config.id + async updateLayer(id: string, config: string) { if (id === undefined || id === "") { return } @@ -38,11 +65,13 @@ export default class StudioServer { headers: { "Content-Type": "application/json;charset=utf-8", }, - body: JSON.stringify(config, null, " "), + body: config, }) } public layerUrl(id: string) { - return `${this.url}/layers/${id}/${id}.json` + const uid = this._userId.data + const uidStr = uid !== undefined ? "/" + uid : "" + return `${this.url}${uidStr}/layers/${id}/${id}.json` } } diff --git a/src/UI/Studio/TagExpression.svelte b/src/UI/Studio/TagExpression.svelte index 50afee9c13..d334d7db8e 100644 --- a/src/UI/Studio/TagExpression.svelte +++ b/src/UI/Studio/TagExpression.svelte @@ -139,7 +139,7 @@ if (!initialTag) {
    {#each $basicTags as basicTag (basicTag)}
    - + {#if $basicTags.length + $expressions.length > 1}