From e069b31e4e6366841f9fa8ce856da50785c32a79 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 9 Sep 2020 18:42:13 +0200 Subject: [PATCH] Various bug fixes and updates --- Customizations/JSON/FromJSON.ts | 7 +- Customizations/JSON/LayerConfigJson.ts | 8 + Customizations/LayerDefinition.ts | 44 +-- Customizations/Layers/Bos.ts | 2 +- Customizations/Layers/NatureReserves.ts | 2 +- Customizations/Layers/Park.ts | 4 +- Customizations/Layers/Widths.ts | 4 +- Customizations/Layouts/Groen.ts | 2 +- Customizations/OnlyShowIf.ts | 2 +- Customizations/Questions/AccessTag.ts | 2 +- Customizations/Questions/NameInline.ts | 16 +- Customizations/Questions/NameQuestion.ts | 44 ++- Customizations/Questions/OsmLink.ts | 4 +- Customizations/TagRendering.ts | 44 ++- Logic/FilteredLayer.ts | 39 +-- Logic/Tags.ts | 64 ++++- State.ts | 2 +- UI/CustomGenerator/AllLayersPanel.ts | 19 +- UI/CustomGenerator/GenerateEmpty.ts | 1 + UI/CustomGenerator/LayerPanel.ts | 5 +- UI/CustomGenerator/LayerPanelWithPreview.ts | 1 - UI/CustomGenerator/TagRenderingPanel.ts | 2 +- UI/Input/FixedInputElement.ts | 10 +- UI/Input/RadioButton.ts | 5 +- UI/SimpleAddUI.ts | 3 + UI/UIElement.ts | 10 +- .../bike_repair_station.json | 14 +- .../layers/nature_reserve/nature_reserve.json | 252 ++++++++++++++++++ test/Tag.spec.ts | 18 +- 29 files changed, 482 insertions(+), 148 deletions(-) create mode 100644 assets/layers/nature_reserve/nature_reserve.json diff --git a/Customizations/JSON/FromJSON.ts b/Customizations/JSON/FromJSON.ts index cbba4b6..b390127 100644 --- a/Customizations/JSON/FromJSON.ts +++ b/Customizations/JSON/FromJSON.ts @@ -17,6 +17,7 @@ import * as viewpoint from "../../assets/layers/viewpoint/viewpoint.json" import * as bike_parking from "../../assets/layers/bike_parking/bike_parking.json" import * as bike_repair_station from "../../assets/layers/bike_repair_station/bike_repair_station.json" import * as birdhides from "../../assets/layers/bird_hide/birdhides.json" +import * as nature_reserve from "../../assets/layers/nature_reserve/nature_reserve.json" import {Utils} from "../../Utils"; @@ -34,6 +35,7 @@ export class FromJSON { FromJSON.Layer(bike_parking), FromJSON.Layer(bike_repair_station), FromJSON.Layer(birdhides), + FromJSON.Layer(nature_reserve), ]; for (const layer of sharedLayersList) { @@ -102,7 +104,6 @@ export class FromJSON { public static TagRenderingWithDefault(json: TagRenderingConfigJson | string, propertyName, defaultValue: string): TagDependantUIElementConstructor { if (json === undefined) { if(defaultValue !== undefined){ - console.log(`Using default value ${defaultValue} for ${propertyName}`) return FromJSON.TagRendering(defaultValue, propertyName); } throw `Tagrendering ${propertyName} is undefined...` @@ -207,7 +208,7 @@ export class FromJSON { return new Tag(tag[0], tag[1]); } - public static Tag(json: AndOrTagConfigJson | string, context: string): TagsFilter { + public static Tag(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter { if(json === undefined){ throw "Error while parsing a tag: nothing defined. Make sure all the tags are defined and at least one tag is present in a complex expression" } @@ -286,7 +287,6 @@ export class FromJSON { private static LayerUncaught(json: LayerConfigJson): LayerDefinition { - console.log("Parsing layer", json) const tr = FromJSON.Translation; const overpassTags = FromJSON.Tag(json.overpassTags, "overpasstags for layer "+json.id); const icon = FromJSON.TagRenderingWithDefault(json.icon, "icon", "./assets/bug.svg"); @@ -381,6 +381,7 @@ export class FromJSON { } ); + layer.maxAllowedOverlapPercentage = json.hideUnderlayingFeaturesMinPercentage; return layer; } diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index b8f488b..1eac673 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -70,6 +70,14 @@ export interface LayerConfigJson { */ wayHandling?: number; + /** + * Consider that we want to show 'Nature Reserves' and 'Forests'. Now, ofter, there are pieces of forest mapped _in_ the nature reserve. + * Now, showing those pieces of forest overlapping with the nature reserve truly clutters the map and is very user-unfriendly. + * + * The features are placed layer by layer. If a feature below a feature on this layer overlaps for more then 'x'-percent, the underlying feature is hidden. + */ + hideUnderlayingFeaturesMinPercentage?:number; + /** * Presets for this layer */ diff --git a/Customizations/LayerDefinition.ts b/Customizations/LayerDefinition.ts index a17b7de..10b5d3e 100644 --- a/Customizations/LayerDefinition.ts +++ b/Customizations/LayerDefinition.ts @@ -129,47 +129,5 @@ export class LayerDefinition { this.style = options.style; this.wayHandling = options.wayHandling ?? LayerDefinition.WAYHANDLING_DEFAULT; } - -/* - ToJson() { - - function t(translation: string | Translation | UIElement) { - if (translation === undefined) { - return undefined; - } - if (typeof (translation) === "string") { - return translation; - } - if (translation instanceof Translation && translation.translations !== undefined) { - return translation.translations; - } - return translation.InnerRender(); - } - - function tr(tagRendering : TagRenderingOptions) : TagRenderingConfigJson{ - const o = tagRendering.options; - return { - key: o.freeform.key, - render: o.freeform.renderTemplate, - type: o.freeform.template. - } - } - - const layerConfig : LayerConfigJson = { - name: t(this.name), - description: t(this.description), - maxAllowedOverlapPercentage: this.maxAllowedOverlapPercentage, - presets: this.presets, - icon: this.icon, - minzoom: this.minzoom, - overpassFilter: this.overpassFilter, - title: this.title, - elementsToShow: this.elementsToShow, - style: this.style, - wayHandling: this.wayHandling, - - }; - - return JSON.stringify(layerConfig) - }*/ + } \ No newline at end of file diff --git a/Customizations/Layers/Bos.ts b/Customizations/Layers/Bos.ts index 28a4184..d002742 100644 --- a/Customizations/Layers/Bos.ts +++ b/Customizations/Layers/Bos.ts @@ -35,7 +35,7 @@ export class Bos extends LayerDefinition { this.minzoom = 13; this.style = this.generateStyleFunction(); - this.title = new NameInline("bos"); + this.title = new NameInline("Bos"); this.elementsToShow = [ new ImageCarouselWithUploadConstructor(), new NameQuestion(), diff --git a/Customizations/Layers/NatureReserves.ts b/Customizations/Layers/NatureReserves.ts index 6beb53a..3725f84 100644 --- a/Customizations/Layers/NatureReserves.ts +++ b/Customizations/Layers/NatureReserves.ts @@ -26,7 +26,7 @@ export class NatureReserves extends LayerDefinition { } ]; this.minzoom = 13; - this.title = new NameInline("natuurreservaat"); + this.title = new NameInline("Natuurreservaat"); this.style = this.generateStyleFunction(); this.elementsToShow = [ new ImageCarouselWithUploadConstructor(), diff --git a/Customizations/Layers/Park.ts b/Customizations/Layers/Park.ts index ff1f7a6..6fe87f8 100644 --- a/Customizations/Layers/Park.ts +++ b/Customizations/Layers/Park.ts @@ -13,7 +13,7 @@ export class Park extends LayerDefinition { question: "Is dit park publiek toegankelijk?", mappings: [ {k: new Tag("access", "yes"), txt: "Publiek toegankelijk"}, - {k: new Tag("access", ""), txt: "Publiek toegankelijk"}, + {k: new Tag("access", ""), txt: "Publiek toegankelijk", hideInAnswer: true}, {k: new Tag("access", "no"), txt: "Niet publiek toegankelijk"}, {k: new Tag("access", "private"), txt: "Niet publiek toegankelijk, want privaat"}, {k: new Tag("access", "guided"), txt: "Enkel toegankelijk met een gids of op een activiteit"}, @@ -59,7 +59,7 @@ export class Park extends LayerDefinition { this.minzoom = 13; this.style = this.generateStyleFunction(); - this.title = new NameInline("park"); + this.title = new NameInline("Park"); this.elementsToShow = [ new ImageCarouselWithUploadConstructor(), new NameQuestion(), diff --git a/Customizations/Layers/Widths.ts b/Customizations/Layers/Widths.ts index 22995e7..f873ef0 100644 --- a/Customizations/Layers/Widths.ts +++ b/Customizations/Layers/Widths.ts @@ -168,12 +168,12 @@ export class Widths extends LayerDefinition { let dashArray = undefined; if (props.onewayBike) { - dashArray = [20, 8] + dashArray = [5, 6] } return { icon: null, color: c, - weight: 9, + weight: 5, dashArray: dashArray } } diff --git a/Customizations/Layouts/Groen.ts b/Customizations/Layouts/Groen.ts index 97b92b8..103f479 100644 --- a/Customizations/Layouts/Groen.ts +++ b/Customizations/Layouts/Groen.ts @@ -1,7 +1,7 @@ -import {NatureReserves} from "../Layers/NatureReserves"; import {Park} from "../Layers/Park"; import {Bos} from "../Layers/Bos"; import {Layout} from "../Layout"; +import {NatureReserves} from "../Layers/NatureReserves"; export class Groen extends Layout { diff --git a/Customizations/OnlyShowIf.ts b/Customizations/OnlyShowIf.ts index d439f07..0fe632d 100644 --- a/Customizations/OnlyShowIf.ts +++ b/Customizations/OnlyShowIf.ts @@ -41,7 +41,7 @@ export class OnlyShowIfConstructor implements TagDependantUIElementConstructor{ } GetContent(tags: any): Translation { - if(this.IsKnown(tags)){ + if(!this.IsKnown(tags)){ return undefined; } return this._embedded.GetContent(tags); diff --git a/Customizations/Questions/AccessTag.ts b/Customizations/Questions/AccessTag.ts index 8190cdc..6375962 100644 --- a/Customizations/Questions/AccessTag.ts +++ b/Customizations/Questions/AccessTag.ts @@ -17,7 +17,7 @@ export class AccessTag extends TagRenderingOptions { {k: new And([new Tag("access", "no"), new Tag("fee", "")]), txt: "Niet toegankelijk"}, {k: new And([new Tag("access", "private"), new Tag("fee", "")]), txt: "Niet toegankelijk, want privegebied"}, {k: new And([new Tag("access", "permissive"), new Tag("fee", "")]), txt: "Toegankelijk, maar het is privegebied"}, - {k: new And([new Tag("access", "guided"), new Tag("fee", "")]), txt: "Enkel met gids of op activiteit"}, + {k: new And([new Tag("access", "guided"), new Tag("fee", "")]), txt: "Enkel met een gids of tijdens een activiteit toegankelijk"}, { k: new And([new Tag("access", "yes"), new Tag("fee", "yes")]), diff --git a/Customizations/Questions/NameInline.ts b/Customizations/Questions/NameInline.ts index 6d53508..5a2c280 100644 --- a/Customizations/Questions/NameInline.ts +++ b/Customizations/Questions/NameInline.ts @@ -1,4 +1,4 @@ -import {Tag} from "../../Logic/Tags"; +import {RegexTag, Tag} from "../../Logic/Tags"; import Translations from "../../UI/i18n/Translations"; import {TagRenderingOptions} from "../TagRenderingOptions"; import Translation from "../../UI/i18n/Translation"; @@ -8,18 +8,10 @@ export class NameInline extends TagRenderingOptions{ constructor(category: string | Translation ) { super({ - question: "", - - freeform: { - renderTemplate: "{name}", - template: Translations.t.general.nameInlineQuestion.Subs({category: category}), - key: "name", - extraTags: new Tag("noname", "") // Remove 'noname=yes' - }, - mappings: [ - {k: new Tag("noname","yes"), txt: Translations.t.general.noNameCategory.Subs({category: category})}, - {k: null, txt: category} + {k: new Tag("noname", "yes"), txt: Translations.t.general.noNameCategory.Subs({category: category})}, + {k: new RegexTag("name", /.+/), txt: "{name}"}, + {k:new Tag("name",""), txt: category} ] }); } diff --git a/Customizations/Questions/NameQuestion.ts b/Customizations/Questions/NameQuestion.ts index 7b54f8c..a8301a4 100644 --- a/Customizations/Questions/NameQuestion.ts +++ b/Customizations/Questions/NameQuestion.ts @@ -3,31 +3,29 @@ * One is a big 'name-question', the other is the 'edit name' in the title. * THis one is the big question */ -import {Tag} from "../../Logic/Tags"; +import {And, Tag} from "../../Logic/Tags"; import {TagRenderingOptions} from "../TagRenderingOptions"; -export class NameQuestion extends TagRenderingOptions{ - - static options = { - priority: 10, // Move this last on the priority list, in order to prevent ppl to enter access restrictions and descriptions - question: "Wat is de officiële naam van dit gebied?
" + - "Zelf een naam bedenken wordt afgeraden.
" + - "Een beschrijving van het gebied geven kan in een volgende stap.
" + - "
", - freeform: { - key: "name", - template: "De naam is $$$", - renderTemplate: "", // We don't actually render it, only ask - placeholder: "", - extraTags: new Tag("noname","") - }, - mappings: [ - {k: new Tag("noname", "yes"), txt: "Dit gebied heeft geen naam"}, - ] - } - +export class NameQuestion extends TagRenderingOptions { + constructor() { - super(NameQuestion.options); + super({ + priority: 10, // Move this last on the priority list, in order to prevent ppl to enter access restrictions and descriptions + question: "Wat is de officiële naam van dit gebied?
" + + "Zelf een naam bedenken wordt afgeraden.
" + + "Een beschrijving van het gebied geven kan in een volgende stap.
" + + "
", + freeform: { + key: "name", + template: "De naam is $$$", + renderTemplate: "Dit gebied heet {name}", + placeholder: "", + extraTags: new Tag("noname", "") + }, + mappings: [ + {k: new And([new Tag("name", ""), new Tag("noname", "yes")]), txt: "Dit gebied heeft geen naam"}, + ] + }); } - + } \ No newline at end of file diff --git a/Customizations/Questions/OsmLink.ts b/Customizations/Questions/OsmLink.ts index 97d8821..fdcf5e7 100644 --- a/Customizations/Questions/OsmLink.ts +++ b/Customizations/Questions/OsmLink.ts @@ -1,5 +1,5 @@ import {Img} from "../../UI/Img"; -import {Tag} from "../../Logic/Tags"; +import {RegexTag, Tag} from "../../Logic/Tags"; import {TagRenderingOptions} from "../TagRenderingOptions"; @@ -18,7 +18,7 @@ export class OsmLink extends TagRenderingOptions { placeholder: "", }, mappings: [ - {k: new Tag("id", "node/-1"), txt: "Uploading"} + {k: new RegexTag("id", /node\/-.+/), txt: "Uploading"} ] } diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts index 34c0817..c1b21ee 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -18,17 +18,16 @@ import Translation from "../UI/i18n/Translation"; import Combine from "../UI/Base/Combine"; -export class -TagRendering extends UIElement implements TagDependantUIElement { +export class TagRendering extends UIElement implements TagDependantUIElement { private readonly _priority: number; private readonly _question: string | Translation; private readonly _mapping: { k: TagsFilter, txt: string | UIElement, priority?: number }[]; - private currentTags : UIEventSource ; - - + private currentTags: UIEventSource; + + private readonly _freeform: { key: string, template: string | UIElement, @@ -110,7 +109,6 @@ TagRendering extends UIElement implements TagDependantUIElement { for (const choice of options.mappings ?? []) { - let choiceSubbed = { k: choice.k?.substituteValues(this.currentTags.data), txt: choice.txt, @@ -225,7 +223,7 @@ TagRendering extends UIElement implements TagDependantUIElement { } previousTexts.push(this.ApplyTemplate(mapping.txt)); - elements.push(this.InputElementForMapping(mapping)); + elements.push(this.InputElementForMapping(mapping, mapping.substitute)); } } @@ -247,14 +245,26 @@ TagRendering extends UIElement implements TagDependantUIElement { } - private InputElementForMapping(mapping: { k: TagsFilter, txt: (string | Translation) }) { - return new FixedInputElement(this.ApplyTemplate(mapping.txt), - mapping.k.substituteValues(this.currentTags.data) - ); + private InputElementForMapping(mapping: { k: TagsFilter, txt: (string | Translation) }, substituteValues: boolean) { + if (substituteValues) { + + return new FixedInputElement(this.ApplyTemplate(mapping.txt), + mapping.k.substituteValues(this.currentTags.data), + (t0, t1) => t0.isEquivalent(t1) + ); + } + return new FixedInputElement(this.ApplyTemplate(mapping.txt),mapping.k, + (t0, t1) => t0.isEquivalent(t1)); } - private InputForFreeForm(freeform): InputElement { + private InputForFreeForm(freeform : { + key: string, + template: string | Translation, + renderTemplate: string | Translation, + placeholder?: string | Translation, + extraTags?: TagsFilter, + }): InputElement { if (freeform?.template === undefined) { return undefined; } @@ -283,7 +293,7 @@ TagRendering extends UIElement implements TagDependantUIElement { const tag = new Tag(freeform.key, formatter(string, this._source.data._country)); if (tag.value.length > 255) { - return undefined; // Toolong + return undefined; // Too long } if (freeform.extraTags === undefined) { @@ -299,7 +309,13 @@ TagRendering extends UIElement implements TagDependantUIElement { const toString = (tag) => { if (tag instanceof And) { - return toString(tag.and[0]) + for (const subtag of tag.and) { + if(subtag instanceof Tag && subtag.key === freeform.key){ + return subtag.value; + } + } + + return undefined; } else if (tag instanceof Tag) { return tag.value } diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index ebb1db1..8ce39f9 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -216,21 +216,26 @@ export class FilteredLayer { pointToLayer: function (feature, latLng) { const style = self._style(feature.properties); let marker; - if (style.icon === undefined) { - marker = L.circle(latLng, { - radius: 25, - color: style.color - }); + if (style.icon === undefined) { + marker = L.circle(latLng, { + radius: 25, + color: style.color + }); - } else { - if(style.icon.iconSize === undefined){ - style.icon.iconSize = [50,50] - } - - marker = L.marker(latLng, { - icon: new L.icon(style.icon), - }); - } + } else if (style.icon.iconUrl.startsWith("$circle ")) { + marker = L.circle(latLng, { + radius: 25, + color: style.color + }); + } else { + if (style.icon.iconSize === undefined) { + style.icon.iconSize = [50, 50] + } + + marker = L.marker(latLng, { + icon: new L.icon(style.icon), + }); + } let eventSource = State.state.allElements.addOrGetElement(feature); const uiElement = self._showOnPopup(eventSource, feature); const popup = L.popup({}, marker).setContent(uiElement.Render()); @@ -253,11 +258,7 @@ export class FilteredLayer { } } else { self._geolayer.setStyle(function (featureX) { - const style = self._style(featureX.properties); - if (featureX === feature) { - console.log("Selected element is", featureX.properties.id) - } - return style; + return self._style(featureX.properties); }); } } diff --git a/Logic/Tags.ts b/Logic/Tags.ts index df8a280..a0afad8 100644 --- a/Logic/Tags.ts +++ b/Logic/Tags.ts @@ -4,7 +4,9 @@ export abstract class TagsFilter { abstract matches(tags: { k: string, v: string }[]): boolean abstract asOverpass(): string[] abstract substituteValues(tags: any) : TagsFilter; - abstract isUsableAsAnswer() : boolean; + abstract isUsableAsAnswer(): boolean; + + abstract isEquivalent(other: TagsFilter): boolean; matchesProperties(properties: Map): boolean { return this.matches(TagUtils.proprtiesToKV(properties)); @@ -58,16 +60,26 @@ export class RegexTag extends TagsFilter { return this.invert; } - substituteValues(tags: any) : TagsFilter{ + substituteValues(tags: any): TagsFilter { return this; } - + asHumanString() { if (typeof this.key === "string") { return `${this.key}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}`; } return `~${this.key.source}${this.invert ? "!" : ""}~${RegexTag.source(this.value)}` } + + isEquivalent(other: TagsFilter): boolean { + if (other instanceof RegexTag) { + return other.asHumanString() == this.asHumanString(); + } + if(other instanceof Tag){ + return RegexTag.doesMatch(other.key, this.key) && RegexTag.doesMatch(other.value, this.value); + } + return false; + } } @@ -140,6 +152,16 @@ export class Tag extends TagsFilter { isUsableAsAnswer(): boolean { return true; } + + isEquivalent(other: TagsFilter): boolean { + if(other instanceof Tag){ + return this.key === other.key && this.value === other.value; + } + if(other instanceof RegexTag){ + other.isEquivalent(this); + } + return false; + } } @@ -187,6 +209,24 @@ export class Or extends TagsFilter { isUsableAsAnswer(): boolean { return false; } + + isEquivalent(other: TagsFilter): boolean { + if(other instanceof Or){ + + for (const selfTag of this.or) { + let matchFound = false; + for (let i = 0; i < other.or.length && !matchFound; i++){ + let otherTag = other.or[i]; + matchFound = selfTag.isEquivalent(otherTag); + } + if(!matchFound){ + return false; + } + } + return true; + } + return false; + } } @@ -256,6 +296,24 @@ export class And extends TagsFilter { } return true; } + + isEquivalent(other: TagsFilter): boolean { + if(other instanceof And){ + + for (const selfTag of this.and) { + let matchFound = false; + for (let i = 0; i < other.and.length && !matchFound; i++){ + let otherTag = other.and[i]; + matchFound = selfTag.isEquivalent(otherTag); + } + if(!matchFound){ + return false; + } + } + return true; + } + return false; + } } diff --git a/State.ts b/State.ts index 6a94316..416928f 100644 --- a/State.ts +++ b/State.ts @@ -23,7 +23,7 @@ export class State { // The singleton of the global state public static state: State; - public static vNumber = "0.0.7j"; + public static vNumber = "0.0.7k"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/UI/CustomGenerator/AllLayersPanel.ts b/UI/CustomGenerator/AllLayersPanel.ts index cf33733..2e28b02 100644 --- a/UI/CustomGenerator/AllLayersPanel.ts +++ b/UI/CustomGenerator/AllLayersPanel.ts @@ -10,6 +10,8 @@ import {UserDetails} from "../../Logic/Osm/OsmConnection"; import {MultiInput} from "../Input/MultiInput"; import TagRenderingPanel from "./TagRenderingPanel"; import SingleSetting from "./SingleSetting"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import {FromJSON} from "../../Customizations/JSON/FromJSON"; export default class AllLayersPanel extends UIElement { @@ -49,9 +51,22 @@ export default class AllLayersPanel extends UIElement { const layers = this._config.data.layers; for (let i = 0; i < layers.length; i++) { - tabs.push({ - header: "", + header: new VariableUiElement(this._config.map((config: LayoutConfigJson) => { + const layer = config.layers[i]; + if (typeof layer !== "string") { + try { + const iconTagRendering = FromJSON.TagRendering(layer.icon, "icon"); + const icon = iconTagRendering.GetContent({"id": "node/-1"}).txt; + return `` + } catch (e) { + return "" + // Nothing to do here + } + } + return "" + + })), content: new LayerPanelWithPreview(this._config, this.languages, i, userDetails) }); } diff --git a/UI/CustomGenerator/GenerateEmpty.ts b/UI/CustomGenerator/GenerateEmpty.ts index a1102e7..8427464 100644 --- a/UI/CustomGenerator/GenerateEmpty.ts +++ b/UI/CustomGenerator/GenerateEmpty.ts @@ -12,6 +12,7 @@ export class GenerateEmpty { title: {}, description: {}, tagRenderings: [], + hideUnderlayingFeaturesMinPercentage: 0, icon: { render: "./assets/bug.svg" }, diff --git a/UI/CustomGenerator/LayerPanel.ts b/UI/CustomGenerator/LayerPanel.ts index 97a8edd..97e3796 100644 --- a/UI/CustomGenerator/LayerPanel.ts +++ b/UI/CustomGenerator/LayerPanel.ts @@ -96,7 +96,10 @@ export default class LayerPanel extends UIElement { {value: 2, shown: "Show both the ways/areas and the centerpoints"}, {value: 1, shown: "Show everything as centerpoint"}]), "wayHandling", "Way handling", "Describes how ways and areas are represented on the map: areas can be represented as the area itself, or it can be converted into the centerpoint"), - + setting(TextField.NumberInput("nat", n => n <= 100), "hideUnderlayingFeaturesMinPercentage", "Max allowed overlap percentage", + "Consider that we want to show 'Nature Reserves' and 'Forests'. Now, ofter, there are pieces of forest mapped _in_ the nature reserve.
" + + "Now, showing those pieces of forest overlapping with the nature reserve truly clutters the map and is very user-unfriendly.
" + + "The features are placed layer by layer. If a feature below a feature on this layer overlaps for more then 'x'-percent, the underlying feature is hidden."), setting(new AndOrTagInput(), "overpassTags", "Overpass query", "The tags of the objects to load from overpass"), diff --git a/UI/CustomGenerator/LayerPanelWithPreview.ts b/UI/CustomGenerator/LayerPanelWithPreview.ts index 891ad6c..16e17f1 100644 --- a/UI/CustomGenerator/LayerPanelWithPreview.ts +++ b/UI/CustomGenerator/LayerPanelWithPreview.ts @@ -13,7 +13,6 @@ import {UserDetails} from "../../Logic/Osm/OsmConnection"; export default class LayerPanelWithPreview extends UIElement{ private panel: UIElement; - constructor(config: UIEventSource, languages: UIEventSource, index: number, userDetails: UserDetails) { super(); diff --git a/UI/CustomGenerator/TagRenderingPanel.ts b/UI/CustomGenerator/TagRenderingPanel.ts index aa81aa5..367ca86 100644 --- a/UI/CustomGenerator/TagRenderingPanel.ts +++ b/UI/CustomGenerator/TagRenderingPanel.ts @@ -91,7 +91,7 @@ export default class TagRenderingPanel extends InputElementMappings", setting(new MultiInput<{ if: AndOrTagConfigJson, then: (string | any), hideInAnswer?: boolean }>("Add a mapping", - () => ({if: undefined, then: undefined}), + () => ({if: {and: []}, then: {}}), () => new MappingInput(languages, options?.disableQuestions ?? false), undefined, {allowMovement: true}), "mappings", "If a tag matches, then show the first respective text", "") diff --git a/UI/Input/FixedInputElement.ts b/UI/Input/FixedInputElement.ts index 6a19a1a..0309a5c 100644 --- a/UI/Input/FixedInputElement.ts +++ b/UI/Input/FixedInputElement.ts @@ -7,9 +7,13 @@ export class FixedInputElement extends InputElement { private readonly rendering: UIElement; private readonly value: UIEventSource; public readonly IsSelected : UIEventSource = new UIEventSource(false); + private readonly _comparator: (t0: T, t1: T) => boolean; - constructor(rendering: UIElement | string, value: T) { + constructor(rendering: UIElement | string, + value: T, + comparator: ((t0: T, t1: T) => boolean ) = undefined) { super(undefined); + this._comparator = comparator ?? ((t0, t1) => t0 == t1); this.value = new UIEventSource(value); this.rendering = typeof (rendering) === 'string' ? new FixedUiElement(rendering) : rendering; } @@ -22,7 +26,9 @@ export class FixedInputElement extends InputElement { } IsValid(t: T): boolean { - return t == this.value.data; + + console.log("Comparing ",t, "with", this.value.data); + return this._comparator(t, this.value.data); } protected InnerUpdate(htmlElement: HTMLElement) { diff --git a/UI/Input/RadioButton.ts b/UI/Input/RadioButton.ts index 196efed..b120eff 100644 --- a/UI/Input/RadioButton.ts +++ b/UI/Input/RadioButton.ts @@ -8,14 +8,15 @@ export class RadioButton extends InputElement { private readonly _selectedElementIndex: UIEventSource = new UIEventSource(null); - private value: UIEventSource; + private readonly value: UIEventSource; private readonly _elements: InputElement[] - private _selectFirstAsDefault: boolean; + private readonly _selectFirstAsDefault: boolean; constructor(elements: InputElement[], selectFirstAsDefault = true) { super(undefined); + console.log("Created new radiobutton with values ", elements) this._elements = Utils.NoNull(elements); this._selectFirstAsDefault = selectFirstAsDefault; const self = this; diff --git a/UI/SimpleAddUI.ts b/UI/SimpleAddUI.ts index a3a9a89..70c47b2 100644 --- a/UI/SimpleAddUI.ts +++ b/UI/SimpleAddUI.ts @@ -54,6 +54,9 @@ export class SimpleAddUI extends UIElement { if (typeof (preset.icon) !== "string") { const tags = Utils.MergeTags(TagUtils.KVtoProperties(preset.tags), {id:"node/-1"}); icon = preset.icon.GetContent(tags).txt; + if(icon.startsWith("$")){ + icon = undefined; + } } else { icon = preset.icon; } diff --git a/UI/UIElement.ts b/UI/UIElement.ts index cae8fb5..f1c0d12 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -13,6 +13,8 @@ export abstract class UIElement extends UIEventSource { private _hideIfEmpty = false; public dumbMode = false; + + private lastInnerRender: string; /** * In the 'deploy'-step, some code needs to be run by ts-node. @@ -37,6 +39,7 @@ export abstract class UIElement extends UIEventSource { this.dumbMode = false; const self = this; source.addCallback(() => { + self.lastInnerRender = undefined; self.Update(); }) return this; @@ -92,7 +95,7 @@ export abstract class UIElement extends UIEventSource { return; } - this.setData(this.InnerRender()); + this.setData(this.lastInnerRender ?? this.InnerRender()); element.innerHTML = this.data; if (this._hideIfEmpty) { @@ -151,14 +154,15 @@ export abstract class UIElement extends UIEventSource { } Render(): string { + this.lastInnerRender = this.lastInnerRender ?? this.InnerRender(); if (this.dumbMode) { - return this.InnerRender(); + return this.lastInnerRender; } let style = ""; if (this.style !== undefined && this.style !== "") { style = `style="${this.style}"`; } - return `${this.InnerRender()}` + return `${this.lastInnerRender}` } AttachTo(divId: string) { diff --git a/assets/layers/bike_repair_station/bike_repair_station.json b/assets/layers/bike_repair_station/bike_repair_station.json index 12d7698..59daccf 100644 --- a/assets/layers/bike_repair_station/bike_repair_station.json +++ b/assets/layers/bike_repair_station/bike_repair_station.json @@ -52,7 +52,12 @@ "if": { "and": [ "service:bicycle:pump:operational_status=broken", - "service:bicycle:tools=no" + { + "or": [ + "service:bicycle:tools=no", + "service:bicycle:tools=" + ] + } ] }, "then": { @@ -381,7 +386,12 @@ "if": { "and": [ "service:bicycle:pump=yes", - "service:bicycle:tools=no" + { + "or": [ + "service:bicycle:tools=no", + "service:bicycle:tools=" + ] + } ] }, "then": "./assets/layers/bike_repair_station/pump.svg" diff --git a/assets/layers/nature_reserve/nature_reserve.json b/assets/layers/nature_reserve/nature_reserve.json new file mode 100644 index 0000000..d3e4839 --- /dev/null +++ b/assets/layers/nature_reserve/nature_reserve.json @@ -0,0 +1,252 @@ +{ + "id": "nature_reserve_simple", + "name": "Layer", + "minzoom": 10, + "overpassTags": { + "or": [ + "leisure=nature_reserve", + "boundary=protected_area" + ] + }, + "title": { + "mappings": [ + { + "if": { + "and": [ + "name~*" + ] + }, + "then": { + "nl": "Natuurgebied {name}" + } + } + ], + "render": { + "nl": "Natuurgebied" + } + }, + "description": { + "nl": "Een natuurreservaat is een gebied dat wordt beheerd door Natuurpunt, ANB of een privépersoon zodat deze biodiversiteit bevordert. " + }, + "tagRenderings": [ + { + "question": { + "nl": "Is dit gebied vrij toegankelijk?" + }, + "freeform": { + "key": "access:description", + "addExtraTags": [] + }, + "render": { + "nl": "De toegankelijkheid van dit gebied is {access:description}" + }, + "mappings": [ + { + "if": { + "and": [ + "access=yes", + "fee=" + ] + }, + "then": { + "nl": "Publiek toegankelijk" + } + }, + { + "if": { + "and": [ + "access=no", + "fee=" + ] + }, + "then": { + "nl": "Niet publiek toegankelijk" + } + }, + { + "if": { + "and": [ + "access=guided", + "fee=" + ] + }, + "then": { + "nl": "Enkel met gids of op activiteit" + } + }, + { + "if": { + "and": [ + "access=private", + "fee=" + ] + }, + "then": { + "nl": "Niet toegankelijk privégebied" + } + }, + { + "if": { + "and": [ + "access=permissive", + "fee=" + ] + }, + "then": { + "nl": "Toegankelijk, maar het is privégebied" + } + }, + { + "if": { + "and": [ + "access=yes", + "fee=yes" + ] + }, + "then": { + "nl": "Toegankelijk mits betaling" + } + } + ] + }, + { + "render": { + "nl": "Beheer door {operator}" + }, + "question": { + "nl": "Wie beheert dit natuurgebied?" + }, + "freeform": { + "key": "operator" + }, + "mappings": [ + { + "if": { + "and": [ + "operator=Natuurpunt" + ] + }, + "then": { + "nl": "Beheer door Natuurpunt" + } + }, + { + "if": { + "and": [ + "operator=Agenstchap Natuur en Bos" + ] + }, + "then": { + "nl": "Beheer door het Agentschap Natuur en Bos (ANB)" + } + } + ] + }, + { + "question": { + "nl": "Wat is de officiële naam van dit natuurgebied?
Sommige gebieden hebben geen naam" + }, + "freeform": { + "key": "name", + "addExtraTags": [ + "noname=" + ] + }, + "render": { + "nl": "Dit gebied heet {name}" + }, + "mappings": [ + { + "if": { + "and": [ + "noname=yes", + "name=" + ] + }, + "then": { + "nl": "Dit gebied heeft geen naam" + } + } + ], + "condition": { + "and": [ + "name:nl=" + ] + } + }, + { + "render": { + "nl": "De naam van dit gebied is {name:nl}" + }, + "freeform": { + "key": "name:nl" + }, + "question": { + "nl": "Wat is de Nederlandstalige naam van dit gebied?" + }, + "condition": { + "and": [ + "name:nl~*" + ] + } + }, + { + "render": { + "nl": "Meer uitleg:
{description:0}" + }, + "question": { + "nl": "Zijn er nog opmerkingen of vermeldenswaardigheden?" + }, + "freeform": { + "key": "description:0" + } + } + ], + "icon": { + "render": "$circle" + }, + "width": { + "render": "3" + }, + "iconSize": { + "render": "40,40,center" + }, + "color": { + "render": "#c90014", + "mappings": [ + { + "if": { + "and": [ + "name~*", + "operator~*", + "access~*" + ] + }, + "then": "#37c65b" + }, + { + "if": { + "and": [ + "name~*", + "access~*" + ] + }, + "then": "#c98d00" + } + ] + }, + "presets": [ + { + "tags": [ + "leisure=nature_reserve" + ], + "title": { + "nl": "Natuurreservaat" + }, + "description": { + "nl": "Voeg een ontbrekend, erkend natuurreservaat toe, bv. een gebied dat beheerd wordt door het ANB of natuurpunt" + } + } + ], + "hideUnderlayingFeaturesMinPercentage": 10 +} \ No newline at end of file diff --git a/test/Tag.spec.ts b/test/Tag.spec.ts index 0a224ac..11674a9 100644 --- a/test/Tag.spec.ts +++ b/test/Tag.spec.ts @@ -54,14 +54,22 @@ new T([ } ], condition: "x=" - }); + }, ""); equal(true, tr.IsKnown({"noname": "yes"})); equal(true, tr.IsKnown({"name": "ABC"})); equal(false, tr.IsKnown({"foo": "bar"})); - equal("Has no name", tr.GetContent({"noname": "yes"})); - equal("Ook een xyz", tr.GetContent({"name": "xyz"})); - equal("Ook een {name}", tr.GetContent({"foo": "bar"})); + equal("Has no name", tr.GetContent({"noname": "yes"})?.txt); + equal("Ook een xyz", tr.GetContent({"name": "xyz"})?.txt); + equal(undefined, tr.GetContent({"foo": "bar"})); - })] + })], + [ + "Select right value test", + () => { + + } + ] + + ]);