forked from MapComplete/MapComplete
		
	Studio: more work on studio
This commit is contained in:
		
							parent
							
								
									81876fc5ed
								
							
						
					
					
						commit
						4e8dfc0026
					
				
					 20 changed files with 1842 additions and 94 deletions
				
			
		|  | @ -534,7 +534,7 @@ class MappedStore<TIn, T> extends Store<T> { | ||||||
|     private update(): void { |     private update(): void { | ||||||
|         const newData = this._f(this._upstream.data) |         const newData = this._f(this._upstream.data) | ||||||
|         this._upstreamPingCount = this._upstreamCallbackHandler?.pingCount |         this._upstreamPingCount = this._upstreamCallbackHandler?.pingCount | ||||||
|         if (this._data == newData) { |         if (this._data === newData) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|         this._data = newData |         this._data = newData | ||||||
|  |  | ||||||
|  | @ -645,6 +645,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (json.allowSplit && !ValidationUtils.hasSpecialVisualisation(json, "split_button")) { |         if (json.allowSplit && !ValidationUtils.hasSpecialVisualisation(json, "split_button")) { | ||||||
|  |             json.tagRenderings ??= [] | ||||||
|             json.tagRenderings.push({ |             json.tagRenderings.push({ | ||||||
|                 id: "split-button", |                 id: "split-button", | ||||||
|                 render: { "*": "{split_button()}" }, |                 render: { "*": "{split_button()}" }, | ||||||
|  | @ -653,6 +654,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) { |         if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) { | ||||||
|  |             json.tagRenderings ??= [] | ||||||
|             json.tagRenderings.push({ |             json.tagRenderings.push({ | ||||||
|                 id: "move-button", |                 id: "move-button", | ||||||
|                 render: { "*": "{move_button()}" }, |                 render: { "*": "{move_button()}" }, | ||||||
|  |  | ||||||
|  | @ -1033,7 +1033,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> { | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             { |             { | ||||||
|                 const hasCondition = json.mapRendering?.filter( |                 const hasCondition = json.pointRendering?.filter( | ||||||
|                     (mr) => mr["icon"] !== undefined && mr["icon"]["condition"] !== undefined |                     (mr) => mr["icon"] !== undefined && mr["icon"]["condition"] !== undefined | ||||||
|                 ) |                 ) | ||||||
|                 if (hasCondition?.length > 0) { |                 if (hasCondition?.length > 0) { | ||||||
|  |  | ||||||
|  | @ -43,7 +43,6 @@ export interface LayerConfigJson { | ||||||
|     description?: Translatable |     description?: Translatable | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * |  | ||||||
|      * Question: Where should the data be fetched from? |      * Question: Where should the data be fetched from? | ||||||
|      * title: Data Source |      * title: Data Source | ||||||
|      * |      * | ||||||
|  | @ -202,13 +201,13 @@ export interface LayerConfigJson { | ||||||
|     minzoomVisible?: number |     minzoomVisible?: number | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |      * question: What title should be shown on the infobox? | ||||||
|      * The title shown in a popup for elements of this layer. |      * The title shown in a popup for elements of this layer. | ||||||
|      * |      * | ||||||
|      * group: title |      * group: title | ||||||
|      * question: What title should be shown on the infobox? |      * types: use a fixed translation ; Use a dynamic tagRendering ; hidden | ||||||
|      * types: use a fixed translation ; Use a dynamic tagRendering ; use a fixed string for all languages |  | ||||||
|      * typesdefault: 1 |      * typesdefault: 1 | ||||||
|      * |      * type: translation | ||||||
|      */ |      */ | ||||||
|     title?: TagRenderingConfigJson | Translatable |     title?: TagRenderingConfigJson | Translatable | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,17 @@ import { TagRenderingConfigJson } from "./TagRenderingConfigJson" | ||||||
| import { TagConfigJson } from "./TagConfigJson" | import { TagConfigJson } from "./TagConfigJson" | ||||||
| 
 | 
 | ||||||
| export interface IconConfigJson { | export interface IconConfigJson { | ||||||
|  |     /** | ||||||
|  |      * question: What icon should be used? | ||||||
|  |      * type: icon | ||||||
|  |      * suggestions: return ["pin","square","circle","checkmark","clock","close","crosshair","help","home","invalid","location","location_empty","location_locked","note","resolved","ring","scissors","teardrop","teardrop_with_hole_green","triangle"].map(i => ({if: "value="+i, then: i, icon: i})) | ||||||
|  |      */ | ||||||
|     icon: string | TagRenderingConfigJson | { builtin: string; override: any } |     icon: string | TagRenderingConfigJson | { builtin: string; override: any } | ||||||
|  |     /** | ||||||
|  |      * question: What colour should the icon be? | ||||||
|  |      * This will only work for the default icons such as `pin`,`circle`,... | ||||||
|  |      * type: color | ||||||
|  |      */ | ||||||
|     color?: string | TagRenderingConfigJson | { builtin: string; override: any } |     color?: string | TagRenderingConfigJson | { builtin: string; override: any } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -23,17 +33,15 @@ export default interface PointRenderingConfigJson { | ||||||
|     location: ("point" | "centroid" | "start" | "end" | "projected_centerpoint" | string)[] |     location: ("point" | "centroid" | "start" | "end" | "projected_centerpoint" | string)[] | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|    * question: What marker should be used to |      * The marker for an element. | ||||||
|    * The icon for an element. |      * Note that this also defines the icon for this layer (rendered with the overpass-tags) <i>and</i> the icon in the presets. | ||||||
|    * Note that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets. |  | ||||||
|      * |      * | ||||||
|      * The result of the icon is rendered as follows: |      * The result of the icon is rendered as follows: | ||||||
|    * the resulting string is interpreted as a _list_ of items, separated by ";". The bottommost layer is the first layer. |      * - The first icon is rendered on the map | ||||||
|    * As a result, on could use a generic pin, then overlay it with a specific icon. |      * - The second entry is overlayed on top of it | ||||||
|    * To make things even more practical, one c    an use all SVG's from the folder "assets/svg" and _substitute the color_ in it. |      * - ... | ||||||
|    * E.g. to draw a red pin, use "pin:#f00", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>` |      * | ||||||
| 
 |      * As a result, on could use a generic icon (`pin`, `circle`, `square`) with a color, then overlay it with a specific icon. | ||||||
|    * Type: icon |  | ||||||
|      */ |      */ | ||||||
|     marker?: IconConfigJson[] |     marker?: IconConfigJson[] | ||||||
| 
 | 
 | ||||||
|  | @ -53,8 +61,9 @@ export default interface PointRenderingConfigJson { | ||||||
|     }[] |     }[] | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * A string containing "width,height" or "width,height,anchorpoint" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ... |      * question: What size should the marker be on the map? | ||||||
|      * Default is '40,40,center' |      * A string containing "<width>,<height>" in pixels | ||||||
|  |      * ifunset: Use the default size (<b>40,40</b> px) | ||||||
|      */ |      */ | ||||||
|     iconSize?: string | TagRenderingConfigJson |     iconSize?: string | TagRenderingConfigJson | ||||||
| 
 | 
 | ||||||
|  | @ -69,13 +78,15 @@ export default interface PointRenderingConfigJson { | ||||||
|     anchor?: "center" | "top" | "bottom" | "left" | "right" | string | TagRenderingConfigJson |     anchor?: "center" | "top" | "bottom" | "left" | "right" | string | TagRenderingConfigJson | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The rotation of an icon, useful for e.g. directions. |      * question: What rotation should be applied on the icon? | ||||||
|      * Usage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)`` |      * This is mostly useful for items that face a specific direction, such as surveillance cameras | ||||||
|  |      * This is interpreted as css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)`` | ||||||
|  |      * ifunset: Do not rotate | ||||||
|      */ |      */ | ||||||
|     rotation?: string | TagRenderingConfigJson |     rotation?: string | TagRenderingConfigJson | ||||||
|     /** |     /** | ||||||
|      * question: What label should be shown beneath the marker? |      * question: What label should be shown beneath the marker? | ||||||
|      * For example: <div style="background: white">{name}</div> |      * For example: `<div style="background: white">{name}</div>` | ||||||
|      * |      * | ||||||
|      * If the icon is undefined, then the label is shown in the center of the feature. |      * If the icon is undefined, then the label is shown in the center of the feature. | ||||||
|      * types: Dynamic value | string |      * types: Dynamic value | string | ||||||
|  | @ -124,15 +135,15 @@ export default interface PointRenderingConfigJson { | ||||||
|     labelCssClasses?: string | TagRenderingConfigJson |     labelCssClasses?: string | TagRenderingConfigJson | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * If the map is pitched, the marker will stay parallel to the screen. |      * question: If the map is pitched, should the icon stay parallel to the screen or to the groundplane? | ||||||
|      * Set to 'map' if you want to put it flattened on the map |      * suggestions: return [{if: "value=canvas", then: "The icon will stay upward and not be transformed as if it sticks to the screen"}, {if: "value=map", then: "The icon will be transformed as if it were painted onto the ground. (Automatically sets rotationAlignment)"}] | ||||||
|      */ |      */ | ||||||
|     pitchAlignment?: "canvas" | "map" | TagRenderingConfigJson |     pitchAlignment?: "canvas" | "map" | TagRenderingConfigJson | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * question: Should the icon be rotated or tilted if the map is rotated or tilted? |      * question: Should the icon be rotated if the map is rotated? | ||||||
|      * ifunset: Do not rotate or tilt icons. Always keep the icons straight |      * ifunset: Do not rotate or tilt icons. Always keep the icons straight | ||||||
|      * suggestions: return [{if: "value=canvas", then: "If the map is tilted, tilt the icon as well. This gives the impression of an icon that is glued to the ground."}, {if: "value=map", then: "If the map is rotated, rotate the icon as well. This gives the impression of an icon that floats perpendicular above the ground."}] |      * suggestions: return [{if: "value=canvas", then: "Never rotate the icon"}, {if: "value=map", then: "If the map is rotated, rotate the icon as well. This gives the impression of an icon that floats perpendicular above the ground."}] | ||||||
|      */ |      */ | ||||||
|     rotationAlignment?: "map" | "canvas" | TagRenderingConfigJson |     rotationAlignment?: "map" | "canvas" | TagRenderingConfigJson | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import { TagUtils } from "../../Logic/Tags/TagUtils" | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| import Svg from "../../Svg" | import Svg from "../../Svg" | ||||||
| import WithContextLoader from "./WithContextLoader" | import WithContextLoader from "./WithContextLoader" | ||||||
| import { Store } from "../../Logic/UIEventSource" | import { ImmutableStore, Store } from "../../Logic/UIEventSource" | ||||||
| import BaseUIElement from "../../UI/BaseUIElement" | import BaseUIElement from "../../UI/BaseUIElement" | ||||||
| import { FixedUiElement } from "../../UI/Base/FixedUiElement" | import { FixedUiElement } from "../../UI/Base/FixedUiElement" | ||||||
| import Img from "../../UI/Base/Img" | import Img from "../../UI/Base/Img" | ||||||
|  | @ -93,7 +93,7 @@ export default class PointRenderingConfig extends WithContextLoader { | ||||||
|                 ".location)" |                 ".location)" | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|         this.marker = (json.marker ?? []).map((m) => new IconConfig(m)) |         this.marker = (json.marker ?? []).map((m) => new IconConfig(<any>m)) | ||||||
|         if (json.css !== undefined) { |         if (json.css !== undefined) { | ||||||
|             this.cssDef = this.tr("css", undefined) |             this.cssDef = this.tr("css", undefined) | ||||||
|         } |         } | ||||||
|  | @ -192,7 +192,7 @@ export default class PointRenderingConfig extends WithContextLoader { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public GetBaseIcon(tags?: Record<string, string>): BaseUIElement { |     public GetBaseIcon(tags?: Record<string, string>): BaseUIElement { | ||||||
|         return new SvelteUIElement(Marker, { icons: this.marker, tags }) |         return new SvelteUIElement(Marker, { config: this, tags: new ImmutableStore(tags) }) | ||||||
|     } |     } | ||||||
|     public RenderIcon( |     public RenderIcon( | ||||||
|         tags: Store<Record<string, string>>, |         tags: Store<Record<string, string>>, | ||||||
|  |  | ||||||
|  | @ -20,6 +20,6 @@ | ||||||
|   <slot name="image" slot="image" /> |   <slot name="image" slot="image" /> | ||||||
|   <div class="flex w-full items-center justify-between" slot="message"> |   <div class="flex w-full items-center justify-between" slot="message"> | ||||||
|     <slot /> |     <slot /> | ||||||
|     <ChevronRightIcon class="h-12 w-12 shrink-0" /> |     <ChevronRightIcon class={clss?.indexOf("small") >= 0?  "h-4 w-4 shrink-0": "h-12 w-12 shrink-0" }/> | ||||||
|   </div> |   </div> | ||||||
| </SubtleButton> | </SubtleButton> | ||||||
|  |  | ||||||
|  | @ -79,7 +79,7 @@ | ||||||
| 
 | 
 | ||||||
|     onDestroy(_value.addCallbackAndRun((_) => setValues())) |     onDestroy(_value.addCallbackAndRun((_) => setValues())) | ||||||
|     onDestroy(value.addCallbackAndRunD(fromUpstream => { |     onDestroy(value.addCallbackAndRunD(fromUpstream => { | ||||||
|         if(_value.data !== fromUpstream){ |         if(_value.data !== fromUpstream && fromUpstream !== ""){ | ||||||
|             _value.setData(fromUpstream) |             _value.setData(fromUpstream) | ||||||
|         } |         } | ||||||
|     })) |     })) | ||||||
|  |  | ||||||
|  | @ -25,6 +25,7 @@ import TranslationValidator from "./Validators/TranslationValidator" | ||||||
| import FediverseValidator from "./Validators/FediverseValidator" | import FediverseValidator from "./Validators/FediverseValidator" | ||||||
| import IconValidator from "./Validators/IconValidator" | import IconValidator from "./Validators/IconValidator" | ||||||
| import TagValidator from "./Validators/TagValidator" | import TagValidator from "./Validators/TagValidator" | ||||||
|  | import IdValidator from "./Validators/IdValidator" | ||||||
| 
 | 
 | ||||||
| export type ValidatorType = (typeof Validators.availableTypes)[number] | export type ValidatorType = (typeof Validators.availableTypes)[number] | ||||||
| 
 | 
 | ||||||
|  | @ -54,6 +55,7 @@ export default class Validators { | ||||||
|         "fediverse", |         "fediverse", | ||||||
|         "tag", |         "tag", | ||||||
|         "fediverse", |         "fediverse", | ||||||
|  |         "id", | ||||||
|     ] as const |     ] as const | ||||||
| 
 | 
 | ||||||
|     public static readonly AllValidators: ReadonlyArray<Validator> = [ |     public static readonly AllValidators: ReadonlyArray<Validator> = [ | ||||||
|  | @ -80,6 +82,7 @@ export default class Validators { | ||||||
|         new TranslationValidator(), |         new TranslationValidator(), | ||||||
|         new IconValidator(), |         new IconValidator(), | ||||||
|         new FediverseValidator(), |         new FediverseValidator(), | ||||||
|  |         new IdValidator(), | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|     private static _byType = Validators._byTypeConstructor() |     private static _byType = Validators._byTypeConstructor() | ||||||
|  |  | ||||||
|  | @ -11,7 +11,12 @@ export default class IconValidator extends Validator { | ||||||
|         super("icon", "Makes sure that a valid .svg-path is added") |         super("icon", "Makes sure that a valid .svg-path is added") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     reformat(s: string, _?: () => string): string { | ||||||
|  |         return s.trim() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     getFeedback(s: string, getCountry, sloppy?: boolean): Translation | undefined { |     getFeedback(s: string, getCountry, sloppy?: boolean): Translation | undefined { | ||||||
|  |         s = this.reformat(s) | ||||||
|         if (!s.startsWith("http")) { |         if (!s.startsWith("http")) { | ||||||
|             if (!IconValidator.allLicenses.has(s)) { |             if (!IconValidator.allLicenses.has(s)) { | ||||||
|                 const close = sloppy |                 const close = sloppy | ||||||
|  |  | ||||||
|  | @ -0,0 +1,29 @@ | ||||||
|  | import { Translation } from "../../i18n/Translation" | ||||||
|  | import { Validator } from "../Validator" | ||||||
|  | import Translations from "../../i18n/Translations" | ||||||
|  | 
 | ||||||
|  | export default class IdValidator extends Validator { | ||||||
|  |     constructor() { | ||||||
|  |         super( | ||||||
|  |             "id", | ||||||
|  |             "Checks for valid identifiers for layers, will automatically replace spaces and uppercase" | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |     isValid(key: string, getCountry?: () => string): boolean { | ||||||
|  |         return this.getFeedback(key, getCountry) === undefined | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     reformat(s: string, _?: () => string): string { | ||||||
|  |         return s.replaceAll(" ", "_").toLowerCase() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getFeedback(s: string, _?: () => string): Translation | undefined { | ||||||
|  |         if (s.length < 3) { | ||||||
|  |             return Translations.t.validation.id.shouldBeLonger | ||||||
|  |         } | ||||||
|  |         if (!s.match(/^[a-zA-Z0-9_ ]+$/)) { | ||||||
|  |             return Translations.t.validation.id.invalidCharacter | ||||||
|  |         } | ||||||
|  |         return undefined | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -7,14 +7,15 @@ | ||||||
|   /** |   /** | ||||||
|    * Renders a 'marker', which consists of multiple 'icons' |    * Renders a 'marker', which consists of multiple 'icons' | ||||||
|    */ |    */ | ||||||
|   export let config : PointRenderingConfig |   export let config: PointRenderingConfig; | ||||||
|   let icons: IconConfig[] = config.marker; |   let icons: IconConfig[] = config.marker; | ||||||
|   export let tags: Store<Record<string, string>>; |   export let tags: Store<Record<string, string>>; | ||||||
| 
 | 
 | ||||||
| </script> | </script> | ||||||
| 
 | {#if config !== undefined} | ||||||
|   <div class="relative w-full h-full"> |   <div class="relative w-full h-full"> | ||||||
|     {#each icons as icon} |     {#each icons as icon} | ||||||
|       <Icon {icon} {tags} /> |       <Icon {icon} {tags} /> | ||||||
|     {/each} |     {/each} | ||||||
|   </div> |   </div> | ||||||
|  | {/if} | ||||||
|  |  | ||||||
|  | @ -7,10 +7,12 @@ | ||||||
|   import { Store, UIEventSource } from "../../Logic/UIEventSource"; |   import { Store, UIEventSource } from "../../Logic/UIEventSource"; | ||||||
|   import type { ConfigMeta } from "./configMeta"; |   import type { ConfigMeta } from "./configMeta"; | ||||||
|   import { Utils } from "../../Utils"; |   import { Utils } from "../../Utils"; | ||||||
|  |   import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"; | ||||||
| 
 | 
 | ||||||
|   const layerSchema: ConfigMeta[] = <any>layerSchemaRaw; |   const layerSchema: ConfigMeta[] = <any>layerSchemaRaw; | ||||||
|   let state = new EditLayerState(layerSchema); |   let state = new EditLayerState(layerSchema); | ||||||
|   state.configuration.setData({}); |   export let initialLayerConfig: Partial<LayerConfigJson> = {} | ||||||
|  |   state.configuration.setData(initialLayerConfig); | ||||||
|   const configuration = state.configuration; |   const configuration = state.configuration; | ||||||
|   new LayerStateSender("http://localhost:1235", state); |   new LayerStateSender("http://localhost:1235", state); | ||||||
|   /** |   /** | ||||||
|  |  | ||||||
|  | @ -129,7 +129,7 @@ export default class EditLayerState { | ||||||
|             } |             } | ||||||
|             entry = entry[breadcrumb] |             entry = entry[breadcrumb] | ||||||
|         } |         } | ||||||
|         if (v) { |         if (v !== undefined && v !== null && v !== "") { | ||||||
|             entry[path.at(-1)] = v |             entry[path.at(-1)] = v | ||||||
|         } else if (entry) { |         } else if (entry) { | ||||||
|             delete entry[path.at(-1)] |             delete entry[path.at(-1)] | ||||||
|  |  | ||||||
|  | @ -72,11 +72,11 @@ | ||||||
|         configJson.mappings.push( |         configJson.mappings.push( | ||||||
|             { |             { | ||||||
|                 if: "value=true", |                 if: "value=true", | ||||||
|                 then: "Yes "+(schema.hints?.iftrue??"") |                 then: "Yes: "+(schema.hints?.iftrue??"") | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|                 if: "value=false", |                 if: "value=false", | ||||||
|                 then: "No "+(schema.hints?.iffalse??"") |                 then: "No: "+(schema.hints?.iffalse??"") | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  | @ -106,6 +106,15 @@ | ||||||
|             if (schema.type === "boolan") { |             if (schema.type === "boolan") { | ||||||
|                 return v === "true" || v === "yes" || v === "1" |                 return v === "true" || v === "yes" || v === "1" | ||||||
|             } |             } | ||||||
|  |             if(mightBeBoolean(schema.type)){ | ||||||
|  |               if(v === "true" || v === "yes" || v === "1"){ | ||||||
|  |                 return true | ||||||
|  |               } | ||||||
|  |               if(v === "false" || v === "no" || v === "0"){ | ||||||
|  |                 console.log("Setting false...") | ||||||
|  |                 return false | ||||||
|  |               } | ||||||
|  |             } | ||||||
|             if (schema.type === "number") { |             if (schema.type === "number") { | ||||||
|                 return Number(v) |                 return Number(v) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -4,9 +4,8 @@ | ||||||
|   import EditLayerState from "./EditLayerState"; |   import EditLayerState from "./EditLayerState"; | ||||||
|   import SchemaBasedArray from "./SchemaBasedArray.svelte"; |   import SchemaBasedArray from "./SchemaBasedArray.svelte"; | ||||||
|   import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte"; |   import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte"; | ||||||
|   import SchemaBasedTranslationInput from "./SchemaBasedTranslationInput.svelte"; |  | ||||||
|   import { ConfigMetaUtils } from "./configMeta.ts" |  | ||||||
|   import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte"; |   import ArrayMultiAnswer from "./ArrayMultiAnswer.svelte"; | ||||||
|  | 
 | ||||||
|   export let schema: ConfigMeta; |   export let schema: ConfigMeta; | ||||||
|   export let state: EditLayerState; |   export let state: EditLayerState; | ||||||
|   export let path: (string | number)[] = []; |   export let path: (string | number)[] = []; | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ | ||||||
|   import type { JsonSchemaType } from "./jsonSchema"; |   import type { JsonSchemaType } from "./jsonSchema"; | ||||||
|   // @ts-ignore |   // @ts-ignore | ||||||
|   import nmd from "nano-markdown"; |   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. |    * If 'types' is defined: allow the user to pick one of the types to input. | ||||||
|  | @ -84,7 +85,7 @@ | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   const config = new TagRenderingConfig(configJson, "config based on " + schema.path.join(".")); |   const config = new TagRenderingConfig(configJson, "config based on " + schema.path.join(".")); | ||||||
|   let chosenOption: number = defaultOption; |   let chosenOption: number = writable(defaultOption); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   const existingValue = state.getCurrentValueFor(path); |   const existingValue = state.getCurrentValueFor(path); | ||||||
|  | @ -126,8 +127,11 @@ | ||||||
|     } |     } | ||||||
|     possibleTypes.sort((a, b) => b.optionalMatches - a.optionalMatches); |     possibleTypes.sort((a, b) => b.optionalMatches - a.optionalMatches); | ||||||
|     possibleTypes.sort((a, b) => b.matchingPropertiesCount - a.matchingPropertiesCount); |     possibleTypes.sort((a, b) => b.matchingPropertiesCount - a.matchingPropertiesCount); | ||||||
|  |     console.log("Possible types are", possibleTypes) | ||||||
|     if (possibleTypes.length > 0) { |     if (possibleTypes.length > 0) { | ||||||
|       tags.setData({ chosen_type_index: "" + possibleTypes[0].index }); |       chosenOption = possibleTypes[0].index | ||||||
|  |       tags.setData({ chosen_type_index: "" + chosenOption}); | ||||||
|  |        | ||||||
|     } |     } | ||||||
|   } else if (defaultOption !== undefined) { |   } else if (defaultOption !== undefined) { | ||||||
|     tags.setData({ chosen_type_index: "" + defaultOption }); |     tags.setData({ chosen_type_index: "" + defaultOption }); | ||||||
|  | @ -150,13 +154,14 @@ | ||||||
|   let subSchemas: ConfigMeta[] = []; |   let subSchemas: ConfigMeta[] = []; | ||||||
| 
 | 
 | ||||||
|   let subpath = path; |   let subpath = path; | ||||||
|   console.log("Initial chosen option is", chosenOption); |   console.log("Initial chosen option for",path.join("."),"is", chosenOption); | ||||||
|   onDestroy(tags.addCallbackAndRun(tags => { |   onDestroy(tags.addCallbackAndRun(tags => { | ||||||
|     if (tags["value"] !== "") { |     if (tags["value"] !== "") { | ||||||
|       chosenOption = undefined; |       chosenOption = undefined; | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     const oldOption = chosenOption; |     const oldOption = chosenOption; | ||||||
|  |     console.log("Updating chosenOption based on", tags, oldOption) | ||||||
|     chosenOption = tags["chosen_type_index"] ? Number(tags["chosen_type_index"]) : defaultOption; |     chosenOption = tags["chosen_type_index"] ? Number(tags["chosen_type_index"]) : defaultOption; | ||||||
|     const type = schema.type[chosenOption]; |     const type = schema.type[chosenOption]; | ||||||
|     if (chosenOption !== oldOption) { |     if (chosenOption !== oldOption) { | ||||||
|  | @ -209,4 +214,5 @@ | ||||||
|                         path={[...subpath, (subschema?.path?.at(-1) ?? "???")]}></SchemaBasedInput> |                         path={[...subpath, (subschema?.path?.at(-1) ?? "???")]}></SchemaBasedInput> | ||||||
|     {/each} |     {/each} | ||||||
|   {/if} |   {/if} | ||||||
|  |   {chosenOption} | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | @ -1,7 +1,87 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |   import NextButton from "./Base/NextButton.svelte"; | ||||||
|  |   import { Utils } from "../Utils"; | ||||||
|  |   import { UIEventSource } from "../Logic/UIEventSource"; | ||||||
|  |   import Constants from "../Models/Constants"; | ||||||
|  |   import ValidatedInput from "./InputElement/ValidatedInput.svelte"; | ||||||
|  |   import EditLayerState from "./Studio/EditLayerState"; | ||||||
|   import EditLayer from "./Studio/EditLayer.svelte"; |   import EditLayer from "./Studio/EditLayer.svelte"; | ||||||
| </script> |   import Loading from "../assets/svg/Loading.svelte"; | ||||||
| 
 | 
 | ||||||
| <EditLayer/> | 
 | ||||||
|  |   export let studioUrl = "http://127.0.0.1:1235"; | ||||||
|  |   let overview = UIEventSource.FromPromise<{ allFiles: string[] }>(Utils.downloadJson(studioUrl + "/overview")); | ||||||
|  |   let layers = overview.map(overview => { | ||||||
|  |     if (!overview) { | ||||||
|  |       return []; | ||||||
|  |     } | ||||||
|  |     return overview.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); | ||||||
|  |   }); | ||||||
|  |   let state: undefined | "edit_layer" | "new_layer" | "edit_theme" | "new_theme" | "editing_layer" | "loading" = undefined; | ||||||
|  | 
 | ||||||
|  |   let initialLayerConfig: undefined; | ||||||
|  |   let newLayerId = new UIEventSource<string>(""); | ||||||
|  |   let layerIdFeedback = new UIEventSource<string>(undefined); | ||||||
|  |   newLayerId.addCallbackD(layerId => { | ||||||
|  |     if (layerId === "") { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (layers.data.indexOf(layerId) >= 0) { | ||||||
|  |       layerIdFeedback.setData("This id is already used"); | ||||||
|  |     } | ||||||
|  |   }, [layers]); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let editLayerState = new EditLayerState(); | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | {#if state === undefined} | ||||||
|  |   <h1>MapComplete Studio</h1> | ||||||
|  |   <div class="w-full flex flex-col"> | ||||||
|  | 
 | ||||||
|  |     <NextButton on:click={() => state = "edit_layer"}> | ||||||
|  |       Edit an existing layer | ||||||
|  |     </NextButton> | ||||||
|  |     <NextButton on:click={() => state = "new_layer"}> | ||||||
|  |       Create a new layer | ||||||
|  |     </NextButton> | ||||||
|  |     <NextButton on:click={() => state = "edit_theme"}> | ||||||
|  |       Edit a theme | ||||||
|  |     </NextButton> | ||||||
|  |     <NextButton on:click={() => state = "new_theme"}> | ||||||
|  |       Create a new theme | ||||||
|  |     </NextButton> | ||||||
|  |   </div> | ||||||
|  | {:else if state === "edit_layer"} | ||||||
|  |   <div class="flex flex-wrap"> | ||||||
|  |     {#each $layers as layerId} | ||||||
|  |       <NextButton clss="small" on:click={async () => { | ||||||
|  |         console.log("Editing layer",layerId) | ||||||
|  |         state = "loading" | ||||||
|  |         initialLayerConfig = await Utils.downloadJson(studioUrl+"/layers/"+layerId+"/"+layerId+".json") | ||||||
|  |         state = "editing_layer" | ||||||
|  |        }}> | ||||||
|  |         {layerId} | ||||||
|  |       </NextButton> | ||||||
|  |     {/each} | ||||||
|  |   </div> | ||||||
|  | {:else if state === "new_layer"} | ||||||
|  |   <ValidatedInput type="id" value={newLayerId} feedback={layerIdFeedback} /> | ||||||
|  |   {#if $layerIdFeedback !== undefined} | ||||||
|  |     <div class="alert"> | ||||||
|  |       {$layerIdFeedback} | ||||||
|  |     </div> | ||||||
|  |   {:else } | ||||||
|  |     <NextButton on:click={() => {initialLayerConfig = ({id: newLayerId.data}); state = "editing_layer"}}> | ||||||
|  |       Create this layer | ||||||
|  |     </NextButton> | ||||||
|  |   {/if} | ||||||
|  | {:else if state === "loading"} | ||||||
|  |   <Loading /> | ||||||
|  | {:else if state === "editing_layer"} | ||||||
|  |   <EditLayer {initialLayerConfig} /> | ||||||
|  | {/if} | ||||||
|  |  | ||||||
|  | @ -389,7 +389,8 @@ | ||||||
|     ], |     ], | ||||||
|     "required": false, |     "required": false, | ||||||
|     "hints": { |     "hints": { | ||||||
|       "types": "use a fixed translation ; Use a dynamic tagRendering ; use a fixed string for all languages", |       "typehint": "translation", | ||||||
|  |       "types": "use a fixed translation ; Use a dynamic tagRendering ; hidden", | ||||||
|       "typesdefault": "1", |       "typesdefault": "1", | ||||||
|       "group": "title", |       "group": "title", | ||||||
|       "question": "What title should be shown on the infobox?" |       "question": "What title should be shown on the infobox?" | ||||||
|  | @ -1621,12 +1622,318 @@ | ||||||
|       "marker" |       "marker" | ||||||
|     ], |     ], | ||||||
|     "required": false, |     "required": false, | ||||||
|  |     "hints": {}, | ||||||
|  |     "type": "array", | ||||||
|  |     "description": "The marker for an element.\nNote that this also defines the icon for this layer (rendered with the overpass-tags) <i>and</i> the icon in the presets.\nThe result of the icon is rendered as follows:\n- The first icon is rendered on the map\n- The second entry is overlayed on top of it\n- ...\nAs a result, on could use a generic icon (`pin`, `circle`, `square`) with a color, then overlay it with a specific icon." | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "path": [ | ||||||
|  |       "pointRendering", | ||||||
|  |       "marker", | ||||||
|  |       "icon" | ||||||
|  |     ], | ||||||
|  |     "required": true, | ||||||
|     "hints": { |     "hints": { | ||||||
|       "typehint": "icon", |       "typehint": "icon", | ||||||
|       "question": "What marker should be used to" |       "question": "What icon should be used?", | ||||||
|  |       "suggestions": [ | ||||||
|  |         { | ||||||
|  |           "if": "value=pin", | ||||||
|  |           "then": "pin", | ||||||
|  |           "icon": "pin" | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=square", | ||||||
|  |           "then": "square", | ||||||
|  |           "icon": "square" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=circle", | ||||||
|  |           "then": "circle", | ||||||
|  |           "icon": "circle" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=checkmark", | ||||||
|  |           "then": "checkmark", | ||||||
|  |           "icon": "checkmark" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=clock", | ||||||
|  |           "then": "clock", | ||||||
|  |           "icon": "clock" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=close", | ||||||
|  |           "then": "close", | ||||||
|  |           "icon": "close" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=crosshair", | ||||||
|  |           "then": "crosshair", | ||||||
|  |           "icon": "crosshair" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=help", | ||||||
|  |           "then": "help", | ||||||
|  |           "icon": "help" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=home", | ||||||
|  |           "then": "home", | ||||||
|  |           "icon": "home" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=invalid", | ||||||
|  |           "then": "invalid", | ||||||
|  |           "icon": "invalid" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=location", | ||||||
|  |           "then": "location", | ||||||
|  |           "icon": "location" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=location_empty", | ||||||
|  |           "then": "location_empty", | ||||||
|  |           "icon": "location_empty" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=location_locked", | ||||||
|  |           "then": "location_locked", | ||||||
|  |           "icon": "location_locked" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=note", | ||||||
|  |           "then": "note", | ||||||
|  |           "icon": "note" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=resolved", | ||||||
|  |           "then": "resolved", | ||||||
|  |           "icon": "resolved" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=ring", | ||||||
|  |           "then": "ring", | ||||||
|  |           "icon": "ring" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=scissors", | ||||||
|  |           "then": "scissors", | ||||||
|  |           "icon": "scissors" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=teardrop", | ||||||
|  |           "then": "teardrop", | ||||||
|  |           "icon": "teardrop" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=teardrop_with_hole_green", | ||||||
|  |           "then": "teardrop_with_hole_green", | ||||||
|  |           "icon": "teardrop_with_hole_green" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=triangle", | ||||||
|  |           "then": "triangle", | ||||||
|  |           "icon": "triangle" | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     "type": [ | ||||||
|  |       { | ||||||
|  |         "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", | ||||||
|  |         "type": "object", | ||||||
|  |         "properties": { | ||||||
|  |           "render": { | ||||||
|  |             "description": "question: What text should be rendered?\n\nThis piece of text will be shown in the infobox.\nNote that \"&LBRACEkey&RBRACE\"-parts are substituted by the corresponding values of the element.\n\nThis text will be shown if:\n- there is no mapping which matches (or there are no matches)\n- no question, no mappings and no 'freeform' is set\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`\ntype: rendered", | ||||||
|  |             "anyOf": [ | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/Record<string,string>" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "object", | ||||||
|  |                 "properties": { | ||||||
|  |                   "special": { | ||||||
|  |                     "allOf": [ | ||||||
|  |                       { | ||||||
|  |                         "$ref": "#/definitions/Record<string,string|Record<string,string>>" | ||||||
|  |                       }, | ||||||
|  |                       { | ||||||
|  |                         "type": "object", | ||||||
|  |                         "properties": { | ||||||
|  |                           "type": { | ||||||
|  |                             "type": "string" | ||||||
|  |                           } | ||||||
|  |                         }, | ||||||
|  |                         "required": [ | ||||||
|  |                           "type" | ||||||
|  |                         ] | ||||||
|  |                       } | ||||||
|  |                     ] | ||||||
|  |                   } | ||||||
|  |                 }, | ||||||
|  |                 "required": [ | ||||||
|  |                   "special" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "icon": { | ||||||
|  |             "description": "question: what icon should be shown next to the 'render' value?\nAn icon shown next to the rendering; typically shown pretty small\nThis is only shown next to the \"render\" value\nType: icon", | ||||||
|  |             "anyOf": [ | ||||||
|  |               { | ||||||
|  |                 "type": "object", | ||||||
|  |                 "properties": { | ||||||
|  |                   "path": { | ||||||
|  |                     "description": "The path to the icon\nType: icon", | ||||||
|  |                     "type": "string" | ||||||
|  |                   }, | ||||||
|  |                   "class": { | ||||||
|  |                     "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-<classtype>', so defining your own in combination with a custom CSS is possible (but discouraged)", | ||||||
|  |                     "type": "string" | ||||||
|  |                   } | ||||||
|  |                 }, | ||||||
|  |                 "required": [ | ||||||
|  |                   "path" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "condition": { | ||||||
|  |             "description": "question: When should this item be shown?\n\nOnly show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n    {\n      \"question\": \"Where is the changing table located?\",\n      \"render\": \"The changing table is located at {changing_table:location}\",\n      \"condition\": \"changing_table=yes\",\n      \"freeform\": {\n        \"key\": \"changing_table:location\",\n        \"inline\": true\n      },\n      \"mappings\": [\n        {\n          \"then\": \"The changing table is in the toilet for women.\",\n          \"if\": \"changing_table:location=female_toilet\"\n        },\n        {\n          \"then\": \"The changing table is in the toilet for men.\",\n          \"if\": \"changing_table:location=male_toilet\"\n        },\n        {\n          \"if\": \"changing_table:location=wheelchair_toilet\",\n          \"then\": \"The changing table is in the toilet for wheelchair users.\",\n        },\n        {\n          \"if\": \"changing_table:location=dedicated_room\",\n          \"then\": \"The changing table is in a dedicated room. \",\n        }\n      ],\n      \"id\": \"toilet-changing_table:location\"\n    },\n```", | ||||||
|  |             "anyOf": [ | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/{and:TagConfigJson[];}" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/{or:TagConfigJson[];}" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "metacondition": { | ||||||
|  |             "description": "question: When should this item be shown (including special conditions)?\n\nIf set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", | ||||||
|  |             "anyOf": [ | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/{and:TagConfigJson[];}" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/{or:TagConfigJson[];}" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "freeform": { | ||||||
|  |             "description": "question: Should a freeform text field be shown?\nAllow freeform text input from the user\nifunset: Do not add a freeform text field", | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |               "key": { | ||||||
|  |                 "description": "What attribute should be filled out\nIf this key is present in the feature, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown", | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             "required": [ | ||||||
|  |               "key" | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "mappings": { | ||||||
|  |             "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes", | ||||||
|             "type": "array", |             "type": "array", | ||||||
|     "description": "The icon for an element.\nNote that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.\nThe result of the icon is rendered as follows:\nthe resulting string is interpreted as a _list_ of items, separated by \";\". The bottommost layer is the first layer.\nAs a result, on could use a generic pin, then overlay it with a specific icon.\nTo make things even more practical, one c    an use all SVG's from the folder \"assets/svg\" and _substitute the color_ in it.\nE.g. to draw a red pin, use \"pin:#f00\", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>`" |             "items": { | ||||||
|  |               "type": "object", | ||||||
|  |               "properties": { | ||||||
|  |                 "if": { | ||||||
|  |                   "$ref": "#/definitions/TagConfigJson", | ||||||
|  |                   "description": "question: When should this single mapping match?\n\nIf this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" | ||||||
|  |                 }, | ||||||
|  |                 "then": { | ||||||
|  |                   "description": "question: What text should be shown?\n\nIf the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", | ||||||
|  |                   "anyOf": [ | ||||||
|  |                     { | ||||||
|  |                       "$ref": "#/definitions/Record<string,string>" | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                       "type": "string" | ||||||
|  |                     } | ||||||
|  |                   ] | ||||||
|  |                 }, | ||||||
|  |                 "icon": { | ||||||
|  |                   "description": "question: What icon should be added to this mapping?\nAn icon supporting this mapping; typically shown pretty small\ninline: <img src='{icon}' class=\"w-8 h-8\" /> {icon}\nType: icon", | ||||||
|  |                   "anyOf": [ | ||||||
|  |                     { | ||||||
|  |                       "type": "object", | ||||||
|  |                       "properties": { | ||||||
|  |                         "path": { | ||||||
|  |                           "description": "The path to the icon\nType: icon", | ||||||
|  |                           "type": "string" | ||||||
|  |                         }, | ||||||
|  |                         "class": { | ||||||
|  |                           "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-<classtype>', so defining your own in combination with a custom CSS is possible (but discouraged)", | ||||||
|  |                           "type": "string" | ||||||
|  |                         } | ||||||
|  |                       }, | ||||||
|  |                       "required": [ | ||||||
|  |                         "path" | ||||||
|  |                       ] | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                       "type": "string" | ||||||
|  |                     } | ||||||
|  |                   ] | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |               "required": [ | ||||||
|  |                 "if", | ||||||
|  |                 "then" | ||||||
|  |               ] | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "description": { | ||||||
|  |             "description": "A human-readable text explaining what this tagRendering does.\nMostly used for the shared tagrenderings", | ||||||
|  |             "anyOf": [ | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/Record<string,string>" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "classes": { | ||||||
|  |             "description": "question: What css-classes should be applied to showing this attribute?\n\nA list of css-classes to apply to the entire tagRendering.\nThese classes are applied in 'answer'-mode, not in question mode\nThis is only for advanced users.\n\nValues are split on ` `  (space)", | ||||||
|  |             "type": "string" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "additionalProperties": false | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "type": "object", | ||||||
|  |         "properties": { | ||||||
|  |           "builtin": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "override": {} | ||||||
|  |         }, | ||||||
|  |         "required": [ | ||||||
|  |           "builtin", | ||||||
|  |           "override" | ||||||
|  |         ] | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "type": "string" | ||||||
|  |       } | ||||||
|  |     ], | ||||||
|  |     "description": "" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": [ |     "path": [ | ||||||
|  | @ -2131,6 +2438,213 @@ | ||||||
|     "type": "string", |     "type": "string", | ||||||
|     "description": "A list of css-classes to apply to the entire tagRendering.\nThese classes are applied in 'answer'-mode, not in question mode\nThis is only for advanced users.\nValues are split on ` `  (space)" |     "description": "A list of css-classes to apply to the entire tagRendering.\nThese classes are applied in 'answer'-mode, not in question mode\nThis is only for advanced users.\nValues are split on ` `  (space)" | ||||||
|   }, |   }, | ||||||
|  |   { | ||||||
|  |     "path": [ | ||||||
|  |       "pointRendering", | ||||||
|  |       "marker", | ||||||
|  |       "color" | ||||||
|  |     ], | ||||||
|  |     "required": false, | ||||||
|  |     "hints": { | ||||||
|  |       "typehint": "color", | ||||||
|  |       "question": "What colour should the icon be?" | ||||||
|  |     }, | ||||||
|  |     "type": [ | ||||||
|  |       { | ||||||
|  |         "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", | ||||||
|  |         "type": "object", | ||||||
|  |         "properties": { | ||||||
|  |           "render": { | ||||||
|  |             "description": "question: What text should be rendered?\n\nThis piece of text will be shown in the infobox.\nNote that \"&LBRACEkey&RBRACE\"-parts are substituted by the corresponding values of the element.\n\nThis text will be shown if:\n- there is no mapping which matches (or there are no matches)\n- no question, no mappings and no 'freeform' is set\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`\ntype: rendered", | ||||||
|  |             "anyOf": [ | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/Record<string,string>" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "object", | ||||||
|  |                 "properties": { | ||||||
|  |                   "special": { | ||||||
|  |                     "allOf": [ | ||||||
|  |                       { | ||||||
|  |                         "$ref": "#/definitions/Record<string,string|Record<string,string>>" | ||||||
|  |                       }, | ||||||
|  |                       { | ||||||
|  |                         "type": "object", | ||||||
|  |                         "properties": { | ||||||
|  |                           "type": { | ||||||
|  |                             "type": "string" | ||||||
|  |                           } | ||||||
|  |                         }, | ||||||
|  |                         "required": [ | ||||||
|  |                           "type" | ||||||
|  |                         ] | ||||||
|  |                       } | ||||||
|  |                     ] | ||||||
|  |                   } | ||||||
|  |                 }, | ||||||
|  |                 "required": [ | ||||||
|  |                   "special" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "icon": { | ||||||
|  |             "description": "question: what icon should be shown next to the 'render' value?\nAn icon shown next to the rendering; typically shown pretty small\nThis is only shown next to the \"render\" value\nType: icon", | ||||||
|  |             "anyOf": [ | ||||||
|  |               { | ||||||
|  |                 "type": "object", | ||||||
|  |                 "properties": { | ||||||
|  |                   "path": { | ||||||
|  |                     "description": "The path to the icon\nType: icon", | ||||||
|  |                     "type": "string" | ||||||
|  |                   }, | ||||||
|  |                   "class": { | ||||||
|  |                     "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-<classtype>', so defining your own in combination with a custom CSS is possible (but discouraged)", | ||||||
|  |                     "type": "string" | ||||||
|  |                   } | ||||||
|  |                 }, | ||||||
|  |                 "required": [ | ||||||
|  |                   "path" | ||||||
|  |                 ] | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "condition": { | ||||||
|  |             "description": "question: When should this item be shown?\n\nOnly show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n    {\n      \"question\": \"Where is the changing table located?\",\n      \"render\": \"The changing table is located at {changing_table:location}\",\n      \"condition\": \"changing_table=yes\",\n      \"freeform\": {\n        \"key\": \"changing_table:location\",\n        \"inline\": true\n      },\n      \"mappings\": [\n        {\n          \"then\": \"The changing table is in the toilet for women.\",\n          \"if\": \"changing_table:location=female_toilet\"\n        },\n        {\n          \"then\": \"The changing table is in the toilet for men.\",\n          \"if\": \"changing_table:location=male_toilet\"\n        },\n        {\n          \"if\": \"changing_table:location=wheelchair_toilet\",\n          \"then\": \"The changing table is in the toilet for wheelchair users.\",\n        },\n        {\n          \"if\": \"changing_table:location=dedicated_room\",\n          \"then\": \"The changing table is in a dedicated room. \",\n        }\n      ],\n      \"id\": \"toilet-changing_table:location\"\n    },\n```", | ||||||
|  |             "anyOf": [ | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/{and:TagConfigJson[];}" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/{or:TagConfigJson[];}" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "metacondition": { | ||||||
|  |             "description": "question: When should this item be shown (including special conditions)?\n\nIf set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", | ||||||
|  |             "anyOf": [ | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/{and:TagConfigJson[];}" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/{or:TagConfigJson[];}" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "freeform": { | ||||||
|  |             "description": "question: Should a freeform text field be shown?\nAllow freeform text input from the user\nifunset: Do not add a freeform text field", | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |               "key": { | ||||||
|  |                 "description": "What attribute should be filled out\nIf this key is present in the feature, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown", | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             "required": [ | ||||||
|  |               "key" | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "mappings": { | ||||||
|  |             "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes", | ||||||
|  |             "type": "array", | ||||||
|  |             "items": { | ||||||
|  |               "type": "object", | ||||||
|  |               "properties": { | ||||||
|  |                 "if": { | ||||||
|  |                   "$ref": "#/definitions/TagConfigJson", | ||||||
|  |                   "description": "question: When should this single mapping match?\n\nIf this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" | ||||||
|  |                 }, | ||||||
|  |                 "then": { | ||||||
|  |                   "description": "question: What text should be shown?\n\nIf the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", | ||||||
|  |                   "anyOf": [ | ||||||
|  |                     { | ||||||
|  |                       "$ref": "#/definitions/Record<string,string>" | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                       "type": "string" | ||||||
|  |                     } | ||||||
|  |                   ] | ||||||
|  |                 }, | ||||||
|  |                 "icon": { | ||||||
|  |                   "description": "question: What icon should be added to this mapping?\nAn icon supporting this mapping; typically shown pretty small\ninline: <img src='{icon}' class=\"w-8 h-8\" /> {icon}\nType: icon", | ||||||
|  |                   "anyOf": [ | ||||||
|  |                     { | ||||||
|  |                       "type": "object", | ||||||
|  |                       "properties": { | ||||||
|  |                         "path": { | ||||||
|  |                           "description": "The path to the icon\nType: icon", | ||||||
|  |                           "type": "string" | ||||||
|  |                         }, | ||||||
|  |                         "class": { | ||||||
|  |                           "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-<classtype>', so defining your own in combination with a custom CSS is possible (but discouraged)", | ||||||
|  |                           "type": "string" | ||||||
|  |                         } | ||||||
|  |                       }, | ||||||
|  |                       "required": [ | ||||||
|  |                         "path" | ||||||
|  |                       ] | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                       "type": "string" | ||||||
|  |                     } | ||||||
|  |                   ] | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |               "required": [ | ||||||
|  |                 "if", | ||||||
|  |                 "then" | ||||||
|  |               ] | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           "description": { | ||||||
|  |             "description": "A human-readable text explaining what this tagRendering does.\nMostly used for the shared tagrenderings", | ||||||
|  |             "anyOf": [ | ||||||
|  |               { | ||||||
|  |                 "$ref": "#/definitions/Record<string,string>" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "type": "string" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           "classes": { | ||||||
|  |             "description": "question: What css-classes should be applied to showing this attribute?\n\nA list of css-classes to apply to the entire tagRendering.\nThese classes are applied in 'answer'-mode, not in question mode\nThis is only for advanced users.\n\nValues are split on ` `  (space)", | ||||||
|  |             "type": "string" | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "additionalProperties": false | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "type": "object", | ||||||
|  |         "properties": { | ||||||
|  |           "builtin": { | ||||||
|  |             "type": "string" | ||||||
|  |           }, | ||||||
|  |           "override": {} | ||||||
|  |         }, | ||||||
|  |         "required": [ | ||||||
|  |           "builtin", | ||||||
|  |           "override" | ||||||
|  |         ] | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         "type": "string" | ||||||
|  |       } | ||||||
|  |     ], | ||||||
|  |     "description": "This will only work for the default icons such as `pin`,`circle`,..." | ||||||
|  |   }, | ||||||
|   { |   { | ||||||
|     "path": [ |     "path": [ | ||||||
|       "pointRendering", |       "pointRendering", | ||||||
|  | @ -3380,7 +3894,10 @@ | ||||||
|       "iconSize" |       "iconSize" | ||||||
|     ], |     ], | ||||||
|     "required": false, |     "required": false, | ||||||
|     "hints": {}, |     "hints": { | ||||||
|  |       "question": "What size should the marker be on the map?", | ||||||
|  |       "ifunset": "Use the default size (<b>40,40</b> px)" | ||||||
|  |     }, | ||||||
|     "type": [ |     "type": [ | ||||||
|       { |       { | ||||||
|         "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", |         "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", | ||||||
|  | @ -3562,7 +4079,7 @@ | ||||||
|         "type": "string" |         "type": "string" | ||||||
|       } |       } | ||||||
|     ], |     ], | ||||||
|     "description": "A string containing \"width,height\" or \"width,height,anchorpoint\" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...\nDefault is '40,40,center'" |     "description": "A string containing \"<width>,<height>\" in pixels" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": [ |     "path": [ | ||||||
|  | @ -4751,7 +5268,10 @@ | ||||||
|       "rotation" |       "rotation" | ||||||
|     ], |     ], | ||||||
|     "required": false, |     "required": false, | ||||||
|     "hints": {}, |     "hints": { | ||||||
|  |       "question": "What rotation should be applied on the icon?", | ||||||
|  |       "ifunset": "Do not rotate" | ||||||
|  |     }, | ||||||
|     "type": [ |     "type": [ | ||||||
|       { |       { | ||||||
|         "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", |         "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", | ||||||
|  | @ -4933,7 +5453,7 @@ | ||||||
|         "type": "string" |         "type": "string" | ||||||
|       } |       } | ||||||
|     ], |     ], | ||||||
|     "description": "The rotation of an icon, useful for e.g. directions.\nUsage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``" |     "description": "This is mostly useful for items that face a specific direction, such as surveillance cameras\nThis is interpreted as css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": [ |     "path": [ | ||||||
|  | @ -5610,7 +6130,7 @@ | ||||||
|         "type": "string" |         "type": "string" | ||||||
|       } |       } | ||||||
|     ], |     ], | ||||||
|     "description": "For example: <div style=\"background: white\">{name}</div>\nIf the icon is undefined, then the label is shown in the center of the feature." |     "description": "For example: `<div style=\"background: white\">{name}</div>`\nIf the icon is undefined, then the label is shown in the center of the feature." | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": [ |     "path": [ | ||||||
|  | @ -8809,7 +9329,19 @@ | ||||||
|       "pitchAlignment" |       "pitchAlignment" | ||||||
|     ], |     ], | ||||||
|     "required": false, |     "required": false, | ||||||
|     "hints": {}, |     "hints": { | ||||||
|  |       "question": "If the map is pitched, should the icon stay parallel to the screen or to the groundplane?", | ||||||
|  |       "suggestions": [ | ||||||
|  |         { | ||||||
|  |           "if": "value=canvas", | ||||||
|  |           "then": "The icon will stay upward and not be transformed as if it sticks to the screen" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "if": "value=map", | ||||||
|  |           "then": "The icon will be transformed as if it were painted onto the ground. (Automatically sets rotationAlignment)" | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|     "type": [ |     "type": [ | ||||||
|       { |       { | ||||||
|         "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", |         "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", | ||||||
|  | @ -8995,7 +9527,7 @@ | ||||||
|         "type": "string" |         "type": "string" | ||||||
|       } |       } | ||||||
|     ], |     ], | ||||||
|     "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map" |     "description": "" | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     "path": [ |     "path": [ | ||||||
|  | @ -9487,12 +10019,12 @@ | ||||||
|     ], |     ], | ||||||
|     "required": false, |     "required": false, | ||||||
|     "hints": { |     "hints": { | ||||||
|       "question": "Should the icon be rotated or tilted if the map is rotated or tilted?", |       "question": "Should the icon be rotated if the map is rotated?", | ||||||
|       "ifunset": "Do not rotate or tilt icons. Always keep the icons straight", |       "ifunset": "Do not rotate or tilt icons. Always keep the icons straight", | ||||||
|       "suggestions": [ |       "suggestions": [ | ||||||
|         { |         { | ||||||
|           "if": "value=canvas", |           "if": "value=canvas", | ||||||
|           "then": "If the map is tilted, tilt the icon as well. This gives the impression of an icon that is glued to the ground." |           "then": "Never rotate the icon" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           "if": "value=map", |           "if": "value=map", | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue