From d1f8080c24ea93e02a3d0ccc9c095043e4a57263 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 5 Jul 2020 18:59:47 +0200 Subject: [PATCH] New question system --- Customizations/AllKnownLayouts.ts | 24 ++ .../LayerDefinition.ts | 24 +- {Layers => Customizations/Layers}/Artwork.ts | 13 +- .../Layers}/Bookcases.ts | 12 +- {Layers => Customizations/Layers}/Bos.ts | 33 +- Customizations/Layers/GrbToFix.ts | 88 +++++ .../Layers}/NatureReserves.ts | 30 +- {Layers => Customizations/Layers}/Park.ts | 38 +- {Layers => Customizations/Layers}/Toilets.ts | 7 +- Customizations/Layout.ts | 53 +++ Customizations/Layouts/Bookcases.ts | 27 ++ Customizations/Layouts/GRB.ts | 21 + Customizations/Layouts/Groen.ts | 50 +++ Customizations/Layouts/Statues.ts | 26 ++ Customizations/Layouts/Toilets.ts | 24 ++ Customizations/Questions/AccessTag.ts | 39 ++ Customizations/Questions/NameInline.ts | 29 ++ Customizations/Questions/NameQuestion.ts | 30 ++ Customizations/Questions/OperatorTag.ts | 30 ++ Customizations/Questions/OsmLink.ts | 31 ++ Customizations/Questions/WikipediaLink.ts | 53 +++ Customizations/TagRendering.ts | 358 ++++++++++++++++++ Layers/CommonTagMappings.ts | 60 --- Layers/GrbToFix.ts | 58 --- Layers/KnownSet.ts | 175 --------- Layers/Playground.ts | 72 ---- Logic/Basemap.ts | 9 +- Logic/Changes.ts | 43 +-- Logic/OsmConnection.ts | 6 +- Logic/OsmObject.ts | 15 +- Logic/TagsFilter.ts | 42 +- UI/Base/TextField.ts | 21 +- UI/Base/UIInputElement.ts | 8 + UI/Base/UIRadioButton.ts | 83 ++-- UI/Base/UIRadioButtonWithOther.ts | 72 ++++ UI/FeatureInfoBox.ts | 96 +++-- UI/QuestionPicker.ts | 2 +- UI/SaveButton.ts | 25 ++ UI/SearchAndGo.ts | 4 +- UI/TagMapping.ts | 80 ---- index.css | 98 ++++- index.ts | 26 +- package-lock.json | 5 - package.json | 1 - test.ts | 39 +- 45 files changed, 1391 insertions(+), 689 deletions(-) create mode 100644 Customizations/AllKnownLayouts.ts rename LayerDefinition.ts => Customizations/LayerDefinition.ts (57%) rename {Layers => Customizations/Layers}/Artwork.ts (88%) rename {Layers => Customizations/Layers}/Bookcases.ts (90%) rename {Layers => Customizations/Layers}/Bos.ts (67%) create mode 100644 Customizations/Layers/GrbToFix.ts rename {Layers => Customizations/Layers}/NatureReserves.ts (67%) rename {Layers => Customizations/Layers}/Park.ts (56%) rename {Layers => Customizations/Layers}/Toilets.ts (93%) create mode 100644 Customizations/Layout.ts create mode 100644 Customizations/Layouts/Bookcases.ts create mode 100644 Customizations/Layouts/GRB.ts create mode 100644 Customizations/Layouts/Groen.ts create mode 100644 Customizations/Layouts/Statues.ts create mode 100644 Customizations/Layouts/Toilets.ts create mode 100644 Customizations/Questions/AccessTag.ts create mode 100644 Customizations/Questions/NameInline.ts create mode 100644 Customizations/Questions/NameQuestion.ts create mode 100644 Customizations/Questions/OperatorTag.ts create mode 100644 Customizations/Questions/OsmLink.ts create mode 100644 Customizations/Questions/WikipediaLink.ts create mode 100644 Customizations/TagRendering.ts delete mode 100644 Layers/CommonTagMappings.ts delete mode 100644 Layers/GrbToFix.ts delete mode 100644 Layers/KnownSet.ts delete mode 100644 Layers/Playground.ts create mode 100644 UI/Base/UIInputElement.ts create mode 100644 UI/Base/UIRadioButtonWithOther.ts create mode 100644 UI/SaveButton.ts delete mode 100644 UI/TagMapping.ts diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts new file mode 100644 index 0000000..9697fd1 --- /dev/null +++ b/Customizations/AllKnownLayouts.ts @@ -0,0 +1,24 @@ +import {Groen} from "./Layouts/Groen"; +import {Toilets} from "./Layouts/Toilets"; +import {GRB} from "./Layouts/GRB"; +import {Statues} from "./Layouts/Statues"; +import {Bookcases} from "./Layouts/Bookcases"; + +export class AllKnownLayouts { + public static allSets: any = AllKnownLayouts.AllLayouts(); + + private static AllLayouts() { + const layouts = [ + new Groen(), + new GRB(), + /*new Toilets(), + new Statues(), + new Bookcases()*/ + ]; + const allSets = {}; + for (const layout of layouts) { + allSets[layout.name] = layout; + } + return allSets; + } +} diff --git a/LayerDefinition.ts b/Customizations/LayerDefinition.ts similarity index 57% rename from LayerDefinition.ts rename to Customizations/LayerDefinition.ts index acb76c9..bd64aa4 100644 --- a/LayerDefinition.ts +++ b/Customizations/LayerDefinition.ts @@ -1,14 +1,12 @@ -import {Basemap} from "./Logic/Basemap"; -import {ElementStorage} from "./Logic/ElementStorage"; -import {Changes} from "./Logic/Changes"; -import {QuestionDefinition} from "./Logic/Question"; -import {TagMappingOptions} from "./UI/TagMapping"; -import {UIEventSource} from "./UI/UIEventSource"; -import {UIElement} from "./UI/UIElement"; -import {Tag, TagsFilter} from "./Logic/TagsFilter"; -import {FilteredLayer} from "./Logic/FilteredLayer"; -import {UserDetails} from "./Logic/OsmConnection"; - +import {Tag, TagsFilter} from "../Logic/TagsFilter"; +import {UIElement} from "../UI/UIElement"; +import {Basemap} from "../Logic/Basemap"; +import {ElementStorage} from "../Logic/ElementStorage"; +import {UIEventSource} from "../UI/UIEventSource"; +import {FilteredLayer} from "../Logic/FilteredLayer"; +import {Changes} from "../Logic/Changes"; +import {UserDetails} from "../Logic/OsmConnection"; +import {TagRenderingOptions} from "./TagRendering"; export class LayerDefinition { @@ -19,8 +17,8 @@ export class LayerDefinition { minzoom: number; overpassFilter: TagsFilter; - elementsToShow: (TagMappingOptions | QuestionDefinition | UIElement)[]; - questions: QuestionDefinition[]; // Questions are shown below elementsToShow in a questionPicker + title: TagRenderingOptions; + elementsToShow: TagRenderingOptions[]; style: (tags: any) => { color: string, icon: any }; diff --git a/Layers/Artwork.ts b/Customizations/Layers/Artwork.ts similarity index 88% rename from Layers/Artwork.ts rename to Customizations/Layers/Artwork.ts index 80897af..eb65dc5 100644 --- a/Layers/Artwork.ts +++ b/Customizations/Layers/Artwork.ts @@ -1,10 +1,7 @@ import {LayerDefinition} from "../LayerDefinition"; -import {FixedUiElement} from "../UI/Base/FixedUiElement"; +import {QuestionDefinition} from "../../Logic/Question"; +import {Tag} from "../../Logic/TagsFilter"; import L from "leaflet"; -import {CommonTagMappings} from "./CommonTagMappings"; -import {TagMappingOptions} from "../UI/TagMapping"; -import {QuestionDefinition} from "../Logic/Question"; -import {Tag} from "../Logic/TagsFilter"; export class Artwork extends LayerDefinition { @@ -40,7 +37,8 @@ export class Artwork extends LayerDefinition { iconUrl: "assets/statue.svg", iconSize: [40, 40], text: "hi" - }) + }), + color: "#0000ff" }; } @@ -80,8 +78,7 @@ export class Artwork extends LayerDefinition { - new TagMappingOptions({key: "image", template: "image"}), - CommonTagMappings.osmLink + new TagMappingOptions({key: "image", template: "image"}) ]; } diff --git a/Layers/Bookcases.ts b/Customizations/Layers/Bookcases.ts similarity index 90% rename from Layers/Bookcases.ts rename to Customizations/Layers/Bookcases.ts index 6723064..81015f1 100644 --- a/Layers/Bookcases.ts +++ b/Customizations/Layers/Bookcases.ts @@ -1,8 +1,8 @@ import {LayerDefinition} from "../LayerDefinition"; -import {TagMappingOptions} from "../UI/TagMapping"; import L from "leaflet"; -import {QuestionDefinition} from "../Logic/Question"; -import {Tag} from "../Logic/TagsFilter"; +import {Tag} from "../../Logic/TagsFilter"; +import {QuestionDefinition} from "../../Logic/Question"; +import {TagRenderingOptions} from "../TagRendering"; export class Bookcases extends LayerDefinition { @@ -31,13 +31,15 @@ export class Bookcases extends LayerDefinition { icon: new L.icon({ iconUrl: "assets/bookcase.svg", iconSize: [40, 40] - }) + }), + color: "#0000ff" }; } this.elementsToShow = [ - + + new TagMappingOptions({ key: "name", template: "{name}", diff --git a/Layers/Bos.ts b/Customizations/Layers/Bos.ts similarity index 67% rename from Layers/Bos.ts rename to Customizations/Layers/Bos.ts index 1398a94..51520ff 100644 --- a/Layers/Bos.ts +++ b/Customizations/Layers/Bos.ts @@ -1,9 +1,11 @@ import {LayerDefinition} from "../LayerDefinition"; -import {Quests} from "../Quests"; -import {TagMappingOptions} from "../UI/TagMapping"; -import L from "leaflet" -import {CommonTagMappings} from "./CommonTagMappings"; -import {Or, Tag} from "../Logic/TagsFilter"; +import {Quests} from "../../Quests"; +import {And, Or, Tag} from "../../Logic/TagsFilter"; +import {AccessTag} from "../Questions/AccessTag"; +import {OperatorTag} from "../Questions/OperatorTag"; +import {TagRenderingOptions} from "../TagRendering"; +import {NameQuestion} from "../Questions/NameQuestion"; +import {NameInline} from "../Questions/NameInline"; export class Bos extends LayerDefinition { @@ -27,17 +29,12 @@ export class Bos extends LayerDefinition { this.maxAllowedOverlapPercentage = 10; this.minzoom = 13; - this.questions = [Quests.nameOf(this.name), Quests.accessNatureReserve, Quests.operator]; this.style = this.generateStyleFunction(); + this.title = new NameInline("bos"); this.elementsToShow = [ - new TagMappingOptions({ - key: "name", - template: "{name}", - missing: "Naamloos bos" - }), - - CommonTagMappings.access, - CommonTagMappings.operator, + new NameQuestion(), + new AccessTag(), + new OperatorTag() ]; } @@ -47,9 +44,9 @@ export class Bos extends LayerDefinition { const self = this; return function (properties: any) { let questionSeverity = 0; - for (const qd of self.questions) { - if (qd.isApplicable(properties)) { - questionSeverity = Math.max(questionSeverity, qd.severity); + for (const qd of self.elementsToShow) { + if (qd.IsQuestioning(properties)) { + questionSeverity = Math.max(questionSeverity, qd.options.priority ?? 0); } } @@ -63,7 +60,7 @@ export class Bos extends LayerDefinition { let colour = colormapping[questionSeverity]; while (colour == undefined) { questionSeverity--; - colormapping[questionSeverity]; + colour = colormapping[questionSeverity]; } return { diff --git a/Customizations/Layers/GrbToFix.ts b/Customizations/Layers/GrbToFix.ts new file mode 100644 index 0000000..a7e395a --- /dev/null +++ b/Customizations/Layers/GrbToFix.ts @@ -0,0 +1,88 @@ +import {LayerDefinition} from "../LayerDefinition"; +import L from "leaflet" +import {And, Regex, Tag} from "../../Logic/TagsFilter"; +import {TagRenderingOptions} from "../TagRendering"; + +export class GrbToFix extends LayerDefinition { + + constructor() { + super(); + + this.name = "grb"; + this.newElementTags = undefined; + this.icon = "./assets/star.svg"; + this.overpassFilter = new Regex("fixme", "GRB"); + this.minzoom = 13; + + + + this.style = function (tags) { + return { + icon: new L.icon({ + iconUrl: "assets/star.svg", + iconSize: [40, 40], + text: "hi" + }), + color: "#ff0000" + }; + + } + + this.title = new TagRenderingOptions({ + freeform: { + key: "fixme", + renderTemplate: "{fixme}", + template: "Fixme $$$" + } + }) + + this.elementsToShow = [ + + new TagRenderingOptions( + { + freeform: { + key: "addr:street", + renderTemplate: "Het adres is {addr:street} {addr:housenumber}", + template: "Straat? $$$" + } + } + ), + + new TagRenderingOptions({ + + question: "Wat is het huisnummer?", + tagsPreprocessor: tags => { + const newTags = {}; + newTags["addr:housenumber"] = tags["addr:housenumber"] + newTags["addr:street"] = tags["addr:street"] + + const telltale = "GRB thinks that this has number "; + const index = tags.fixme.indexOf(telltale); + if (index >= 0) { + const housenumber = tags.fixme.slice(index + telltale.length); + newTags["grb:housenumber:human"] = housenumber; + newTags["grb:housenumber"] = housenumber == "no number" ? "" : housenumber; + } + + return newTags; + }, + mappings: [ + { + k: new And([new Tag("addr:housenumber", "{grb:housenumber}"), new Tag("fixme", "")]), + txt: "Volg GRB: {grb:housenumber:human}", + substitute: true + }, + { + k: new And([new Tag("addr:housenumber", "{addr:housenumber}"), new Tag("fixme", "")]), + txt: "Volg OSM: {addr:housenumber}", + substitute: true + } + ] + }) + + + ]; + } + + +} \ No newline at end of file diff --git a/Layers/NatureReserves.ts b/Customizations/Layers/NatureReserves.ts similarity index 67% rename from Layers/NatureReserves.ts rename to Customizations/Layers/NatureReserves.ts index ae6a5f9..c741766 100644 --- a/Layers/NatureReserves.ts +++ b/Customizations/Layers/NatureReserves.ts @@ -1,8 +1,10 @@ import {LayerDefinition} from "../LayerDefinition"; -import {Quests} from "../Quests"; -import {TagMappingOptions} from "../UI/TagMapping"; -import {CommonTagMappings} from "./CommonTagMappings"; -import {Or, Tag} from "../Logic/TagsFilter"; +import {Or, Tag} from "../../Logic/TagsFilter"; +import {TagRenderingOptions} from "../TagRendering"; +import {AccessTag} from "../Questions/AccessTag"; +import {OperatorTag} from "../Questions/OperatorTag"; +import {NameQuestion} from "../Questions/NameQuestion"; +import {NameInline} from "../Questions/NameInline"; export class NatureReserves extends LayerDefinition { @@ -17,16 +19,12 @@ export class NatureReserves extends LayerDefinition { this.newElementTags = [new Tag("leisure", "nature_reserve"), new Tag("fixme", "Toegevoegd met MapComplete, geometry nog uit te tekenen")] this.minzoom = 13; - this.questions = [Quests.nameOf(this.name), Quests.accessNatureReserve, Quests.operator]; + this.title = new NameInline("natuurreservaat"); this.style = this.generateStyleFunction(); this.elementsToShow = [ - new TagMappingOptions({ - key: "name", - template: "{name}", - missing: "Naamloos gebied" - }), - CommonTagMappings.access, - CommonTagMappings.operator, + new NameQuestion(), + new AccessTag(), + new OperatorTag(), ]; } @@ -35,9 +33,9 @@ export class NatureReserves extends LayerDefinition { const self = this; return function (properties: any) { let questionSeverity = 0; - for (const qd of self.questions) { - if (qd.isApplicable(properties)) { - questionSeverity = Math.max(questionSeverity, qd.severity); + for (const qd of self.elementsToShow) { + if (qd.IsQuestioning(properties)) { + questionSeverity = Math.max(questionSeverity, qd.options.priority ?? 0); } } @@ -51,7 +49,7 @@ export class NatureReserves extends LayerDefinition { let colour = colormapping[questionSeverity]; while (colour == undefined) { questionSeverity--; - colormapping[questionSeverity]; + colour = colormapping[questionSeverity]; } return { diff --git a/Layers/Park.ts b/Customizations/Layers/Park.ts similarity index 56% rename from Layers/Park.ts rename to Customizations/Layers/Park.ts index cc74a61..3736435 100644 --- a/Layers/Park.ts +++ b/Customizations/Layers/Park.ts @@ -1,8 +1,11 @@ import {LayerDefinition} from "../LayerDefinition"; -import {Quests} from "../Quests"; -import {TagMappingOptions} from "../UI/TagMapping"; -import {CommonTagMappings} from "./CommonTagMappings"; -import {Or, Tag} from "../Logic/TagsFilter"; +import {Quests} from "../../Quests"; +import {And, Or, Tag} from "../../Logic/TagsFilter"; +import {AccessTag} from "../Questions/AccessTag"; +import {OperatorTag} from "../Questions/OperatorTag"; +import {TagRenderingOptions} from "../TagRendering"; +import {NameQuestion} from "../Questions/NameQuestion"; +import {NameInline} from "../Questions/NameInline"; export class Park extends LayerDefinition { @@ -10,25 +13,16 @@ export class Park extends LayerDefinition { super(); this.name = "park"; this.icon = "./assets/tree_white_background.svg"; - this.overpassFilter = - new Or([new Tag("leisure","park"), new Tag("landuse","village_green")]); - this.newElementTags = [new Tag("leisure", "park"), + this.overpassFilter = + new Or([new Tag("leisure", "park"), new Tag("landuse", "village_green")]); + this.newElementTags = [new Tag("leisure", "park"), new Tag("fixme", "Toegevoegd met MapComplete, geometry nog uit te tekenen")]; this.maxAllowedOverlapPercentage = 25; this.minzoom = 13; - this.questions = [Quests.nameOf("park")]; this.style = this.generateStyleFunction(); - this.elementsToShow = [ - new TagMappingOptions({ - key: "name", - template: "{name}", - missing: "Naamloos park" - }), - - CommonTagMappings.access, - CommonTagMappings.operator, - ]; + this.title = new NameInline("park"); + this.elementsToShow = [new NameQuestion()]; } @@ -39,9 +33,9 @@ export class Park extends LayerDefinition { const self = this; return function (properties: any) { let questionSeverity = 0; - for (const qd of self.questions) { - if (qd.isApplicable(properties)) { - questionSeverity = Math.max(questionSeverity, qd.severity); + for (const qd of self.elementsToShow) { + if (qd.IsQuestioning(properties)) { + questionSeverity = Math.max(questionSeverity, qd.options.priority ?? 0); } } @@ -55,7 +49,7 @@ export class Park extends LayerDefinition { let colour = colormapping[questionSeverity]; while (colour == undefined) { questionSeverity--; - colormapping[questionSeverity]; + colour = colormapping[questionSeverity]; } return { diff --git a/Layers/Toilets.ts b/Customizations/Layers/Toilets.ts similarity index 93% rename from Layers/Toilets.ts rename to Customizations/Layers/Toilets.ts index 6e77686..2c0b69b 100644 --- a/Layers/Toilets.ts +++ b/Customizations/Layers/Toilets.ts @@ -1,9 +1,8 @@ import {LayerDefinition} from "../LayerDefinition"; -import {Quests} from "../Quests"; -import {FixedUiElement} from "../UI/Base/FixedUiElement"; -import {TagMappingOptions} from "../UI/TagMapping"; +import {Quests} from "../../Quests"; +import {FixedUiElement} from "../../UI/Base/FixedUiElement"; import L from "leaflet"; -import {Tag} from "../Logic/TagsFilter"; +import {Tag} from "../../Logic/TagsFilter"; export class Toilets extends LayerDefinition{ diff --git a/Customizations/Layout.ts b/Customizations/Layout.ts new file mode 100644 index 0000000..9ff0e4c --- /dev/null +++ b/Customizations/Layout.ts @@ -0,0 +1,53 @@ +import {LayerDefinition} from "./LayerDefinition"; + +/** + * A layout is a collection of settings of the global view (thus: welcome text, title, selection of layers). + */ +export class Layout { + public name: string; + public title: string; + public layers: LayerDefinition[]; + public welcomeMessage: string; + public gettingStartedPlzLogin: string; + public welcomeBackMessage: string; + + public startzoom: number; + public startLon: number; + public startLat: number; + public welcomeTail: string; + + + constructor( + name: string, + title: string, + layers: LayerDefinition[], + startzoom: number, + startLat: number, + startLon: number, + welcomeMessage: string, + gettingStartedPlzLogin: string, + welcomeBackMessage: string, + welcomeTail: string = "" + ) { + this.title = title; + this.startLon = startLon; + this.startLat = startLat; + this.startzoom = startzoom; + this.name = name; + this.layers = layers; + this.welcomeMessage = welcomeMessage; + this.gettingStartedPlzLogin = gettingStartedPlzLogin; + this.welcomeBackMessage = welcomeBackMessage; + this.welcomeTail = welcomeTail; + } + +/* + + + static statues = new Layout( + + ); + +*/ +} + diff --git a/Customizations/Layouts/Bookcases.ts b/Customizations/Layouts/Bookcases.ts new file mode 100644 index 0000000..7d02aab --- /dev/null +++ b/Customizations/Layouts/Bookcases.ts @@ -0,0 +1,27 @@ +import {Layout} from "../Layout"; +import * as Layer from "../Layers/Bookcases"; + +export class Bookcases extends Layout{ + constructor() { + super( "bookcases", + "Open Bookcase Map", + [new Layer.Bookcases()], + 14, + 51.2, + 3.2, + + + "

Open BoekenkastjesKaart

\n" + + "\n" + + "

" + + "Help mee met het creëeren van een volledige kaart met alle boekenruilkastjes!" + + "Een boekenruilkastje is een vaste plaats in publieke ruimte waar iedereen een boek in kan zetten of uit kan meenemen." + + "Meestal een klein kastje of doosje dat op straat staat, maar ook een oude telefooncellen of een schap in een station valt hieronder."+ + "

" + , + "

Begin met het aanmaken van een account\n" + + " of door je " + + " aan te melden.

", + "Klik op een boekenruilkastje om vragen te beantwoorden"); + } +} \ No newline at end of file diff --git a/Customizations/Layouts/GRB.ts b/Customizations/Layouts/GRB.ts new file mode 100644 index 0000000..93514a1 --- /dev/null +++ b/Customizations/Layouts/GRB.ts @@ -0,0 +1,21 @@ +import {Layout} from "../Layout"; +import {GrbToFix} from "../Layers/GrbToFix"; + +export class GRB extends Layout { + constructor() { + super("grb", + "Grb import fix tool", + [new GrbToFix()], + 15, + 51.2083, + 3.2279, + + + "

GRB Fix tool

\n" + + "\n" + + "Expert use only" + + , + "", ""); + } +} \ No newline at end of file diff --git a/Customizations/Layouts/Groen.ts b/Customizations/Layouts/Groen.ts new file mode 100644 index 0000000..2432869 --- /dev/null +++ b/Customizations/Layouts/Groen.ts @@ -0,0 +1,50 @@ +import {NatureReserves} from "../Layers/NatureReserves"; +import {Park} from "../Layers/Park"; +import {Bos} from "../Layers/Bos"; +import {Layout} from "../Layout"; + +export class Groen extends Layout { + + constructor() { + super("groen", + "Buurtnatuur", + [new NatureReserves(), new Park(), new Bos()], + 10, + 50.8435, + 4.3688, + "\n" + + "
" + + "

Breng jouw buurtnatuur in kaart

" + + "Natuur maakt gelukkig. Aan de hand van deze website willen we de natuur dicht bij ons beter inventariseren. Met als doel meer mensen te laten genieten van toegankelijke natuur én te strijden voor meer natuur in onze buurten. \n" + + "" + + "

Samen kleuren we heel Vlaanderen en Brussel groen.

" + + "

Blijf op de hoogte van de resultaten van buurtnatuur.be: meld je aan voor e-mailupdates.

\n" + , + + "Begin meteen door een account te maken\n" + + " te maken of\n" + + " in te loggen.", + "", + + "

Tips

" + + + "" + + "" + + "

" + + "De oorspronkelijke data komt van OpenStreetMap en je antwoorden worden daar bewaard.
Omdat iedereen vrij kan meewerken aan dit project, kunnen we niet garanderen dat er geen fouten opduiken." + + "

" + + "Je privacy is belangrijk. We tellen wel hoeveel gebruikers deze website bezoeken. We plaatsen een cookie waar geen persoonlijke informatie in bewaard wordt. " + + "Als je inlogt, komt er een tweede cookie bij met je inloggegevens." + + "
" + ); + } +} \ No newline at end of file diff --git a/Customizations/Layouts/Statues.ts b/Customizations/Layouts/Statues.ts new file mode 100644 index 0000000..76a46dd --- /dev/null +++ b/Customizations/Layouts/Statues.ts @@ -0,0 +1,26 @@ +import {Layout} from "../Layout"; +import {Artwork} from "../Layers/Artwork"; + +export class Statues extends Layout{ + constructor() { + super( "statues", + "Open Artwork Map", + [new Artwork()], + 10, + 50.8435, + 4.3688, + + + "

Open Statue Map

\n" + + "\n" + + "

" + + "Help with creating a map of all statues all over the world!" + + , + "

Start by creating an account\n" + + " or by " + + " logging in.

", + "Start by clicking a pin and answering the questions"); + } + +} \ No newline at end of file diff --git a/Customizations/Layouts/Toilets.ts b/Customizations/Layouts/Toilets.ts new file mode 100644 index 0000000..c6c585e --- /dev/null +++ b/Customizations/Layouts/Toilets.ts @@ -0,0 +1,24 @@ +import {Layout} from "../Layout"; +import * as Layer from "../Layers/Toilets"; + +export class Toilets extends Layout{ + constructor() { + super( "toilets", + "Open Toilet Map", + [new Layer.Toilets()], + 12, + 51.2, + 3.2, + + + "

Open Toilet Map

\n" + + "\n" + + "

Help us to create the most complete map about all the toilets in the world, based on openStreetMap." + + "One can answer questions here, which help users all over the world to find an accessible toilet, close to them.

" + , + "

Start by creating an account\n" + + " or by " + + " logging in.

", + "Start by clicking a pin and answering the questions"); + } +} \ No newline at end of file diff --git a/Customizations/Questions/AccessTag.ts b/Customizations/Questions/AccessTag.ts new file mode 100644 index 0000000..8e67a12 --- /dev/null +++ b/Customizations/Questions/AccessTag.ts @@ -0,0 +1,39 @@ +import {TagRendering, TagRenderingOptions} from "../TagRendering"; +import {UIEventSource} from "../../UI/UIEventSource"; +import {Changes} from "../../Logic/Changes"; +import {And, Tag} from "../../Logic/TagsFilter"; + +export class AccessTag extends TagRenderingOptions { + + private static options = { + priority: 10, + question: "Is dit gebied toegankelijk?", + primer: "Dit gebied is ", + freeform: { + key: "access", + extraTags: new Tag("fixme", "Freeform access tag used: possibly a wrong value"), + template: "Iets anders: $$$", + renderTemplate: "De toegangekelijkheid van dit gebied is: {access}", + placeholder: "Specifieer" + }, + mappings: [ + {k: new And([new Tag("access", "yes"), new Tag("fee", "")]), txt: "publiek toegankelijk"}, + {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", "yes"), + new Tag("fee", "yes")]), + txt: "toegankelijk mits betaling", + priority: 10 + }, + ] + } + + constructor() { + super(AccessTag.options); + } + + +} \ No newline at end of file diff --git a/Customizations/Questions/NameInline.ts b/Customizations/Questions/NameInline.ts new file mode 100644 index 0000000..83458a5 --- /dev/null +++ b/Customizations/Questions/NameInline.ts @@ -0,0 +1,29 @@ +import {TagRenderingOptions} from "../TagRendering"; +import {And, Tag} from "../../Logic/TagsFilter"; + + +export class NameInline extends TagRenderingOptions{ + + static Upper(string){ + return string.charAt(0).toUpperCase() + string.slice(1); + } + + constructor(category: string) { + super({ + question: "", + + freeform: { + renderTemplate: "{name}", + template: "De naam van dit "+category+" is $$$", + key: "name", + extraTags: new Tag("noname", "") // Remove 'noname=yes' + }, + + mappings: [ + {k: new Tag("noname","yes"), txt: NameInline.Upper(category)+" zonder naam"}, + {k: null, txt: NameInline.Upper(category)} + ] + }); + } + +} \ No newline at end of file diff --git a/Customizations/Questions/NameQuestion.ts b/Customizations/Questions/NameQuestion.ts new file mode 100644 index 0000000..270a00e --- /dev/null +++ b/Customizations/Questions/NameQuestion.ts @@ -0,0 +1,30 @@ +/** + * There are two ways to ask for names: + * One is a big 'name-question', the other is the 'edit name' in the title. + * THis one is the big question + */ +import {TagRenderingOptions} from "../TagRendering"; +import {Tag} from "../../Logic/TagsFilter"; + +export class NameQuestion extends TagRenderingOptions{ + + static options = { + priority: 20, + question: "Wat is de officiële naam van dit gebied?", + 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"}, + ] + } + + constructor() { + super(NameQuestion.options); + } + +} \ No newline at end of file diff --git a/Customizations/Questions/OperatorTag.ts b/Customizations/Questions/OperatorTag.ts new file mode 100644 index 0000000..5cc61a3 --- /dev/null +++ b/Customizations/Questions/OperatorTag.ts @@ -0,0 +1,30 @@ +import {TagRenderingOptions} from "../TagRendering"; +import {UIEventSource} from "../../UI/UIEventSource"; +import {Changes} from "../../Logic/Changes"; +import {Tag} from "../../Logic/TagsFilter"; + + +export class OperatorTag extends TagRenderingOptions { + + + private static options = { + priority: 5, + question: "Wie beheert dit gebied?", + freeform: { + key: "operator", + template: "Dit gebied wordt beheerd door $$$", + renderTemplate: "Dit gebied wordt beheerd door {operator}", + placeholder: "organisatie" + }, + mappings: [ + {k: new Tag("operator", "Natuurpunt"), txt: "Natuurpunt"}, + {k: new Tag("operator", "Agentschap Natuur en Bos"), txt: "het Agentschap Natuur en Bos (ANB)"}, + {k: new Tag("operator", "private"), txt: "Beheer door een privépersoon"} + ] + } + + constructor() { + super(OperatorTag.options); + } + +} \ No newline at end of file diff --git a/Customizations/Questions/OsmLink.ts b/Customizations/Questions/OsmLink.ts new file mode 100644 index 0000000..830ea40 --- /dev/null +++ b/Customizations/Questions/OsmLink.ts @@ -0,0 +1,31 @@ +import {TagRenderingOptions} from "../TagRendering"; +import {Img} from "../../UI/Img"; +import {Tag} from "../../Logic/TagsFilter"; + + +export class OsmLink extends TagRenderingOptions { + + + + static options = { + freeform: { + key: "id", + template: "$$$", + renderTemplate: + "" + + Img.osmAbstractLogo + + "", + placeholder: "", + }, + mappings: [ + {k: new Tag("id", "node/-1"), txt: "Uploading"} + ] + + } + + constructor() { + super(OsmLink.options); + } + + +} \ No newline at end of file diff --git a/Customizations/Questions/WikipediaLink.ts b/Customizations/Questions/WikipediaLink.ts new file mode 100644 index 0000000..03be61e --- /dev/null +++ b/Customizations/Questions/WikipediaLink.ts @@ -0,0 +1,53 @@ +import {TagRenderingOptions} from "../TagRendering"; + + +export class WikipediaLink extends TagRenderingOptions { + + private static FixLink(value: string): string { + // @ts-ignore + if (value.startsWith("https")) { + return value; + } else { + + const splitted = value.split(":"); + const language = splitted[0]; + splitted.shift(); + const page = splitted.join(":"); + return 'https://' + language + '.wikipedia.org/wiki/' + page; + } + } + + static options = { + priority: 10, + // question: "Wat is het overeenstemmende wkipedia-artikel?", + freeform: { + key: "wikipedia", + template: "$$$", + renderTemplate: + "" + + "" + + "wikipedia" + + "", + placeholder: "", + tagsPreprocessor: (tags) => { + + const newTags = {}; + for (const k in tags) { + if (k === "wikipedia") { + newTags["wikipedia"] = WikipediaLink.FixLink(tags[k]); + } else { + newTags[k] = tags[k]; + } + } + return newTags; + } + }, + + } + + constructor() { + super(WikipediaLink.options); + } + + +} \ No newline at end of file diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts new file mode 100644 index 0000000..2158baa --- /dev/null +++ b/Customizations/TagRendering.ts @@ -0,0 +1,358 @@ +import {UIElement} from "../UI/UIElement"; +import {UIEventSource} from "../UI/UIEventSource"; +import {And, Tag, TagsFilter, TagUtils} from "../Logic/TagsFilter"; +import {UIRadioButton} from "../UI/Base/UIRadioButton"; +import {FixedUiElement} from "../UI/Base/FixedUiElement"; +import {SaveButton} from "../UI/SaveButton"; +import {Changes} from "../Logic/Changes"; +import {TextField} from "../UI/Base/TextField"; +import {UIInputElement} from "../UI/Base/UIInputElement"; +import {UIRadioButtonWithOther} from "../UI/Base/UIRadioButtonWithOther"; +import {VariableUiElement} from "../UI/Base/VariableUIElement"; + +export class TagRenderingOptions { + + /** + * Notes: by not giving a 'question', one disables the question form alltogether + */ + + public options: { + priority?: number; question?: string; primer?: string; + freeform?: { key: string; tagsPreprocessor?: (tags: any) => any; template: string; renderTemplate: string; placeholder?: string; extraTags?: TagsFilter }; mappings?: { k: TagsFilter; txt: string; priority?: number, substitute?: boolean }[] + }; + + + constructor(options: { + priority?: number + + question?: string, + primer?: string, + tagsPreprocessor?: ((tags: any) => any), + freeform?: { + key: string, template: string, + renderTemplate: string + placeholder?: string, + extraTags?: TagsFilter, + }, + mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[] + }) { + this.options = options; + + } + + + IsQuestioning(tags: any): boolean { + const tagsKV = TagUtils.proprtiesToKV(tags); + + for (const oneOnOneElement of this.options.mappings) { + if (oneOnOneElement.k.matches(tagsKV)) { + return false; + } + } + if (this.options.freeform !== undefined && tags[this.options.freeform.key] !== undefined) { + return false; + } + if (this.options.question === undefined) { + return false; + } + + return true; + } + + +} + +export class TagRendering extends UIElement { + + + public elementPriority: number; + + + private _question: string; + private _primer: string; + private _mapping: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[]; + private _tagsPreprocessor?: ((tags: any) => any); + private _freeform: { + key: string, template: string, + renderTemplate: string, + + placeholder?: string, + extraTags?: TagsFilter + }; + + private readonly _questionElement: UIElement; + private readonly _textField: TextField; // Only here to update + + private readonly _saveButton: UIElement; + private readonly _skipButton: UIElement; + private readonly _editButton: UIElement; + + private readonly _questionSkipped: UIEventSource = new UIEventSource(false); + + private readonly _editMode: UIEventSource = new UIEventSource(false); + + + constructor(tags: UIEventSource, changes: Changes, options: { + priority?: number + + question?: string, + primer?: string, + + freeform?: { + key: string, template: string, + renderTemplate: string + placeholder?: string, + extraTags?: TagsFilter, + }, + tagsPreprocessor?: ((tags: any) => any), + mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[] + }) { + super(tags); + const self = this; + this.ListenTo(this._questionSkipped); + this.ListenTo(this._editMode); + + this._question = options.question; + this._primer = options.primer ?? ""; + this._tagsPreprocessor = options.tagsPreprocessor; + this._mapping = []; + this._freeform = options.freeform; + this.elementPriority = options.priority ?? 0; + + // Prepare the choices for the Radio buttons + let i = 0; + const choices: UIElement[] = []; + + for (const choice of options.mappings ?? []) { + if (choice.k === null) { + this._mapping.push(choice); + continue; + } + let choiceSubbed = choice; + if (choice.substitute) { + choiceSubbed = { + k : choice.k.substituteValues( + options.tagsPreprocessor(this._source.data)), + txt : this.ApplyTemplate(choice.txt), + substitute: false, + priority: choice.priority + } + } + + + choices.push(new FixedUiElement(choiceSubbed.txt)); + this._mapping.push(choiceSubbed); + i++; + } + + // Map radiobutton choice and textfield answer onto tagfilter. That tagfilter will be pushed into the changes later on + const pickChoice = (i => { + if (i === undefined || i === null) { + return undefined + } + return self._mapping[i].k + }); + const pickString = + (string) => { + if (string === "" || string === undefined) { + return undefined; + } + const tag = new Tag(self._freeform.key, string); + if (self._freeform.extraTags === undefined) { + return tag; + } + return new And([ + self._freeform.extraTags, + tag + ] + ); + }; + + + // Prepare the actual input element -> pick an appropriate implementation + let inputElement: UIInputElement; + + if (this._freeform !== undefined && this._mapping !== undefined) { + // Radio buttons with 'other' + inputElement = new UIRadioButtonWithOther( + choices, + this._freeform.template, + this._freeform.placeholder, + pickChoice, + pickString + ); + this._questionElement = inputElement; + } else if (this._mapping !== undefined) { + // This is a classic radio selection element + inputElement = new UIRadioButton(new UIEventSource(choices), pickChoice) + this._questionElement = inputElement; + } else if (this._freeform !== undefined) { + this._textField = new TextField(new UIEventSource(this._freeform.placeholder), pickString); + inputElement = this._textField; + this._questionElement = new FixedUiElement(this._freeform.template.replace("$$$", inputElement.Render())) + } else { + throw "Invalid questionRendering, expected at least choices or a freeform" + } + + + const save = () => { + const selection = inputElement.GetValue().data; + if (selection) { + changes.addTag(tags.data.id, selection); + } + self._editMode.setData(false); + } + + const cancel = () => { + self._questionSkipped.setData(true); + self._editMode.setData(false); + } + + // Setup the save button and it's action + this._saveButton = new SaveButton(inputElement.GetValue()) + .onClick(save); + + if (this._question !== undefined) { + this._editButton = new FixedUiElement("edit") + .onClick(() => { + console.log("Click", self._editButton); + if (self._textField) { + self._textField.value.setData(self._source.data["name"] ?? ""); + } + + self._editMode.setData(true); + }); + } else { + this._editButton = new FixedUiElement(""); + } + + + const cancelContents = this._editMode.map((isEditing) => { + if (isEditing) { + return "Annuleren"; + } else { + return "Ik weet het niet zeker..."; + } + }); + // And at last, set up the skip button + this._skipButton = new VariableUiElement(cancelContents).onClick(cancel); + + + } + + private ApplyTemplate(template: string): string { + let tags = this._source.data; + if (this._tagsPreprocessor !== undefined) { + tags = this._tagsPreprocessor(tags); + } + + + return TagUtils.ApplyTemplate(template, tags); + } + + IsKnown(): boolean { + const tags = TagUtils.proprtiesToKV(this._source.data); + + for (const oneOnOneElement of this._mapping) { + if (oneOnOneElement.k === null || oneOnOneElement.k.matches(tags)) { + return true; + } + } + return this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined; + } + + IsQuestioning(): boolean { + if (this.IsKnown()) { + return false; + } + if (this._question === undefined) { + // We don't ask this question in the first place + return false; + } + if (this._questionSkipped.data) { + // We don't ask for this question anymore, skipped by user + return false; + } + return true; + } + + private RenderAnwser(): string { + const tags = TagUtils.proprtiesToKV(this._source.data); + + let freeform = ""; + let freeformScore = -10; + if (this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined) { + freeform = this.ApplyTemplate(this._freeform.renderTemplate); + freeformScore = 0; + } + + if (this._mapping !== undefined) { + + let highestScore = -100; + let highestTemplate = undefined; + for (const oneOnOneElement of this._mapping) { + if (oneOnOneElement.k == null || + oneOnOneElement.k.matches(tags)) { + // We have found a matching key -> we use the template, but only if it scores better + let score = oneOnOneElement.priority ?? + (oneOnOneElement.k === null ? -1 : 0); + if (score > highestScore) { + highestScore = score; + highestTemplate = oneOnOneElement.txt + } + } + } + + if (freeformScore > highestScore) { + return freeform; + } + + if (highestTemplate !== undefined) { + // we render the found template + return this._primer + this.ApplyTemplate(highestTemplate); + } + } else { + return freeform; + } + + } + + protected InnerRender(): string { + + if (this.IsQuestioning() || this._editMode.data) { + // Not yet known or questioning, we have to ask a question + + + return "
" + + this._question + + (this._question !== "" ? "
" : "") + + this._questionElement.Render() + + this._skipButton.Render() + + this._saveButton.Render() + + "
" + } + + if (this.IsKnown()) { + const html = this.RenderAnwser(); + if (html == "") { + return ""; + } + return "" + + "" + html + "" + this._editButton.Render() + + ""; + } + + return ""; + + } + + InnerUpdate(htmlElement: HTMLElement) { + super.InnerUpdate(htmlElement); + this._questionElement.Update(); + this._saveButton.Update(); + this._skipButton.Update(); + this._textField?.Update(); + this._editButton.Update(); + } + +} \ No newline at end of file diff --git a/Layers/CommonTagMappings.ts b/Layers/CommonTagMappings.ts deleted file mode 100644 index f64b163..0000000 --- a/Layers/CommonTagMappings.ts +++ /dev/null @@ -1,60 +0,0 @@ -import {TagMappingOptions} from "../UI/TagMapping"; -import {Img} from "../UI/Img"; - - -export class CommonTagMappings { - - - public static access = new TagMappingOptions({ - key: "access", - mapping: { - yes: "Vrij toegankelijk (op de paden)", - no: "Niet toegankelijk", - private: "Niet toegankelijk, want privegebied", - permissive: "Toegankelijk, maar het is privegebied", - guided: "Enkel met gids of op activiteit" - } - }); - - public static operator = new TagMappingOptions({ - key: "operator", - template: "Beheer door {operator}", - mapping: { - private: 'Beheer door een privepersoon of organisatie' - } - - }); - public static osmLink = new TagMappingOptions({ - key: "id", - mapping: { - "node/-1": "" - }, - template: "" + - Img.osmAbstractLogo + - "" - }); - - public static wikipediaLink = new TagMappingOptions({ - key: "wikipedia", - missing: "", - freeform: (value: string) => { - let link = ""; - // @ts-ignore - if (value.startsWith("https")) { - link = value; - } else { - - const splitted = value.split(":"); - const language = splitted[0]; - splitted.shift(); - const page = splitted.join(":"); - link = 'https://' + language + '.wikipedia.org/wiki/' + page; - - } - return "" + - "" + - "wikipedia"; - } - }); -} \ No newline at end of file diff --git a/Layers/GrbToFix.ts b/Layers/GrbToFix.ts deleted file mode 100644 index 38e569d..0000000 --- a/Layers/GrbToFix.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {LayerDefinition} from "../LayerDefinition"; -import {QuestionDefinition} from "../Logic/Question"; -import {TagMappingOptions} from "../UI/TagMapping"; -import {CommonTagMappings} from "./CommonTagMappings"; -import L from "leaflet" -import {Regex} from "../Logic/TagsFilter"; - -export class GrbToFix extends LayerDefinition { - - constructor() { - super(); - - this.name = "grb"; - this.newElementTags = undefined; - this.icon = "./assets/star.svg"; - this.overpassFilter = new Regex("fixme","GRB"); - this.minzoom = 13; - - - this.questions = [ - QuestionDefinition.GrbNoNumberQuestion(), - QuestionDefinition.GrbHouseNumberQuestion() - ]; - - this.style = function (tags) { - return { - icon: new L.icon({ - iconUrl: "assets/star.svg", - iconSize: [40, 40], - text: "hi" - }) - }; - - } - - this.elementsToShow = [ - new TagMappingOptions( - { - key: "fixme", - template: "

Fixme

{fixme}", - }), - new TagMappingOptions({ - key: "addr:street", - template: "Straat: {addr:street}", - missing: "Geen straat bekend" - }), - new TagMappingOptions({ - key: "addr:housenumber", - template: "Nummer: {addr:housenumber}", - missing: "Geen huisnummer bekend" - }), - CommonTagMappings.osmLink - - ]; - } - - -} \ No newline at end of file diff --git a/Layers/KnownSet.ts b/Layers/KnownSet.ts deleted file mode 100644 index 70f1444..0000000 --- a/Layers/KnownSet.ts +++ /dev/null @@ -1,175 +0,0 @@ -import {LayerDefinition} from "../LayerDefinition"; -import {NatureReserves} from "./NatureReserves"; -import {Toilets} from "./Toilets"; -import {Bos} from "./Bos"; -import {Park} from "./Park"; -import {Playground} from "./Playground"; -import {Bookcases} from "./Bookcases"; -import {Artwork} from "./Artwork"; -import {GrbToFix} from "./GrbToFix"; - - -export class KnownSet { - public name: string; - public title: string; - public layers: LayerDefinition[]; - public welcomeMessage: string; - public gettingStartedPlzLogin: string; - public welcomeBackMessage: string; - - public startzoom: number; - public startLon: number; - public startLat: number; - - static allSets : any = {}; - public welcomeTail: string; - - constructor( - name: string, - title: string, - layers: LayerDefinition[], - startzoom: number, - startLat: number, - startLon: number, - welcomeMessage: string, - gettingStartedPlzLogin: string, - welcomeBackMessage: string, - welcomeTail: string = "" - ) { - this.title = title; - this.startLon = startLon; - this.startLat = startLat; - this.startzoom = startzoom; - this.name = name; - this.layers = layers; - this.welcomeMessage = welcomeMessage; - this.gettingStartedPlzLogin = gettingStartedPlzLogin; - this.welcomeBackMessage = welcomeBackMessage; - this.welcomeTail = welcomeTail; - KnownSet.allSets[this.name] = this; - } - - - static groen = new KnownSet("groen", - "Buurtnatuur", - [new NatureReserves(), new Park(), new Bos()], - 10, - 50.8435, - 4.3688, -"\n" + - "
" + - "

Breng jouw buurtnatuur in kaart

" + - "Natuur maakt gelukkig. Aan de hand van deze website willen we de natuur dicht bij ons beter inventariseren. Met als doel meer mensen te laten genieten van toegankelijke natuur én te strijden voor meer natuur in onze buurten. \n" + - "
    " + - "
  • In welke natuurgebieden kan jij terecht? Hoe toegankelijk zijn ze?
  • " + - "
  • In welke bossen kan een gezin in jouw gemeente opnieuw op adem komen?
  • " + - "
  • Op welke onbekende plekjes is het zalig spelen?
  • " + - "
" + - "

Samen kleuren we heel Vlaanderen en Brussel groen.

" + - "

Blijf op de hoogte van de resultaten van buurtnatuur.be: meld je aan voor e-mailupdates.

\n" - , - - "Begin meteen door een account te maken\n" + - " te maken of\n" + - " in te loggen.", - "", - - "

Tips

" + - - "
    " + - "
  • Over groen ingekleurde gebieden weten we alles wat we willen weten.
  • " + - "
  • Bij rood ingekleurde gebieden ontbreekt nog heel wat info: klik een gebied aan en beantwoord de vragen.
  • " + - "
  • Je kan altijd een foto toevoegen
  • " + - "
  • Je kan ook zelf een gebied toevoegen door op de kaart te klikken
  • " + - "
" + - "" + - "

" + - "De oorspronkelijke data komt van OpenStreetMap en je antwoorden worden daar bewaard.
Omdat iedereen vrij kan meewerken aan dit project, kunnen we niet garanderen dat er geen fouten opduiken." + - "

" + - "Je privacy is belangrijk. We tellen wel hoeveel gebruikers deze website bezoeken. We plaatsen een cookie waar geen persoonlijke informatie in bewaard wordt. " + - "Als je inlogt, komt er een tweede cookie bij met je inloggegevens." + - "
" - ); - - static openToiletMap = new KnownSet( - "toilets", - "Open Toilet Map", - [new Toilets()], - 12, - 51.2, - 3.2, - - - "

Open Toilet Map

\n" + - "\n" + - "

Help us to create the most complete map about all the toilets in the world, based on openStreetMap." + - "One can answer questions here, which help users all over the world to find an accessible toilet, close to them.

" - , - "

Start by creating an account\n" + - " or by " + - " logging in.

", - "Start by clicking a pin and answering the questions" - ); - - static bookcases = new KnownSet( - "bookcases", - "Open Bookcase Map", - [new Bookcases()], - 14, - 51.2, - 3.2, - - - "

Open BoekenkastjesKaart

\n" + - "\n" + - "

" + - "Help mee met het creëeren van een volledige kaart met alle boekenruilkastjes!" + - "Een boekenruilkastje is een vaste plaats in publieke ruimte waar iedereen een boek in kan zetten of uit kan meenemen." + - "Meestal een klein kastje of doosje dat op straat staat, maar ook een oude telefooncellen of een schap in een station valt hieronder."+ - "

" - , - "

Begin met het aanmaken van een account\n" + - " of door je " + - " aan te melden.

", - "Klik op een boekenruilkastje om vragen te beantwoorden" - ); - - static statues = new KnownSet( - "statues", - "Open Artwork Map", - [new Artwork()], - 10, - 50.8435, - 4.3688, - - - "

Open Statue Map

\n" + - "\n" + - "

" + - "Help with creating a map of all statues all over the world!" - - , - "

Start by creating an account\n" + - " or by " + - " logging in.

", - "Start by clicking a pin and answering the questions" - ); - - static grb = new KnownSet( - "grb", - "Grb import fix tool", - [new GrbToFix()], - 10, - 50.8435, - 4.3688, - - - "

GRB Fix tool

\n" + - "\n" + - "Expert use only" - - , - "","" - ); - -} \ No newline at end of file diff --git a/Layers/Playground.ts b/Layers/Playground.ts deleted file mode 100644 index 62cd950..0000000 --- a/Layers/Playground.ts +++ /dev/null @@ -1,72 +0,0 @@ -import {LayerDefinition} from "../LayerDefinition"; -import {Quests} from "../Quests"; -import {TagMappingOptions} from "../UI/TagMapping"; -import L from "leaflet" -import {CommonTagMappings} from "./CommonTagMappings"; -import {Tag} from "../Logic/TagsFilter"; - -export class Playground extends LayerDefinition { - - constructor() { - super(); - this.name = "speeltuin"; - this.icon = "./assets/tree_white_background.svg"; - this.overpassFilter = new Tag("leisure","playground"); - this.newElementTags = [new Tag("leisure", "playground"), new Tag( "fixme", "Toegevoegd met MapComplete, geometry nog uit te tekenen")] - this.maxAllowedOverlapPercentage = 0; - - this.minzoom = 13; - this.questions = [Quests.nameOf(this.name)]; - this.style = this.generateStyleFunction(); - this.elementsToShow = [ - new TagMappingOptions({ - key: "name", - template: "

{name}

", - missing: "

Naamloos park

" - }), - - CommonTagMappings.access, - CommonTagMappings.operator, - CommonTagMappings.osmLink - - ]; - - } - - - private readonly treeIcon = new L.icon({ - iconUrl: "assets/tree_white_background.svg", - iconSize: [40, 40] - }) - - private generateStyleFunction() { - const self = this; - return function (properties: any) { - let questionSeverity = 0; - for (const qd of self.questions) { - if (qd.isApplicable(properties)) { - questionSeverity = Math.max(questionSeverity, qd.severity); - } - } - - let colormapping = { - 0: "#00bb00", - 1: "#00ff00", - 10: "#dddd00", - 20: "#ff0000" - }; - - let colour = colormapping[questionSeverity]; - while (colour == undefined) { - questionSeverity--; - colormapping[questionSeverity]; - } - - return { - color: colour, - icon: self.treeIcon - }; - }; - } - -} \ No newline at end of file diff --git a/Logic/Basemap.ts b/Logic/Basemap.ts index f6ae1f8..ebdfe98 100644 --- a/Logic/Basemap.ts +++ b/Logic/Basemap.ts @@ -51,11 +51,10 @@ export class Basemap { private baseLayers = { - "OpenStreetMap Be": this.osmBeLayer, - "OpenStreetMap": this.osmLayer, - "Luchtfoto AIV Vlaanderen (2013-2015)": this.aivLucht2013Layer, - "Luchtfoto AIV Vlaanderen (laatste)": this.aivLuchtLatestLayer, - "GRB Vlaanderen": this.grbLayer + "Luchtfoto Vlaanderen (recentste door AIV)": this.aivLuchtLatestLayer, + "Luchtfoto Vlaanderen (2013-2015, door AIV)": this.aivLucht2013Layer, + "Kaart van OpenStreetMap": this.osmLayer, + "Kaart Grootschalig ReferentieBestand Vlaanderen (GRB) door AIV": this.grbLayer }; constructor(leafletElementId: string, diff --git a/Logic/Changes.ts b/Logic/Changes.ts index 43a6af0..c8aabaf 100644 --- a/Logic/Changes.ts +++ b/Logic/Changes.ts @@ -7,7 +7,7 @@ import {OsmNode, OsmObject} from "./OsmObject"; import {ElementStorage} from "./ElementStorage"; import {UIEventSource} from "../UI/UIEventSource"; import {Question, QuestionDefinition} from "./Question"; -import {Tag} from "./TagsFilter"; +import {And, Tag, TagsFilter} from "./TagsFilter"; export class Changes { @@ -22,19 +22,34 @@ export class Changes { public readonly pendingChangesES = new UIEventSource(this._pendingChanges.length); public readonly isSaving = new UIEventSource(false); private readonly _changesetComment: string; - private readonly _centerMessage: UIEventSource; constructor( changesetComment: string, login: OsmConnection, - allElements: ElementStorage, - centerMessage: UIEventSource) { + allElements: ElementStorage) { this._changesetComment = changesetComment; this.login = login; this._allElements = allElements; - this._centerMessage = centerMessage; } + addTag(elementId: string, tagsFilter : TagsFilter){ + if(tagsFilter instanceof Tag){ + const tag = tagsFilter as Tag; + this.addChange(elementId, tag.key, tag.value); + return; + } + + if(tagsFilter instanceof And){ + const and = tagsFilter as And; + for (const tag of and.and) { + this.addTag(elementId, tag); + } + return; + } + console.log("Unsupported tagsfilter element to addTag", tagsFilter); + throw "Unsupported tagsFilter element"; + } + /** * Adds a change to the pending changes * @param elementId @@ -42,23 +57,7 @@ export class Changes { * @param value */ addChange(elementId: string, key: string, value: string) { - - if (!this.login.userDetails.data.loggedIn) { - this._centerMessage.setData( - "

Bedankt voor je antwoord!

" + - "

Gelieve in te loggen op OpenStreetMap om dit op te slaan.

" + - "

Nog geen account? Registreer hier

" - ); - const self = this; - this.login.userDetails.addCallback(() => { - if (self.login.userDetails.data.loggedIn) { - self._centerMessage.setData(""); - } - }); - return; - } - - +console.log("Received change",key, value) if (key === undefined || key === null) { console.log("Invalid key"); return; diff --git a/Logic/OsmConnection.ts b/Logic/OsmConnection.ts index 1382ffd..45d5bce 100644 --- a/Logic/OsmConnection.ts +++ b/Logic/OsmConnection.ts @@ -106,7 +106,6 @@ export class OsmConnection { /** * All elements with class 'activate-osm-authentication' are loaded and get an 'onclick' to authenticate - * @param osmConnection */ registerActivateOsmAUthenticationClass() { @@ -144,6 +143,10 @@ export class OsmConnection { } public SetPreference(k:string, v:string) { + if(!this.userDetails.data.loggedIn){ + console.log("Not saving preference: user not logged in"); + return; + } if (this.preferences.data[k] === v) { return; @@ -239,7 +242,6 @@ export class OsmConnection { private AddChange(changesetId: string, changesetXML: string, continuation: ((changesetId: string, idMapping: any) => void)){ - const self = this; this.auth.xhr({ method: 'POST', options: { header: { 'Content-Type': 'text/xml' } }, diff --git a/Logic/OsmObject.ts b/Logic/OsmObject.ts index 9ab3745..7e0697c 100644 --- a/Logic/OsmObject.ts +++ b/Logic/OsmObject.ts @@ -32,6 +32,19 @@ export abstract class OsmObject { abstract SaveExtraData(element); + /** + * Replaces all '"' (double quotes) by '"' + * Bugfix where names containing '"' were not uploaded, such as '"Het Zwin" nature reserve' + * @param string + * @constructor + */ + private Escape(string: string) { + while (string.indexOf('"') >= 0) { + string = string.replace('"', '"'); + } + return string; + } + /** * Generates the changeset-XML for tags * @constructor @@ -41,7 +54,7 @@ export abstract class OsmObject { for (const key in this.tags) { const v = this.tags[key]; if (v !== "") { - tags += ' \n' + tags += ' \n' } } return tags; diff --git a/Logic/TagsFilter.ts b/Logic/TagsFilter.ts index be21150..fc3254d 100644 --- a/Logic/TagsFilter.ts +++ b/Logic/TagsFilter.ts @@ -29,6 +29,10 @@ export class Regex implements TagsFilter { return false; } + substituteValues(tags: any) : TagsFilter{ + throw "Substituting values is not supported on regex tags" + } + } export class Tag implements TagsFilter { @@ -42,11 +46,10 @@ export class Tag implements TagsFilter { matches(tags: { k: string; v: string }[]): boolean { for (const tag of tags) { - if (tag.k === this.key) { if (tag.v === "") { // This tag has been removed - return false; + return this.value === ""; } if (this.value === "*") { // Any is allowed @@ -56,6 +59,10 @@ export class Tag implements TagsFilter { return this.value === tag.v; } } + if(this.value === ""){ + return true; + } + return false; } @@ -70,6 +77,10 @@ export class Tag implements TagsFilter { return ['["' + this.key + '"="' + this.value + '"]']; } + substituteValues(tags: any) { + return new Tag(this.key, TagUtils.ApplyTemplate(this.value, tags)); + } + } export class Or implements TagsFilter { @@ -104,6 +115,14 @@ export class Or implements TagsFilter { return choices; } + substituteValues(tags: any): TagsFilter { + const newChoices = []; + for (const c of this.or) { + newChoices.push(c.substituteValues(tags)); + } + return new Or(newChoices); + } + } export class And implements TagsFilter { @@ -154,12 +173,22 @@ export class And implements TagsFilter { } return allChoices; } + + substituteValues(tags: any): TagsFilter { + const newChoices = []; + for (const c of this.and) { + newChoices.push(c.substituteValues(tags)); + } + return new And(newChoices); + } } export interface TagsFilter { matches(tags: { k: string, v: string }[]): boolean asOverpass(): string[] + + substituteValues(tags: any) : TagsFilter; } export class TagUtils { @@ -172,4 +201,13 @@ export class TagUtils { return result; } + static ApplyTemplate(template: string, tags: any): string { + for (const k in tags) { + while (template.indexOf("{" + k + "}") >= 0) { + template = template.replace("{" + k + "}", tags[k]); + } + } + return template; + } + } \ No newline at end of file diff --git a/UI/Base/TextField.ts b/UI/Base/TextField.ts index d872b03..ca0c4ea 100644 --- a/UI/Base/TextField.ts +++ b/UI/Base/TextField.ts @@ -1,24 +1,32 @@ import {UIElement} from "../UIElement"; import {UIEventSource} from "../UIEventSource"; +import {UIInputElement} from "./UIInputElement"; -export class TextField extends UIElement { +export class TextField extends UIInputElement { - public value = new UIEventSource(""); + public value: UIEventSource = new UIEventSource(""); /** * Pings and has the value data */ public enterPressed = new UIEventSource(undefined); private _placeholder: UIEventSource; + private _mapping: (string) => T; - constructor(placeholder : UIEventSource) { + constructor(placeholder: UIEventSource, + mapping: ((string) => T)) { super(placeholder); this._placeholder = placeholder; + this._mapping = mapping; + } + + GetValue(): UIEventSource { + return this.value.map(this._mapping); } protected InnerRender(): string { return "
" + - "" + + "" + "
"; } @@ -27,19 +35,24 @@ export class TextField extends UIElement { const field = document.getElementById('text-' + this.id); const self = this; field.oninput = () => { + // @ts-ignore self.value.setData(field.value); }; field.addEventListener("keyup", function (event) { if (event.key === "Enter") { + // @ts-ignore self.enterPressed.setData(field.value); } }); + + } Clear() { const field = document.getElementById('text-' + this.id); if (field !== undefined) { + // @ts-ignore field.value = ""; } } diff --git a/UI/Base/UIInputElement.ts b/UI/Base/UIInputElement.ts new file mode 100644 index 0000000..fd4a242 --- /dev/null +++ b/UI/Base/UIInputElement.ts @@ -0,0 +1,8 @@ +import {UIElement} from "../UIElement"; +import {UIEventSource} from "../UIEventSource"; + +export abstract class UIInputElement extends UIElement{ + + abstract GetValue() : UIEventSource; + +} \ No newline at end of file diff --git a/UI/Base/UIRadioButton.ts b/UI/Base/UIRadioButton.ts index 7d1616e..d368dc6 100644 --- a/UI/Base/UIRadioButton.ts +++ b/UI/Base/UIRadioButton.ts @@ -1,30 +1,33 @@ import {UIElement} from "../UIElement"; import {UIEventSource} from "../UIEventSource"; -import {FixedUiElement} from "./FixedUiElement"; -import $ from "jquery" +import {UIInputElement} from "./UIInputElement"; -export class UIRadioButton extends UIElement { +export class UIRadioButton extends UIInputElement { - public readonly SelectedElementIndex: UIEventSource<{ index: number, value: string }> - = new UIEventSource<{ index: number, value: string }>(null); + public readonly SelectedElementIndex: UIEventSource + = new UIEventSource(null); - private readonly _elements: UIEventSource<{ element: UIElement, value: string }[]> + private readonly _elements: UIEventSource + private _selectFirstAsDefault: boolean; + private _valueMapping: (i: number) => T; - constructor(elements: UIEventSource<{ element: UIElement, value: string }[]>) { + constructor(elements: UIEventSource, + valueMapping: ((i: number) => T), + selectFirstAsDefault = true) { super(elements); this._elements = elements; + this._selectFirstAsDefault = selectFirstAsDefault; + const self = this; + this._valueMapping = valueMapping; + this.SelectedElementIndex.addCallback(() => { + self.InnerUpdate(undefined); + }) + } + + GetValue(): UIEventSource { + return this.SelectedElementIndex.map(this._valueMapping); } - static FromStrings(choices: string[]): UIRadioButton { - const wrapped = []; - for (const choice of choices) { - wrapped.push({ - element: new FixedUiElement(choice), - value: choice - }); - } - return new UIRadioButton(new UIEventSource<{ element: UIElement, value: string }[]>(wrapped)) - } private IdFor(i) { return 'radio-' + this.id + '-' + i; @@ -35,12 +38,9 @@ export class UIRadioButton extends UIElement { let body = ""; let i = 0; for (const el of this._elements.data) { - const uielement = el.element; - const value = el.value; - const htmlElement = - '' + - '' + + '' + + '' + '
'; body += htmlElement; @@ -51,7 +51,6 @@ export class UIRadioButton extends UIElement { } InnerUpdate(htmlElement: HTMLElement) { - super.InnerUpdate(htmlElement); const self = this; function checkButtons() { @@ -59,8 +58,7 @@ export class UIRadioButton extends UIElement { const el = document.getElementById(self.IdFor(i)); // @ts-ignore if (el.checked) { - var v = {index: i, value: self._elements.data[i].value} - self.SelectedElementIndex.setData(v); + self.SelectedElementIndex.setData(i); } } } @@ -74,29 +72,32 @@ export class UIRadioButton extends UIElement { ); if (this.SelectedElementIndex.data == null) { - const el = document.getElementById(this.IdFor(0)); - el.checked = true; - checkButtons(); + if (this._selectFirstAsDefault) { + const el = document.getElementById(this.IdFor(0)); + // @ts-ignore + el.checked = true; + checkButtons(); + } } else { // We check that what is selected matches the previous rendering var checked = -1; - var expected = -1 - for (let i = 0; i < self._elements.data.length; i++) { - const el = document.getElementById(self.IdFor(i)); - // @ts-ignore - if (el.checked) { - checked = i; + var expected = this.SelectedElementIndex.data; + if (expected) { + + for (let i = 0; i < self._elements.data.length; i++) { + const el = document.getElementById(self.IdFor(i)); + // @ts-ignore + if (el.checked) { + checked = i; + } } - if (el.value === this.SelectedElementIndex.data.value) { - expected = i; + if (expected != checked) { + const el = document.getElementById(this.IdFor(expected)); + // @ts-ignore + el.checked = true; } } - if (expected != checked) { - const el = document.getElementById(this.IdFor(expected)); - // @ts-ignore - el.checked = true; - } } diff --git a/UI/Base/UIRadioButtonWithOther.ts b/UI/Base/UIRadioButtonWithOther.ts new file mode 100644 index 0000000..806764d --- /dev/null +++ b/UI/Base/UIRadioButtonWithOther.ts @@ -0,0 +1,72 @@ +import {UIInputElement} from "./UIInputElement"; +import {UIEventSource} from "../UIEventSource"; +import {UIRadioButton} from "./UIRadioButton"; +import {UIElement} from "../UIElement"; +import {TextField} from "./TextField"; +import {FixedUiElement} from "./FixedUiElement"; + + +export class UIRadioButtonWithOther extends UIInputElement { + private readonly _radioSelector: UIRadioButton; + private readonly _freeformText: TextField; + private readonly _value: UIEventSource = new UIEventSource(undefined) + + constructor(choices: UIElement[], + otherChoiceTemplate: string, + placeholder: string, + choiceToValue: ((i: number) => T), + stringToValue: ((string: string) => T)) { + super(undefined); + const self = this; + + this._freeformText = new TextField( + new UIEventSource(placeholder), + stringToValue); + + + const otherChoiceElement = new FixedUiElement( + otherChoiceTemplate.replace("$$$", this._freeformText.Render())); + choices.push(otherChoiceElement); + + this._radioSelector = new UIRadioButton(new UIEventSource(choices), + (i) => { + if (i === undefined || i === null) { + return undefined; + } + if (i + 1 >= choices.length) { + return this._freeformText.GetValue().data + } + return choiceToValue(i); + }, + false); + + this._radioSelector.GetValue().addCallback( + (i) => { + self._value.setData(i); + }); + this._freeformText.GetValue().addCallback((str) => { + self._value.setData(str); + } + ); + this._freeformText.onClick(() => { + self._radioSelector.SelectedElementIndex.setData(choices.length - 1); + }) + + + } + + GetValue(): UIEventSource { + return this._value; + } + + protected InnerRender(): string { + return this._radioSelector.Render(); + } + + InnerUpdate(htmlElement: HTMLElement) { + super.InnerUpdate(htmlElement); + this._radioSelector.Update(); + this._freeformText.Update(); + } + +} \ No newline at end of file diff --git a/UI/FeatureInfoBox.ts b/UI/FeatureInfoBox.ts index 4558734..b2fa05c 100644 --- a/UI/FeatureInfoBox.ts +++ b/UI/FeatureInfoBox.ts @@ -1,14 +1,15 @@ import {UIElement} from "./UIElement"; -import {TagMapping, TagMappingOptions} from "./TagMapping"; -import {Question, QuestionDefinition} from "../Logic/Question"; import {UIEventSource} from "./UIEventSource"; import {QuestionPicker} from "./QuestionPicker"; import {OsmImageUploadHandler} from "../Logic/OsmImageUploadHandler"; import {ImageCarousel} from "./Image/ImageCarousel"; import {Changes} from "../Logic/Changes"; import {UserDetails} from "../Logic/OsmConnection"; -import {CommonTagMappings} from "../Layers/CommonTagMappings"; import {VerticalCombine} from "./Base/VerticalCombine"; +import {TagRendering, TagRenderingOptions} from "../Customizations/TagRendering"; +import {OsmLink} from "../Customizations/Questions/OsmLink"; +import {WikipediaLink} from "../Customizations/Questions/WikipediaLink"; +import {And} from "../Logic/TagsFilter"; export class FeatureInfoBox extends UIElement { @@ -17,7 +18,6 @@ export class FeatureInfoBox extends UIElement { private _title: UIElement; private _osmLink: UIElement; - private _infoElements: UIElement[] private _questions: QuestionPicker; @@ -27,15 +27,16 @@ export class FeatureInfoBox extends UIElement { private _imageElement: ImageCarousel; private _pictureUploader: UIElement; private _wikipedialink: UIElement; + private _infoboxes: TagRendering[]; constructor( tagsES: UIEventSource, - elementsToShow: (TagMappingOptions | QuestionDefinition | UIElement)[], - questions: QuestionDefinition[], + title: TagRenderingOptions, + elementsToShow: TagRenderingOptions[], changes: Changes, userDetails: UIEventSource, - preferedPictureLicense : UIEventSource + preferedPictureLicense: UIEventSource ) { super(tagsES); this._tagsES = tagsES; @@ -45,48 +46,66 @@ export class FeatureInfoBox extends UIElement { this._imageElement = new ImageCarousel(this._tagsES); - this._questions = new QuestionPicker( - this._changes.asQuestions(questions), this._tagsES); - - var infoboxes: UIElement[] = []; - for (const uiElement of elementsToShow) { - if (uiElement instanceof QuestionDefinition) { - const questionDef = uiElement as QuestionDefinition; - const question = new Question(this._changes, questionDef); - infoboxes.push(question.CreateHtml(this._tagsES)); - } else if (uiElement instanceof TagMappingOptions) { - const tagMappingOpt = uiElement as TagMappingOptions; - infoboxes.push(new TagMapping(tagMappingOpt, this._tagsES)) - } else { - const ui = uiElement as UIElement; - infoboxes.push(ui); + this._infoboxes = []; + for (const tagRenderingOption of elementsToShow) { + if (tagRenderingOption.options === undefined) { + throw "Tagrendering.options not defined" } - + this._infoboxes.push(new TagRendering(this._tagsES, this._changes, tagRenderingOption.options)) } - this._title = infoboxes.shift(); - this._infoElements = infoboxes; + title = title ?? new TagRenderingOptions( + { + mappings: [{k: new And([]), txt: ""}] + } + ) - this._osmLink = new TagMapping(CommonTagMappings.osmLink, this._tagsES); - this._wikipedialink = new TagMapping(CommonTagMappings.wikipediaLink, this._tagsES); + this._title = new TagRendering(this._tagsES, this._changes, title.options); + + this._osmLink = new TagRendering(this._tagsES, this._changes, new OsmLink().options); + this._wikipedialink = new TagRendering(this._tagsES, this._changes, new WikipediaLink().options); this._pictureUploader = new OsmImageUploadHandler(tagsES, userDetails, preferedPictureLicense, changes, this._imageElement.slideshow).getUI(); - } InnerRender(): string { - let questions = ""; - if (this._userDetails.data.loggedIn) { - // Questions is embedded in a span, because it'll hide the parent when the questions dissappear - questions = ""+this._questions.HideOnEmpty(true).Render()+""; + const info = []; + const questions = []; + + for (const infobox of this._infoboxes) { + if (infobox.IsKnown()) { + info.push(infobox); + } else if (infobox.IsQuestioning()) { + questions.push(infobox); + } + + } + + let questionsHtml = ""; + + if (this._userDetails.data.loggedIn && questions.length > 0) { + // We select the most important question and render that one + let mostImportantQuestion; + let score = -1000; + for (const question of questions) { + + if (mostImportantQuestion === undefined || question.priority > score) { + mostImportantQuestion = question; + score = question.priority; + } + } + + questionsHtml = mostImportantQuestion.Render(); } return "
" + "
" + - "" + this._title.Render() + "" + + "" + + this._title.Render() + + "" + this._wikipedialink.Render() + this._osmLink.Render() + "
" + @@ -96,9 +115,9 @@ export class FeatureInfoBox extends UIElement { this._imageElement.Render() + this._pictureUploader.Render() + - new VerticalCombine(this._infoElements, 'infobox-information').HideOnEmpty(true).Render() + + new VerticalCombine(info, "infobox-information ").Render() + - questions + + questionsHtml + "
" + @@ -110,11 +129,18 @@ export class FeatureInfoBox extends UIElement { super.Activate(); this._imageElement.Activate(); this._pictureUploader.Activate(); + for (const infobox of this._infoboxes) { + infobox.Activate(); + } } Update() { super.Update(); this._imageElement.Update(); this._pictureUploader.Update(); + this._title.Update(); + for (const infobox of this._infoboxes) { + infobox.Update(); + } } } diff --git a/UI/QuestionPicker.ts b/UI/QuestionPicker.ts index 0fb6252..b9cf024 100644 --- a/UI/QuestionPicker.ts +++ b/UI/QuestionPicker.ts @@ -39,7 +39,7 @@ export class QuestionPicker extends UIElement { return "Er zijn geen vragen meer!"; } - return "
" + + return "
" + highestQ.CreateHtml(this.source).Render() + "
"; } diff --git a/UI/SaveButton.ts b/UI/SaveButton.ts new file mode 100644 index 0000000..3bba390 --- /dev/null +++ b/UI/SaveButton.ts @@ -0,0 +1,25 @@ +import {UIEventSource} from "./UIEventSource"; +import {UIElement} from "./UIElement"; + +export class SaveButton extends UIElement { + private _value: UIEventSource; + + constructor(value: UIEventSource) { + super(value); + if(value === undefined){ + throw "No event source for savebutton, something is wrong" + } + this._value = value; + } + + protected InnerRender(): string { + if (this._value.data === undefined || + this._value.data === null + || this._value.data === "" + ) { + return "Opslaan" + } + return "Opslaan"; + } + +} \ No newline at end of file diff --git a/UI/SearchAndGo.ts b/UI/SearchAndGo.ts index a0d549a..3916168 100644 --- a/UI/SearchAndGo.ts +++ b/UI/SearchAndGo.ts @@ -60,8 +60,8 @@ export class SearchAndGo extends UIElement { protected InnerRender(): string { // "Search " + - return this._goButton.Render() + - this._searchField.Render(); + return this._searchField.Render() + + this._goButton.Render(); } diff --git a/UI/TagMapping.ts b/UI/TagMapping.ts deleted file mode 100644 index 547dd80..0000000 --- a/UI/TagMapping.ts +++ /dev/null @@ -1,80 +0,0 @@ -import {UIElement} from "./UIElement"; -import {UIEventSource} from "./UIEventSource"; - - -export class TagMappingOptions { - key: string;// The key to show - mapping?: any;// dictionary for specific values, the values are substituted - template?: string; // The template, where {key} will be substituted - missing?: string// What to show when the key is not there - freeform?: ((string) => string) // Freeform template function, only applied on the value if nothing matches - - constructor(options: { - key: string, - mapping?: any, - template?: string, - missing?: string - freeform?: ((string) => string) - }) { - this.key = options.key; - this.mapping = options.mapping; - this.template = options.template; - this.missing = options.missing; - this.freeform = options.freeform; - } - -} - -export class TagMapping extends UIElement { - - private readonly tags; - private readonly options: TagMappingOptions; - - constructor( - options: TagMappingOptions, - tags: UIEventSource) { - super(tags); - this.tags = tags.data; - this.options = options; - } - - IsEmpty(): boolean { - const o = this.options; - return this.tags[o.key] === undefined && o.missing === undefined; - } - - protected InnerRender(): string { - - const o = this.options; - const v = this.tags[o.key]; - - if (v === undefined) { - if (o.missing === undefined) { - return ""; - } - return o.missing; - } - - if (o.mapping !== undefined) { - - const mapped = o.mapping[v]; - if (mapped !== undefined) { - return mapped; - } - } - - if (o.template !== undefined) { - return o.template.replace("{" + o.key + "}", v); - } - - if(o.freeform !== undefined){ - return o.freeform(v); - } - - console.log("Warning: no match for " + o.key + "=" + v); - return v; - } - InnerUpdate(htmlElement: HTMLElement) { - } - -} \ No newline at end of file diff --git a/index.css b/index.css index 6db4510..1028fce 100644 --- a/index.css +++ b/index.css @@ -160,6 +160,7 @@ body { position: relative; float: left; margin-top: 0.2em; + margin-left: 1em; } #searchbox input[type="text"] { @@ -170,12 +171,13 @@ body { .search-go { position: relative; + float: right; height: 1.2em; border: 2px solid black; border-radius: 2em; padding: 0.4em; - float: left; - margin-right: 0.5em; + margin-left: 0.5em; + margin-right: 0; } @@ -216,7 +218,7 @@ body { #collapseButton { position: absolute; - right: 0; + right: 1em; background-color: white; margin: 1.5em; border: 2px solid black; @@ -570,6 +572,42 @@ body { fill: #7ebc6f; } +.featureinfoboxtitle .answer { + display: inline; + margin-right: 3em; +} + +.featureinfoboxtitle .answer-text { + display: inline; +} + +.featureinfoboxtitle .editbutton { + float: none; + width: 0.8em; + height: 0.8em; + padding: 0.3em; + border-radius: 0.35em; + border: solid black 1px; + margin-left: 1em; + top: 0.2em; + position: absolute; + +} + + +.editbutton { + width: 1.3em; + height: 1.3em; + padding: 0.5em; + border-radius: 0.65em; + border: solid black 1px; + + font-size: medium; + float: right; + + +} + .wikipedialink { position: absolute; right: 24px; @@ -606,11 +644,61 @@ body { margin-top: 1em; } -.infobox-questions { +.question { margin-top: 1em; background-color: #e5f5ff; padding: 1em; border-radius: 1em; margin-right: 1em; - + font-size: larger; + +} + +.answer { + display: inline-block; + margin: 0.1em; + width: 100%; + font-size: large; +} + +.answer-text { + width: 90%; + display: inline-block +} + + +/**** The save button *****/ + +.save { + display: inline-block; + border: solid white 2px; + background-color: #3a3aeb; + color: white; + padding: 0.2em; + padding-left: 0.3em; + padding-right: 0.3em; + font-size: x-large; + font-weight: bold; + border-radius: 1.5em; +} + +.save-non-active { + display: inline-block; + border: solid lightgrey 2px; + color: grey; + padding: 0.2em; + padding-left: 0.3em; + padding-right: 0.3em; + font-size: x-large; + font-weight: bold; + border-radius: 1.5em; +} + +.skip-button { + display: inline-block; + border: solid black 0.5px; + padding: 0.2em; + padding-left: 0.3em; + padding-right: 0.3em; + border-radius: 1.5em; } \ No newline at end of file diff --git a/index.ts b/index.ts index bfae562..d9bb317 100644 --- a/index.ts +++ b/index.ts @@ -7,7 +7,6 @@ import {Basemap} from "./Logic/Basemap"; import {PendingChanges} from "./UI/PendingChanges"; import {CenterMessageBox} from "./UI/CenterMessageBox"; import {Helpers} from "./Helpers"; -import {KnownSet} from "./Layers/KnownSet"; import {Tag, TagUtils} from "./Logic/TagsFilter"; import {FilteredLayer} from "./Logic/FilteredLayer"; import {LayerUpdater} from "./Logic/LayerUpdater"; @@ -21,6 +20,7 @@ import {SimpleAddUI} from "./UI/SimpleAddUI"; import {VariableUiElement} from "./UI/Base/VariableUIElement"; import {SearchAndGo} from "./UI/SearchAndGo"; import {CollapseButton} from "./UI/Base/CollapseButton"; +import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; let dryRun = false; @@ -34,10 +34,11 @@ if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { } + // ----------------- SELECT THE RIGHT QUESTSET ----------------- -let questSetToRender = KnownSet.groen; +let defaultQuest = "groen" if (window.location.search) { const params = window.location.search.substr(1).split("&"); const paramDict: any = {}; @@ -45,13 +46,17 @@ if (window.location.search) { var kv = param.split("="); paramDict[kv[0]] = kv[1]; } - if (paramDict.quests) { - questSetToRender = KnownSet.allSets[paramDict.quests]; - console.log("Using quests: ", questSetToRender.name); + defaultQuest = paramDict.quests + } + if(paramDict.test){ + dryRun = true; } - } + +const questSetToRender = AllKnownLayouts.allSets[defaultQuest]; +console.log("Using quests: ", questSetToRender.name); + document.title = questSetToRender.title; @@ -79,12 +84,12 @@ const locationControl = new UIEventSource<{ lat: number, lon: number, zoom: numb // ----------------- Prepare the important objects ----------------- -const saveTimeout = 5000; // After this many milliseconds without changes, saves are sent of to OSM +const saveTimeout = 30000; // After this many milliseconds without changes, saves are sent of to OSM const allElements = new ElementStorage(); const osmConnection = new OsmConnection(dryRun); const changes = new Changes( "Beantwoorden van vragen met MapComplete voor vragenset #" + questSetToRender.name, - osmConnection, allElements, centerMessage); + osmConnection, allElements); const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement( locationControl.map((location) => { const mapComplete = "Mapcomple " + @@ -138,8 +143,8 @@ for (const layer of questSetToRender.layers) { return new FeatureInfoBox( tagsES, + layer.title, layer.elementsToShow, - layer.questions, changes, osmConnection.userDetails, preferedPictureLicense @@ -191,8 +196,8 @@ selectedElement.addCallback((data) => { leftMessage.setData(() => new FeatureInfoBox( allElements.getElement(data.id), + layer.title, layer.elementsToShow, - layer.questions, changes, osmConnection.userDetails, preferedPictureLicense @@ -214,6 +219,7 @@ new SearchAndGo(bm).AttachTo("searchbox"); new CollapseButton("messagesbox") .AttachTo("collapseButton"); + var welcomeMessage = () => { return new VariableUiElement( osmConnection.userDetails.map((userdetails) => { diff --git a/package-lock.json b/package-lock.json index 4b57a62..d4ff40f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -992,11 +992,6 @@ "physical-cpu-count": "^2.0.0" } }, - "@splidejs/splide": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@splidejs/splide/-/splide-2.4.0.tgz", - "integrity": "sha512-IiorzntnnTBdXFxgWgz2x2QgW8/xmr4AU6+biUD+knGMZrSoBU34DtrK9qAdLkYVMevNyUomrJtGZHFZl9nwrw==" - }, "@types/geojson": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", diff --git a/package.json b/package.json index 619c0bb..e02f7d4 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "author": "pietervdvn", "license": "MIT", "dependencies": { - "@splidejs/splide": "^2.4.0", "fs": "0.0.1-security", "jquery": "latest", "leaflet": "^1.6.0", diff --git a/test.ts b/test.ts index 68791ae..9079435 100644 --- a/test.ts +++ b/test.ts @@ -1,26 +1,21 @@ -import {Geocoding} from "./Logic/Geocoding"; -import {SearchAndGo} from "./UI/SearchAndGo"; -import {TextField} from "./UI/Base/TextField"; -import {VariableUiElement} from "./UI/Base/VariableUIElement"; -import {DropDownUI} from "./UI/Base/DropDownUI"; import {UIEventSource} from "./UI/UIEventSource"; +import {Changes} from "./Logic/Changes"; +import {OsmConnection} from "./Logic/OsmConnection"; +import {ElementStorage} from "./Logic/ElementStorage"; +import {WikipediaLink} from "./Customizations/Questions/WikipediaLink"; +import {OsmLink} from "./Customizations/Questions/OsmLink"; -console.log("HI"); +const tags = {name: "Test", + wikipedia: "nl:Pieter", + id: "node/-1"}; +const tagsES = new UIEventSource(tags); + +const login = new OsmConnection(true); + +const allElements = new ElementStorage(); +allElements.addElementById(tags.id, tagsES); + +const changes = new Changes("Test", login, allElements) -var control = new UIEventSource("b"); -control.addCallback((data) => { - console.log("> GOT", control.data) -}) - -new DropDownUI("Test", - [{value: "a", shown: "a"}, - {value: "b", shown: "b"}, - {value: "c", shown: "c"}, - ], control -).AttachTo("maindiv"); - - -new VariableUiElement(control).AttachTo("extradiv"); - -window.setTimeout(() => {control.setData("a")}, 1000); \ No newline at end of file +new OsmLink(tagsES, changes).AttachTo("maindiv"); \ No newline at end of file