Studo: WIP

This commit is contained in:
Pieter Vander Vennet 2023-09-15 01:16:33 +02:00
parent 7ebb3d721c
commit 338599454c
30 changed files with 42794 additions and 749 deletions

View file

@ -149,7 +149,7 @@ class ExpandTagRendering extends Conversion<
this._options = options
this._tagRenderingsByLabel = new Map<string, TagRenderingConfigJson[]>()
for (const trconfig of state.tagRenderings?.values() ?? []) {
for (const label of trconfig.labels ?? []) {
for (const label of trconfig["labels"] ?? []) {
let withLabel = this._tagRenderingsByLabel.get(label)
if (withLabel === undefined) {
withLabel = []
@ -193,7 +193,7 @@ class ExpandTagRendering extends Conversion<
for (let foundTr of indirect) {
foundTr = Utils.Clone<any>(foundTr)
Utils.Merge(tagRenderingConfigJson["override"] ?? {}, foundTr)
foundTr.id = tagRenderingConfigJson.id ?? foundTr.id
foundTr["id"] = tagRenderingConfigJson["id"] ?? foundTr["id"]
result.push(foundTr)
}
} else {
@ -239,9 +239,9 @@ class ExpandTagRendering extends Conversion<
matchingTrs = layerTrs
} else if (id.startsWith("*")) {
const id_ = id.substring(1)
matchingTrs = layerTrs.filter((tr) => tr.labels?.indexOf(id_) >= 0)
matchingTrs = layerTrs.filter((tr) => tr["labels"]?.indexOf(id_) >= 0)
} else {
matchingTrs = layerTrs.filter((tr) => tr.id === id || tr.labels?.indexOf(id) >= 0)
matchingTrs = layerTrs.filter((tr) => tr["id"] === id || tr["labels"]?.indexOf(id) >= 0)
}
const contextWriter = new AddContextToTranslations<TagRenderingConfigJson>("layers:")
@ -489,7 +489,15 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
}
}
json = JSON.parse(JSON.stringify(json))
json.freeform.inline ??= true
if (typeof json.freeform === "string") {
errors.push("At " + context + ": 'freeform' is a string, but should be an object")
return { result: json, errors }
}
try {
json.freeform.inline ??= true
} catch (e) {
errors.push("At " + context + ": " + e.message)
}
return { result: json, errors }
}
}

View file

@ -64,8 +64,7 @@ export interface LayerConfigJson {
| {
/**
* question: Which tags must be present on the feature to show it in this layer?
*
* Every source must set which tags have to be present in order to load the given layer.
* Every source must set which tags have to be present in order to load the given layer.
*/
osmTags: TagConfigJson
/**
@ -238,21 +237,19 @@ export interface LayerConfigJson {
/**
* Visualisation of the items on the map
*
* Set 'null' explicitly if you do not want a maprendering
* group: maprendering
*/
mapRendering:
| null
| (
| PointRenderingConfigJson
mapRendering?: (
| PointRenderingConfigJson
| LineRenderingConfigJson
| RewritableConfigJson<
| LineRenderingConfigJson
| RewritableConfigJson<
| LineRenderingConfigJson
| PointRenderingConfigJson
| LineRenderingConfigJson[]
| PointRenderingConfigJson[]
>
)[]
| PointRenderingConfigJson
| LineRenderingConfigJson[]
| PointRenderingConfigJson[]
>
)[]
/**
* If set, this layer will pass all the features it receives onto the next layer.
@ -467,7 +464,7 @@ export interface LayerConfigJson {
* A no-delete option is offered as 'reason to delete it', but secretly retags.
*
* group: editing
* types: use an advanced delete configuration ; boolean
* types: Use an advanced delete configuration ; boolean
* iftrue: Allow deletion
* iffalse: Do not allow deletion
*

View file

@ -260,7 +260,7 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
questionHint?: string | Translatable
/**
* A list of labels. These are strings that are used for various purposes, e.g. to filter them away
* A list of labels. These are strings that are used for various purposes, e.g. to only include a subset of the tagRenderings when reusing a layer
*/
labels?: string[]
}

View file

@ -2,7 +2,7 @@
import { UIEventSource } from "../../../Logic/UIEventSource";
import LanguageUtils from "../../../Utils/LanguageUtils";
import { onDestroy } from "svelte";
import { createEventDispatcher, onDestroy } from "svelte";
import ValidatedInput from "../ValidatedInput.svelte";
export let value: UIEventSource<string> = new UIEventSource<string>("");
@ -18,6 +18,7 @@
const allLanguages: string[] = LanguageUtils.usedLanguagesSorted;
let currentLang = new UIEventSource("en");
const currentVal = new UIEventSource<string>("");
let dispatch = createEventDispatcher<{ submit }>()
function update() {
const v = currentVal.data;
@ -49,5 +50,5 @@
</option>
{/each}
</select>
<ValidatedInput type="string" value={currentVal} />
<ValidatedInput type="string" value={currentVal} on:submit={() => dispatch("submit")} />
</div>

View file

@ -4,30 +4,59 @@
* Note that all values are stringified
*/
import { UIEventSource } from "../../Logic/UIEventSource"
import type { ValidatorType } from "./Validators"
import InputHelpers from "./InputHelpers"
import ToSvelte from "../Base/ToSvelte.svelte"
import type { Feature } from "geojson"
import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement"
import { UIEventSource } from "../../Logic/UIEventSource";
import type { ValidatorType } from "./Validators";
import InputHelpers from "./InputHelpers";
import ToSvelte from "../Base/ToSvelte.svelte";
import type { Feature } from "geojson";
import BaseUIElement from "../BaseUIElement";
import { VariableUiElement } from "../Base/VariableUIElement";
import { createEventDispatcher } from "svelte";
import ImageHelper from "./Helpers/ImageHelper.svelte";
import TranslationInput from "./Helpers/TranslationInput.svelte";
import TagInput from "./Helpers/TagInput.svelte";
import SimpleTagInput from "./Helpers/SimpleTagInput.svelte";
import DirectionInput from "./Helpers/DirectionInput.svelte";
import DateInput from "./Helpers/DateInput.svelte";
import ColorInput from "./Helpers/ColorInput.svelte";
export let type: ValidatorType
export let value: UIEventSource<string>
export let type: ValidatorType;
export let value: UIEventSource<string>;
export let feature: Feature
export let args: (string | number | boolean)[] = undefined
export let feature: Feature;
export let args: (string | number | boolean)[] = undefined;
let properties = { feature, args: args ?? [] }
let construct = new UIEventSource<(value, extraProperties) => BaseUIElement>(undefined)
let properties = { feature, args: args ?? [] };
let construct = new UIEventSource<(value, extraProperties) => BaseUIElement>(undefined);
$: {
construct.setData(InputHelpers.AvailableInputHelpers[type])
const helper = InputHelpers.AvailableInputHelpers[type];
construct.setData(helper);
}
let dispatch = createEventDispatcher<{ selected, submit }>();
</script>
{#if construct !== undefined}
<ToSvelte
construct={() =>
{#if type === "translation" }
<TranslationInput {value} on:submit={() => dispatch("submit")} />
{:else if type === "direction"}
<DirectionInput {value} mapProperties={InputHelpers.constructMapProperties(properties)} />
{:else if type === "date"}
<DateInput { value } />
{:else if type === "color"}
<ColorInput { value } />
{:else if type === "image"}
<ImageHelper { value } />
{:else if type === "tag"}
<TagInput { value } />
{:else if type === "simple_tag"}
<SimpleTagInput { value } />
{:else if $construct !== undefined}
{#if isBaseUIElement}
<ToSvelte
construct={() =>
new VariableUiElement(construct.mapD((construct) => construct(value, properties)))}
/>
/>
{/if}
{/if}

View file

@ -1,10 +1,7 @@
import { ValidatorType } from "./Validators"
import { UIEventSource } from "../../Logic/UIEventSource"
import SvelteUIElement from "../Base/SvelteUIElement"
import DirectionInput from "./Helpers/DirectionInput.svelte"
import { MapProperties } from "../../Models/MapProperties"
import DateInput from "./Helpers/DateInput.svelte"
import ColorInput from "./Helpers/ColorInput.svelte"
import BaseUIElement from "../BaseUIElement"
import OpeningHoursInput from "../OpeningHours/OpeningHoursInput"
import WikidataSearchBox from "../Wikipedia/WikidataSearchBox"
@ -13,10 +10,6 @@ import { Utils } from "../../Utils"
import Locale from "../i18n/Locale"
import { Feature } from "geojson"
import { GeoOperations } from "../../Logic/GeoOperations"
import ImageHelper from "./Helpers/ImageHelper.svelte"
import TranslationInput from "./Helpers/TranslationInput.svelte"
import TagInput from "./Helpers/TagInput.svelte"
import SimpleTagInput from "./Helpers/SimpleTagInput.svelte"
export interface InputHelperProperties {
/**
@ -39,6 +32,9 @@ export interface InputHelperProperties {
}
export default class InputHelpers {
/**
* @deprecated
*/
public static readonly AvailableInputHelpers: Readonly<
Partial<
Record<
@ -50,30 +46,21 @@ export default class InputHelpers {
>
>
> = {
direction: (value, properties) =>
new SvelteUIElement(DirectionInput, {
value,
mapProperties: InputHelpers.constructMapProperties(properties),
}),
date: (value) => new SvelteUIElement(DateInput, { value }),
color: (value) => new SvelteUIElement(ColorInput, { value }),
// TODO: remake in svelte,move selection logic to 'inputHelper.svelte'
opening_hours: (value) => new OpeningHoursInput(value),
wikidata: InputHelpers.constructWikidataHelper,
image: (value) => new SvelteUIElement(ImageHelper, { value }),
translation: (value) => new SvelteUIElement(TranslationInput, { value }),
tag: (value) => new SvelteUIElement(TagInput, { value }),
simple_tag: (value) => new SvelteUIElement(SimpleTagInput, { value }),
} as const
public static hideInputField: string[] = ["translation", "simple_tag", "tag"]
// noinspection JSUnusedLocalSymbols
/**
* Constructs a mapProperties-object for the given properties.
* Assumes that the first helper-args contains the desired zoom-level
* @param properties
* @private
*/
private static constructMapProperties(
public static constructMapProperties(
properties: InputHelperProperties
): Partial<MapProperties> {
let location = properties?.mapProperties?.location

View file

@ -110,7 +110,7 @@
placeholder={_placeholder}></textarea>
</form>
{:else}
<form class="inline-flex" on:submit={() => dispatch("submit")}>
<form class="inline-flex" on:submit|preventDefault={() => dispatch("submit")}>
<input
bind:this={htmlElem}
bind:value={$_value}

View file

@ -66,5 +66,5 @@
/>
{/if}
<InputHelper args={config.freeform.helperArgs} {feature} type={config.freeform.type} {value} />
<InputHelper args={config.freeform.helperArgs} {feature} type={config.freeform.type} {value} on:submit={() => dispatch("submit")} />
</div>

View file

@ -1,52 +1,52 @@
<script lang="ts">
import {ImmutableStore, Store, UIEventSource} from "../../../Logic/UIEventSource"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import Tr from "../../Base/Tr.svelte"
import type { Feature } from "geojson"
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig"
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
import FreeformInput from "./FreeformInput.svelte"
import Translations from "../../i18n/Translations.js"
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
import { createEventDispatcher, onDestroy } from "svelte"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import SpecialTranslation from "./SpecialTranslation.svelte"
import TagHint from "../TagHint.svelte"
import LoginToggle from "../../Base/LoginToggle.svelte"
import SubtleButton from "../../Base/SubtleButton.svelte"
import Loading from "../../Base/Loading.svelte"
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte"
import { Translation } from "../../i18n/Translation"
import Constants from "../../../Models/Constants"
import { Unit } from "../../../Models/Unit"
import UserRelatedState from "../../../Logic/State/UserRelatedState"
import { twJoin } from "tailwind-merge"
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import Tr from "../../Base/Tr.svelte";
import type { Feature } from "geojson";
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig";
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import { TagsFilter } from "../../../Logic/Tags/TagsFilter";
import FreeformInput from "./FreeformInput.svelte";
import Translations from "../../i18n/Translations.js";
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
import { createEventDispatcher, onDestroy } from "svelte";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import SpecialTranslation from "./SpecialTranslation.svelte";
import TagHint from "../TagHint.svelte";
import LoginToggle from "../../Base/LoginToggle.svelte";
import SubtleButton from "../../Base/SubtleButton.svelte";
import Loading from "../../Base/Loading.svelte";
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte";
import { Translation } from "../../i18n/Translation";
import Constants from "../../../Models/Constants";
import { Unit } from "../../../Models/Unit";
import UserRelatedState from "../../../Logic/State/UserRelatedState";
import { twJoin } from "tailwind-merge";
export let config: TagRenderingConfig
export let tags: UIEventSource<Record<string, string>>
export let selectedElement: Feature
export let state: SpecialVisualizationState
export let layer: LayerConfig | undefined
export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>;
export let selectedElement: Feature;
export let state: SpecialVisualizationState;
export let layer: LayerConfig | undefined;
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key));
// Will be bound if a freeform is available
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key])
let selectedMapping: number = undefined
let checkedMappings: boolean[]
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]);
let selectedMapping: number = undefined;
let checkedMappings: boolean[];
$: {
let tgs = $tags
let tgs = $tags;
mappings = config.mappings?.filter((m) => {
if (typeof m.hideInAnswer === "boolean") {
return !m.hideInAnswer
return !m.hideInAnswer;
}
return !m.hideInAnswer.matchesProperties(tgs)
})
return !m.hideInAnswer.matchesProperties(tgs);
});
// We received a new config -> reinit
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key));
if (
config.mappings?.length > 0 &&
@ -54,23 +54,23 @@
) {
checkedMappings = [
...config.mappings.map((_) => false),
false /*One element extra in case a freeform value is added*/,
]
false /*One element extra in case a freeform value is added*/
];
}
if (config.freeform?.key) {
if (!config.multiAnswer) {
// Somehow, setting multianswer freeform values is broken if this is not set
freeformInput.setData(tgs[config.freeform.key])
freeformInput.setData(tgs[config.freeform.key]);
}
} else {
freeformInput.setData(undefined)
freeformInput.setData(undefined);
}
feedback.setData(undefined)
feedback.setData(undefined);
}
export let selectedTags: TagsFilter = undefined
export let selectedTags: TagsFilter = undefined;
let mappings: Mapping[] = config?.mappings
let searchTerm: UIEventSource<string> = new UIEventSource("")
let mappings: Mapping[] = config?.mappings;
let searchTerm: UIEventSource<string> = new UIEventSource("");
$: {
try {
@ -79,10 +79,10 @@
selectedMapping,
checkedMappings,
tags.data
)
);
} catch (e) {
console.error("Could not calculate changeSpecification:", e)
selectedTags = undefined
console.error("Could not calculate changeSpecification:", e);
selectedTags = undefined;
}
}
@ -91,53 +91,53 @@
config: TagRenderingConfig
applied: TagsFilter
}
}>()
}>();
function onSave() {
if (selectedTags === undefined) {
return
return;
}
if (layer === undefined || layer?.source === null) {
/**
* This is a special, priviliged layer.
* We simply apply the tags onto the records
*/
const kv = selectedTags.asChange(tags.data)
const kv = selectedTags.asChange(tags.data);
for (const { k, v } of kv) {
if (v === undefined) {
delete tags.data[k]
delete tags.data[k];
} else {
tags.data[k] = v
tags.data[k] = v;
}
}
tags.ping()
return
tags.ping();
return;
}
dispatch("saved", { config, applied: selectedTags })
dispatch("saved", { config, applied: selectedTags });
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
theme: state.layout.id,
changeType: "answer",
})
freeformInput.setData(undefined)
selectedMapping = undefined
selectedTags = undefined
changeType: "answer"
});
freeformInput.setData(undefined);
selectedMapping = undefined;
selectedTags = undefined;
change
.CreateChangeDescriptions()
.then((changes) => state.changes.applyChanges(changes))
.catch(console.error)
.catch(console.error);
}
let featureSwitchIsTesting = state.featureSwitchIsTesting ?? new ImmutableStore(false)
let featureSwitchIsDebugging = state.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
let showTags = state.userRelatedState?.showTags ?? new ImmutableStore(undefined)
let numberOfCs = state.osmConnection.userDetails.data.csCount
let featureSwitchIsTesting = state.featureSwitchIsTesting ?? new ImmutableStore(false);
let featureSwitchIsDebugging = state.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false);
let showTags = state.userRelatedState?.showTags ?? new ImmutableStore(undefined);
let numberOfCs = state.osmConnection.userDetails.data.csCount;
onDestroy(
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
numberOfCs = ud.csCount
numberOfCs = ud.csCount;
})
)
);
</script>
{#if config.question !== undefined}
@ -218,6 +218,7 @@
value={freeformInput}
on:selected={() => (selectedMapping = config.mappings?.length)}
on:submit={onSave}
submit={onSave}
/>
</label>
{/if}

View file

@ -1,69 +1,75 @@
<script lang="ts">
import EditLayerState from "./EditLayerState";
import layerSchemaRaw from "../../assets/schemas/layerconfigmeta.json"
import Region from "./Region.svelte";
import TabbedGroup from "../Base/TabbedGroup.svelte";
import {UIEventSource} from "../../Logic/UIEventSource";
import type {ConfigMeta} from "./configMeta";
import {Utils} from "../../Utils";
import EditLayerState, { LayerStateSender } from "./EditLayerState";
import layerSchemaRaw from "../../assets/schemas/layerconfigmeta.json";
import Region from "./Region.svelte";
import TabbedGroup from "../Base/TabbedGroup.svelte";
import { Store, UIEventSource } from "../../Logic/UIEventSource";
import type { ConfigMeta } from "./configMeta";
import { Utils } from "../../Utils";
import drinking_water from "../../../assets/layers/drinking_water/drinking_water.json"
const layerSchema: ConfigMeta[] = <any>layerSchemaRaw;
let state = new EditLayerState(layerSchema);
state.configuration.setData({});
const configuration = state.configuration;
new LayerStateSender("http://localhost:1235", state);
/**
* 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"];
const allNames = Utils.Dedup(layerSchema.map(meta => meta.hints.group));
const layerSchema: ConfigMeta[] = <any> layerSchemaRaw
let state = new EditLayerState(layerSchema)
state.configuration.setData(drinking_water)
/**
* Blacklist for the general area tab
*/
const regionBlacklist = ["hidden",undefined,"infobox", "tagrenderings","maprendering", "editing", "title"]
const allNames = Utils.Dedup(layerSchema.map(meta => meta.hints.group))
const perRegion: Record<string, ConfigMeta[]> = {};
for (const region of allNames) {
perRegion[region] = layerSchema.filter(meta => meta.hints.group === region);
}
const perRegion: Record<string, ConfigMeta[]> = {}
for (const region of allNames) {
perRegion[region] = layerSchema.filter(meta => meta.hints.group === region)
const baselayerRegions: string[] = ["Basic", "presets", "filters", "advanced", "expert"];
for (const baselayerRegion of baselayerRegions) {
if (perRegion[baselayerRegion] === undefined) {
console.error("BaseLayerRegions in editLayer: no items have group '" + baselayerRegion + "\"");
}
const baselayerRegions: string[] = ["Basic", "presets","filters","advanced","expert"]
for (const baselayerRegion of baselayerRegions) {
if(perRegion[baselayerRegion] === undefined){
console.error("BaseLayerRegions in editLayer: no items have group '"+baselayerRegion+'"')
}
}
const leftoverRegions : string[] = allNames.filter(r => regionBlacklist.indexOf(r) <0 && baselayerRegions.indexOf(r) <0 )
}
const leftoverRegions: string[] = allNames.filter(r => regionBlacklist.indexOf(r) < 0 && baselayerRegions.indexOf(r) < 0);
const title: Store<string> = state.getStoreFor(["id"]);
</script>
<h3>Edit layer</h3>
<h3>Editing layer {$title}</h3>
<h4>Leftover regions</h4>
{leftoverRegions.join("; ")}
<div class="m4">
{allNames}
<TabbedGroup tab={new UIEventSource(1)}>
<TabbedGroup tab={new UIEventSource(2)}>
<div slot="title0">General properties</div>
<div class="flex flex-col" slot="content0">
{#each baselayerRegions as region}
<Region {state} configs={perRegion[region]} title={region}/>
{/each}
{#each leftoverRegions as region}
<Region {state} configs={perRegion[region]} title={region}/>
{/each}
{#each baselayerRegions as region}
<Region {state} configs={perRegion[region]} title={region} />
{/each}
{#each leftoverRegions as region}
<Region {state} configs={perRegion[region]} title={region} />
{/each}
</div>
<div slot="title1">Information panel (questions and answers)</div>
<div slot="content1">
<Region {state} configs={perRegion["tagrenderings"]} title="Popup contents">
<div slot="description">
The bulk of the popup content
</div>
</Region>
<Region {state} configs={perRegion["title"]} title="Popup title"/>
<Region {state} configs={perRegion["editing"]} title="Other editing elements"/>
<Region configs={perRegion["title"]} {state} title="Popup title" />
<Region configs={perRegion["tagrenderings"]} {state} title="Popup contents"/>
<Region configs={perRegion["editing"]} {state} title="Other editing elements" />
</div>
<div slot="title2">Rendering on the map</div>
<div slot="content2">
TODO: rendering on the map
<Region configs={perRegion["maprendering"]} {state} />
</div>
</TabbedGroup>
<div slot="title3">Configuration file</div>
<div slot="content3">
<div>
Below, you'll find the raw configuration file in `.json`-format.
This is mostly for debugging purposes
</div>
<div class="literal-code">
{JSON.stringify($configuration, null, " ")}
</div>
</div>
</TabbedGroup>
</div>

View file

@ -2,6 +2,30 @@ 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"
/**
* Sends changes back to the server
*/
export class LayerStateSender {
constructor(serverLocation: string, layerState: EditLayerState) {
layerState.configuration.addCallback(async (config) => {
// console.log("Current config is", Utils.Clone(config))
const id = config.id
if (id === undefined) {
console.log("No id found in layer, not updating")
return
}
const response = await fetch(`${serverLocation}/layers/${id}/${id}.json`, {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify(config, null, " "),
})
})
}
}
export default class EditLayerState {
public readonly osmConnection: OsmConnection
@ -15,13 +39,17 @@ export default class EditLayerState {
constructor(schema: ConfigMeta[]) {
this.schema = schema
this.osmConnection = new OsmConnection({})
this.osmConnection = new OsmConnection({
oauth_token: QueryParameters.GetQueryParameter(
"oauth_token",
undefined,
"Used to complete the login"
),
})
this.featureSwitches = {
featureSwitchIsDebugging: new UIEventSource<boolean>(true),
}
this.configuration.addCallback((config) => {
// console.log("Current config is", Utils.Clone(config))
})
console.log("Configuration store:", this.configuration)
}
public getCurrentValueFor(path: ReadonlyArray<string | number>): any | undefined {
@ -78,12 +106,17 @@ export default class EditLayerState {
description: origConfig.description ?? "A translatable object",
}
}
public getSchema(path: string[]): ConfigMeta[] {
return this.schema.filter(
const schemas = this.schema.filter(
(sch) =>
sch !== undefined &&
!path.some((part, i) => !(sch.path.length == path.length && sch.path[i] === part))
)
if (schemas.length == 0) {
console.warn("No schemas found for path", path.join("."))
}
return schemas
}
public setValueAt(path: ReadonlyArray<string | number>, v: any) {

View file

@ -13,7 +13,11 @@ export let title: string | undefined = undefined;
export let path: (string | number)[] = [];
</script>
{#if title}
{#if configs === undefined}
Bug: 'Region' received 'undefined'
{:else if configs.length === 0}
Bug: Region received empty list as configuration
{:else if title}
<div class="w-full flex flex-col">
<h3>{title}</h3>
<div class="pl-2 border border-black flex flex-col gap-y-1 w-full">
@ -24,6 +28,9 @@ export let path: (string | number)[] = [];
</div>
</div>
{:else}
<div class="literal-code">
{JSON.stringify(configs, null, " ")}
</div>
<div class="pl-2 flex flex-col gap-y-1 w-full">
{#each configs as config}
<SchemaBasedInput {state} path={path.concat(config.path)} schema={config} />

View file

@ -121,7 +121,7 @@
<div class="flex">
<button on:click={() => createItem()}>Add {article} {singular}</button>
{#if path.length === 1 && path[0] === "tagRenderings"}
<button on:click={() => {createItem();}}>Add a builtin tagRendering</button>
<button on:click={() => {createItem("images");}}>Add a builtin tagRendering</button>
{/if}
<slot name="extra-button" />
</div>

View file

@ -11,21 +11,21 @@
import EditLayerState from "./EditLayerState";
import { onDestroy } from "svelte";
import type { JsonSchemaType } from "./jsonSchema";
import { ConfigMetaUtils } from "./configMeta.ts"
export let state: EditLayerState
export let path: (string | number)[] = []
export let schema: ConfigMeta
let value = new UIEventSource<string>(undefined)
const isTranslation = schema.hints.typehint === "translation" || schema.hints.typehint === "rendered" || ConfigMetaUtils.isTranslation(schema)
let type = schema.hints.typehint ?? "string"
if(type === "rendered"){
if(isTranslation){
type = "translation"
}
if(type.endsWith("[]")){
type = type.substring(0, type.length - 2)
}
const isTranslation =schema.hints.typehint === "translation" || schema.hints.typehint === "rendered"
const configJson: QuestionableTagRenderingConfigJson = {
id: path.join("_"),

View file

@ -5,7 +5,7 @@
import SchemaBasedArray from "./SchemaBasedArray.svelte";
import SchemaBasedMultiType from "./SchemaBasedMultiType.svelte";
import SchemaBasedTranslationInput from "./SchemaBasedTranslationInput.svelte";
import { ConfigMetaUtils } from "./configMeta.ts"
export let schema: ConfigMeta;
export let state: EditLayerState;
export let path: (string | number)[] = [];
@ -16,8 +16,6 @@
<SchemaBasedArray {path} {state} {schema} />
{:else if schema.type === "array"}
<SchemaBasedArray {path} {state} {schema} />
{:else if schema.type === "translation"}
<SchemaBasedTranslationInput {path} {state} {schema} />
{:else if schema.hints.types}
<SchemaBasedMultiType {path} {state} {schema} />
{:else}

View file

@ -45,12 +45,12 @@
configJson.mappings.unshift(
{
if: "direct=true",
then: "Yes " + (schema.hints.iftrue ?? ""),
then: (schema.hints.iftrue ?? "Yes"),
addExtraTags: ["value="]
},
{
if: "direct=false",
then: "No " + (schema.hints.iffalse ?? ""),
then: (schema.hints.iffalse ?? "No"),
addExtraTags: ["value="]
}
);
@ -95,7 +95,6 @@
}
possibleTypes.sort((a, b) => b.optionalMatches - a.optionalMatches);
possibleTypes.sort((a, b) => b.matchingPropertiesCount - a.matchingPropertiesCount);
console.log(">>> possible types", possibleTypes)
if (possibleTypes.length > 0) {
tags.setData({ value: "" + possibleTypes[0].index });
}
@ -122,13 +121,19 @@
onDestroy(tags.addCallbackAndRun(tags => {
const oldOption = chosenOption;
chosenOption = tags["value"] ? Number(tags["value"]) : defaultOption;
const type = schema.type[chosenOption];
console.log("Subtype is", type, {chosenOption, oldOption, schema});
if (chosenOption !== oldOption) {
// Reset the values beneath
subSchemas = [];
state.setValueAt(path, undefined);
const o = state.getCurrentValueFor(path) ?? {}
console.log({o})
for(const key of type?.required ?? []){
console.log(key)
o[key] ??= {}
}
state.setValueAt(path, o);
}
const type = schema.type[chosenOption];
console.log("Subtype is", type);
if (!type) {
return;
}
@ -148,6 +153,7 @@
for (const crumble of Object.keys(type.properties)) {
subSchemas.push(...(state.getSchema([...cleanPath, crumble])));
}
console.log("Got subschemas for", path, ":", subSchemas)
}));

View file

@ -22,3 +22,22 @@ export interface ConfigMeta {
required: boolean
description: string
}
export class ConfigMetaUtils {
static isTranslation(configMeta: ConfigMeta) {
/* {
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}*/
if (!configMeta.type) {
return false
}
if (Array.isArray(configMeta.type)) {
return configMeta.type.some((t) => t["$ref"] === "#/definitions/Record<string,string>")
} else {
return configMeta.type["$ref"] === "#/definitions/Record<string,string>"
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -601,7 +601,8 @@
"required": false,
"hints": {
"question": "Show the freeform as box within the question?",
"ifunset": "do not show the"
"iftrue": "show the freeform input field as a small field within the question",
"ifunset": "show the freeform input field full-width"
},
"type": "boolean",
"description": "Instead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout."
@ -661,7 +662,7 @@
"required": false,
"hints": {},
"type": "array",
"description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away"
"description": "A list of labels. These are strings that are used for various purposes, e.g. to only include a subset of the tagRenderings when reusing a layer"
},
{
"path": [

View file

@ -126,6 +126,72 @@
],
"description": "\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```"
},
{
"path": [
"condition",
"and"
],
"required": false,
"hints": {
"typehint": "tag"
},
"type": [
{
"$ref": "#/definitions/{and:TagConfigJson[];}"
},
{
"type": "object",
"properties": {
"or": {
"type": "array",
"items": {
"$ref": "#/definitions/TagConfigJson"
}
}
},
"required": [
"or"
]
},
{
"type": "string"
}
],
"description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation\n"
},
{
"path": [
"condition",
"or"
],
"required": false,
"hints": {
"typehint": "tag"
},
"type": [
{
"$ref": "#/definitions/{and:TagConfigJson[];}"
},
{
"type": "object",
"properties": {
"or": {
"type": "array",
"items": {
"$ref": "#/definitions/TagConfigJson"
}
}
},
"required": [
"or"
]
},
{
"type": "string"
}
],
"description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation\n"
},
{
"path": [
"metacondition"
@ -147,6 +213,72 @@
],
"description": "\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_"
},
{
"path": [
"metacondition",
"and"
],
"required": false,
"hints": {
"typehint": "tag"
},
"type": [
{
"$ref": "#/definitions/{and:TagConfigJson[];}"
},
{
"type": "object",
"properties": {
"or": {
"type": "array",
"items": {
"$ref": "#/definitions/TagConfigJson"
}
}
},
"required": [
"or"
]
},
{
"type": "string"
}
],
"description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation\n"
},
{
"path": [
"metacondition",
"or"
],
"required": false,
"hints": {
"typehint": "tag"
},
"type": [
{
"$ref": "#/definitions/{and:TagConfigJson[];}"
},
{
"type": "object",
"properties": {
"or": {
"type": "array",
"items": {
"$ref": "#/definitions/TagConfigJson"
}
}
},
"required": [
"or"
]
},
{
"type": "string"
}
],
"description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation\n"
},
{
"path": [
"freeform"
@ -178,6 +310,40 @@
"type": "array",
"description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes"
},
{
"path": [
"mappings",
"if"
],
"required": true,
"hints": {
"typehint": "tag",
"question": "When should this single mapping match?"
},
"type": [
{
"$ref": "#/definitions/{and:TagConfigJson[];}"
},
{
"type": "object",
"properties": {
"or": {
"type": "array",
"items": {
"$ref": "#/definitions/TagConfigJson"
}
}
},
"required": [
"or"
]
},
{
"type": "string"
}
],
"description": "\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}'}"
},
{
"path": [
"mappings",

View file

@ -1,19 +1,16 @@
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"
import * as theme from "./assets/generated/themes/bookcases.json"
import ThemeViewState from "./Models/ThemeViewState"
import Combine from "./UI/Base/Combine"
import SpecialVisualizations from "./UI/SpecialVisualizations"
import { Utils } from "./Utils"
function testspecial() {
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
const state = new ThemeViewState(layout)
const all = SpecialVisualizations.specialVisualizations.map((s) =>
SpecialVisualizations.renderExampleOfSpecial(state, s)
)
new Combine(all).AttachTo("maindiv")
class Test {
public async test() {
await Utils.waitFor(0)
const response = await fetch("http://localhost:1235/layers/atm/atm.json", {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify({}),
})
}
}
/*/
testspecial()
//*/
new Test().test()