forked from MapComplete/MapComplete
		
	Various bug fixes and updates
This commit is contained in:
		
							parent
							
								
									97ec893479
								
							
						
					
					
						commit
						e069b31e4e
					
				
					 29 changed files with 482 additions and 148 deletions
				
			
		|  | @ -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; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|      */ | ||||
|  |  | |||
|  | @ -130,46 +130,4 @@ export class LayerDefinition { | |||
|         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) | ||||
|     }*/ | ||||
| } | ||||
|  | @ -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(), | ||||
|  |  | |||
|  | @ -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(), | ||||
|  |  | |||
|  | @ -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(), | ||||
|  |  | |||
|  | @ -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 | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -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 { | ||||
|      | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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")]), | ||||
|  |  | |||
|  | @ -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} | ||||
|             ] | ||||
|         }); | ||||
|     } | ||||
|  |  | |||
|  | @ -3,12 +3,13 @@ | |||
|  * 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{ | ||||
| export class NameQuestion extends TagRenderingOptions { | ||||
| 
 | ||||
|     static options =  { | ||||
|     constructor() { | ||||
|         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 <i>officiële</i> naam van dit gebied?<br><span class='question-subtext'>" + | ||||
|                 "Zelf een naam bedenken wordt afgeraden.<br/>" + | ||||
|  | @ -17,17 +18,14 @@ export class NameQuestion extends TagRenderingOptions{ | |||
|             freeform: { | ||||
|                 key: "name", | ||||
|                 template: "De naam is $$$", | ||||
|             renderTemplate: "", // We don't actually render it, only ask
 | ||||
|                 renderTemplate: "Dit gebied heet <i>{name}</i>", | ||||
|                 placeholder: "", | ||||
|             extraTags: new Tag("noname","") | ||||
|                 extraTags: new Tag("noname", "") | ||||
|             }, | ||||
|             mappings: [ | ||||
|             {k: new Tag("noname", "yes"), txt: "Dit gebied heeft geen naam"}, | ||||
|                 {k: new And([new Tag("name", ""), new Tag("noname", "yes")]), txt: "Dit gebied heeft geen naam"}, | ||||
|             ] | ||||
|     } | ||||
|      | ||||
|     constructor() { | ||||
|         super(NameQuestion.options); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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: "<span class='alert'>Uploading</span>"} | ||||
|             {k: new RegexTag("id", /node\/-.+/), txt: "<span class='alert'>Uploading</span>"} | ||||
|         ] | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -18,15 +18,14 @@ 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<any> ; | ||||
|     private currentTags: UIEventSource<any>; | ||||
| 
 | ||||
| 
 | ||||
|     private readonly _freeform: { | ||||
|  | @ -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) }) { | ||||
|     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) | ||||
|                 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<TagsFilter> { | ||||
|     private InputForFreeForm(freeform :  { | ||||
|         key: string, | ||||
|         template: string | Translation, | ||||
|         renderTemplate: string | Translation, | ||||
|         placeholder?: string | Translation, | ||||
|         extraTags?: TagsFilter, | ||||
|     }): InputElement<TagsFilter> { | ||||
|         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 | ||||
|                 } | ||||
|  |  | |||
|  | @ -222,9 +222,14 @@ export class FilteredLayer { | |||
|                        color: style.color | ||||
|                    }); | ||||
| 
 | ||||
|                } 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] | ||||
|                    if (style.icon.iconSize === undefined) { | ||||
|                        style.icon.iconSize = [50, 50] | ||||
|                    } | ||||
| 
 | ||||
|                    marker = L.marker(latLng, { | ||||
|  | @ -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); | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|  |  | |||
|  | @ -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<string, string>): boolean { | ||||
|         return this.matches(TagUtils.proprtiesToKV(properties)); | ||||
|  | @ -58,7 +60,7 @@ export class RegexTag extends TagsFilter { | |||
|         return this.invert; | ||||
|     } | ||||
| 
 | ||||
|     substituteValues(tags: any) : TagsFilter{ | ||||
|     substituteValues(tags: any): TagsFilter { | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|  | @ -68,6 +70,16 @@ export class RegexTag extends TagsFilter { | |||
|         } | ||||
|         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; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								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 = { | ||||
|  |  | |||
|  | @ -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: "<img src='./assets/bug.svg'>", | ||||
|                 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 `<img src='${icon}'>` | ||||
|                         } catch (e) { | ||||
|                             return "<img src='./assets/bug.svg'>" | ||||
|                             // Nothing to do here
 | ||||
|                         } | ||||
|                     } | ||||
|                     return "<img src='./assets/help.svg'>" | ||||
| 
 | ||||
|                 })), | ||||
|                 content: new LayerPanelWithPreview(this._config, this.languages, i, userDetails) | ||||
|             }); | ||||
|         } | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ export class GenerateEmpty { | |||
|             title: {}, | ||||
|             description: {}, | ||||
|             tagRenderings: [], | ||||
|             hideUnderlayingFeaturesMinPercentage: 0, | ||||
|             icon: { | ||||
|                 render: "./assets/bug.svg" | ||||
|             }, | ||||
|  |  | |||
|  | @ -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.<br/>" + | ||||
|                     "Now, showing those pieces of forest overlapping with the nature reserve truly clutters the map and is very user-unfriendly.<br/>" + | ||||
|                     "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"), | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,7 +13,6 @@ import {UserDetails} from "../../Logic/Osm/OsmConnection"; | |||
| 
 | ||||
| export default class LayerPanelWithPreview extends UIElement{ | ||||
|     private panel: UIElement; | ||||
|      | ||||
|     constructor(config: UIEventSource<any>, languages: UIEventSource<string[]>, index: number, userDetails: UserDetails) { | ||||
|         super(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -91,7 +91,7 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs | |||
| 
 | ||||
|             "<h3>Mappings</h3>", | ||||
|             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", "") | ||||
|  |  | |||
|  | @ -7,9 +7,13 @@ export class FixedInputElement<T> extends InputElement<T> { | |||
|     private readonly rendering: UIElement; | ||||
|     private readonly value: UIEventSource<T>; | ||||
|     public readonly IsSelected : UIEventSource<boolean> = new UIEventSource<boolean>(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<T>(value); | ||||
|         this.rendering = typeof (rendering) === 'string' ? new FixedUiElement(rendering) : rendering; | ||||
|     } | ||||
|  | @ -22,7 +26,9 @@ export class FixedInputElement<T> extends InputElement<T> { | |||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|  |  | |||
|  | @ -8,14 +8,15 @@ export class RadioButton<T> extends InputElement<T> { | |||
|     private readonly _selectedElementIndex: UIEventSource<number> | ||||
|         = new UIEventSource<number>(null); | ||||
| 
 | ||||
|     private value: UIEventSource<T>; | ||||
|     private readonly value: UIEventSource<T>; | ||||
|     private readonly _elements: InputElement<T>[] | ||||
|     private _selectFirstAsDefault: boolean; | ||||
|     private readonly _selectFirstAsDefault: boolean; | ||||
| 
 | ||||
| 
 | ||||
|     constructor(elements: InputElement<T>[], | ||||
|                 selectFirstAsDefault = true) { | ||||
|         super(undefined); | ||||
|         console.log("Created new radiobutton with values ", elements) | ||||
|         this._elements = Utils.NoNull(elements); | ||||
|         this._selectFirstAsDefault = selectFirstAsDefault; | ||||
|         const self = this; | ||||
|  |  | |||
|  | @ -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; | ||||
|                     } | ||||
|  |  | |||
|  | @ -14,6 +14,8 @@ export abstract class UIElement extends UIEventSource<string> { | |||
| 
 | ||||
|     public dumbMode = false; | ||||
|      | ||||
|     private lastInnerRender: string; | ||||
| 
 | ||||
|     /** | ||||
|      * In the 'deploy'-step, some code needs to be run by ts-node. | ||||
|      * However, ts-node crashes when it sees 'document'. When running from console, we flag this and disable all code where document is needed. | ||||
|  | @ -37,6 +39,7 @@ export abstract class UIElement extends UIEventSource<string> { | |||
|         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<string> { | |||
| 
 | ||||
|             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<string> { | |||
|    } | ||||
| 
 | ||||
|     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 `<span class='uielement ${this.clss.join(" ")}' ${style} id='${this.id}'>${this.InnerRender()}</span>` | ||||
|         return `<span class='uielement ${this.clss.join(" ")}' ${style} id='${this.id}'>${this.lastInnerRender}</span>` | ||||
|     } | ||||
| 
 | ||||
|     AttachTo(divId: string) { | ||||
|  |  | |||
|  | @ -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" | ||||
|  |  | |||
							
								
								
									
										252
									
								
								assets/layers/nature_reserve/nature_reserve.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								assets/layers/nature_reserve/nature_reserve.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <i>{name}</i>" | ||||
|         } | ||||
|       } | ||||
|     ], | ||||
|     "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": "<b>Wat is de <i>officiële</i> naam van dit natuurgebied?</b><br/><span class='subtle'>Sommige gebieden hebben geen naam</span>" | ||||
|       }, | ||||
|       "freeform": { | ||||
|         "key": "name", | ||||
|         "addExtraTags": [ | ||||
|           "noname=" | ||||
|         ] | ||||
|       }, | ||||
|       "render": { | ||||
|         "nl": "Dit gebied heet <i>{name}</i>" | ||||
|       }, | ||||
|       "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:<br/><i>{description:0}</i>" | ||||
|       }, | ||||
|       "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 | ||||
| } | ||||
|  | @ -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", | ||||
|         () => { | ||||
|      | ||||
|         } | ||||
|     ] | ||||
|      | ||||
|      | ||||
|     })] | ||||
| ]); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue