New question system
This commit is contained in:
parent
1738fc4252
commit
d1f8080c24
45 changed files with 1391 additions and 689 deletions
24
Customizations/AllKnownLayouts.ts
Normal file
24
Customizations/AllKnownLayouts.ts
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 };
|
||||
|
|
@ -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: "<img class='popupImg' alt='image' src='{image}' />"}),
|
||||
CommonTagMappings.osmLink
|
||||
new TagMappingOptions({key: "image", template: "<img class='popupImg' alt='image' src='{image}' />"})
|
||||
|
||||
];
|
||||
}
|
|
@ -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}",
|
|
@ -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 {
|
88
Customizations/Layers/GrbToFix.ts
Normal file
88
Customizations/Layers/GrbToFix.ts
Normal file
|
@ -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} <b>{addr:housenumber}</b>",
|
||||
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: <b>{grb:housenumber:human}</b>",
|
||||
substitute: true
|
||||
},
|
||||
{
|
||||
k: new And([new Tag("addr:housenumber", "{addr:housenumber}"), new Tag("fixme", "")]),
|
||||
txt: "Volg OSM: <b>{addr:housenumber}</b>",
|
||||
substitute: true
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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 {
|
|
@ -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 {
|
||||
|
||||
|
@ -11,24 +14,15 @@ export class Park extends LayerDefinition {
|
|||
this.name = "park";
|
||||
this.icon = "./assets/tree_white_background.svg";
|
||||
this.overpassFilter =
|
||||
new Or([new Tag("leisure","park"), new Tag("landuse","village_green")]);
|
||||
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 {
|
|
@ -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{
|
||||
|
53
Customizations/Layout.ts
Normal file
53
Customizations/Layout.ts
Normal file
|
@ -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(
|
||||
|
||||
);
|
||||
|
||||
*/
|
||||
}
|
||||
|
27
Customizations/Layouts/Bookcases.ts
Normal file
27
Customizations/Layouts/Bookcases.ts
Normal file
|
@ -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,
|
||||
|
||||
|
||||
" <h3>Open BoekenkastjesKaart</h3>\n" +
|
||||
"\n" +
|
||||
"<p>" +
|
||||
"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."+
|
||||
"</p>"
|
||||
,
|
||||
" <p>Begin met <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">het aanmaken van een account\n" +
|
||||
" </a> of door je " +
|
||||
" <span onclick=\"authOsm()\" class=\"activate-osm-authentication\">aan te melden</span>.</p>",
|
||||
"Klik op een boekenruilkastje om vragen te beantwoorden");
|
||||
}
|
||||
}
|
21
Customizations/Layouts/GRB.ts
Normal file
21
Customizations/Layouts/GRB.ts
Normal file
|
@ -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,
|
||||
|
||||
|
||||
"<h3>GRB Fix tool</h3>\n" +
|
||||
"\n" +
|
||||
"Expert use only"
|
||||
|
||||
,
|
||||
"", "");
|
||||
}
|
||||
}
|
50
Customizations/Layouts/Groen.ts
Normal file
50
Customizations/Layouts/Groen.ts
Normal file
|
@ -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" +
|
||||
"<img src='assets/groen.svg' alt='logo-groen' class='logo'> <br />" +
|
||||
"<h3>Breng jouw buurtnatuur in kaart</h3>" +
|
||||
"<b>Natuur maakt gelukkig.</b> 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" +
|
||||
"<ul>" +
|
||||
"<li>In welke natuurgebieden kan jij terecht? Hoe toegankelijk zijn ze?</li>" +
|
||||
"<li>In welke bossen kan een gezin in jouw gemeente opnieuw op adem komen?</li>" +
|
||||
"<li>Op welke onbekende plekjes is het zalig spelen?</li>" +
|
||||
"</ul>" +
|
||||
"<p>Samen kleuren we heel Vlaanderen en Brussel groen.</p>" +
|
||||
"<p>Blijf op de hoogte van de resultaten van buurtnatuur.be: <a href=\"https://www.groen.be/buurtnatuur\" target='_blank'>meld je aan voor e-mailupdates</a>.</p> \n"
|
||||
,
|
||||
|
||||
"<b>Begin meteen door <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">een account te maken\n" +
|
||||
" te maken</a> of\n" +
|
||||
" <span onclick=\"authOsm()\" class=\"activate-osm-authentication\">in te loggen</span>.</b>",
|
||||
"",
|
||||
|
||||
"<h4>Tips</h4>" +
|
||||
|
||||
"<ul>" +
|
||||
"<li>Over groen ingekleurde gebieden weten we alles wat we willen weten.</li>" +
|
||||
"<li>Bij rood ingekleurde gebieden ontbreekt nog heel wat info: klik een gebied aan en beantwoord de vragen.</li>" +
|
||||
"<li>Je kan altijd een foto toevoegen</li>" +
|
||||
"<li>Je kan ook zelf een gebied toevoegen door op de kaart te klikken</li>" +
|
||||
"</ul>" +
|
||||
"<small>" +
|
||||
"<p>" +
|
||||
"De oorspronkelijke data komt van <b>OpenStreetMap</b> en je antwoorden worden daar bewaard.<br/> Omdat iedereen vrij kan meewerken aan dit project, kunnen we niet garanderen dat er geen fouten opduiken." +
|
||||
"</p>" +
|
||||
"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." +
|
||||
"</small>"
|
||||
);
|
||||
}
|
||||
}
|
26
Customizations/Layouts/Statues.ts
Normal file
26
Customizations/Layouts/Statues.ts
Normal file
|
@ -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,
|
||||
|
||||
|
||||
" <h3>Open Statue Map</h3>\n" +
|
||||
"\n" +
|
||||
"<p>" +
|
||||
"Help with creating a map of all statues all over the world!"
|
||||
|
||||
,
|
||||
" <p>Start by <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">creating an account\n" +
|
||||
" </a> or by " +
|
||||
" <span onclick=\"authOsm()\" class=\"activate-osm-authentication\">logging in</span>.</p>",
|
||||
"Start by clicking a pin and answering the questions");
|
||||
}
|
||||
|
||||
}
|
24
Customizations/Layouts/Toilets.ts
Normal file
24
Customizations/Layouts/Toilets.ts
Normal file
|
@ -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,
|
||||
|
||||
|
||||
" <h3>Open Toilet Map</h3>\n" +
|
||||
"\n" +
|
||||
"<p>Help us to create the most complete map about <i>all</i> 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.</p>"
|
||||
,
|
||||
" <p>Start by <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">creating an account\n" +
|
||||
" </a> or by " +
|
||||
" <span onclick=\"authOsm()\" class=\"activate-osm-authentication\">logging in</span>.</p>",
|
||||
"Start by clicking a pin and answering the questions");
|
||||
}
|
||||
}
|
39
Customizations/Questions/AccessTag.ts
Normal file
39
Customizations/Questions/AccessTag.ts
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
29
Customizations/Questions/NameInline.ts
Normal file
29
Customizations/Questions/NameInline.ts
Normal file
|
@ -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)}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
}
|
30
Customizations/Questions/NameQuestion.ts
Normal file
30
Customizations/Questions/NameQuestion.ts
Normal file
|
@ -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 <i>officiële</i> 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);
|
||||
}
|
||||
|
||||
}
|
30
Customizations/Questions/OperatorTag.ts
Normal file
30
Customizations/Questions/OperatorTag.ts
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
31
Customizations/Questions/OsmLink.ts
Normal file
31
Customizations/Questions/OsmLink.ts
Normal file
|
@ -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:
|
||||
"<span class='osmlink'><a href='https://osm.org/{id}' target='_blank'>" +
|
||||
Img.osmAbstractLogo +
|
||||
"</a></span>",
|
||||
placeholder: "",
|
||||
},
|
||||
mappings: [
|
||||
{k: new Tag("id", "node/-1"), txt: "<span class='alert'>Uploading</span>"}
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(OsmLink.options);
|
||||
}
|
||||
|
||||
|
||||
}
|
53
Customizations/Questions/WikipediaLink.ts
Normal file
53
Customizations/Questions/WikipediaLink.ts
Normal file
|
@ -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:
|
||||
"<span class='wikipedialink'>" +
|
||||
"<a href='{wikipedia}' target='_blank'>" +
|
||||
"<img width='64px' src='./assets/wikipedia.svg' alt='wikipedia'>" +
|
||||
"</a></span>",
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
358
Customizations/TagRendering.ts
Normal file
358
Customizations/TagRendering.ts
Normal file
|
@ -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<TagsFilter>; // Only here to update
|
||||
|
||||
private readonly _saveButton: UIElement;
|
||||
private readonly _skipButton: UIElement;
|
||||
private readonly _editButton: UIElement;
|
||||
|
||||
private readonly _questionSkipped: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
|
||||
private readonly _editMode: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
|
||||
|
||||
constructor(tags: UIEventSource<any>, 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<TagsFilter>;
|
||||
|
||||
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<string>(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("<img class='editbutton' src='./assets/pencil.svg' alt='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 "<span class='skip-button'>Annuleren</span>";
|
||||
} else {
|
||||
return "<span class='skip-button'>Ik weet het niet zeker...</span>";
|
||||
}
|
||||
});
|
||||
// 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 "<div class='question'>" +
|
||||
this._question +
|
||||
(this._question !== "" ? "<br/>" : "") +
|
||||
this._questionElement.Render() +
|
||||
this._skipButton.Render() +
|
||||
this._saveButton.Render() +
|
||||
"</div>"
|
||||
}
|
||||
|
||||
if (this.IsKnown()) {
|
||||
const html = this.RenderAnwser();
|
||||
if (html == "") {
|
||||
return "";
|
||||
}
|
||||
return "<span class='answer'>" +
|
||||
"<span class='answer-text'>" + html + "</span>" + this._editButton.Render() +
|
||||
"</span>";
|
||||
}
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
InnerUpdate(htmlElement: HTMLElement) {
|
||||
super.InnerUpdate(htmlElement);
|
||||
this._questionElement.Update();
|
||||
this._saveButton.Update();
|
||||
this._skipButton.Update();
|
||||
this._textField?.Update();
|
||||
this._editButton.Update();
|
||||
}
|
||||
|
||||
}
|
|
@ -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: "<span class='osmlink'><a href='https://osm.org/{id}' target='_blank'>" +
|
||||
Img.osmAbstractLogo +
|
||||
"</a></span>"
|
||||
});
|
||||
|
||||
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 "<span class='wikipedialink'>" +
|
||||
"<a href='" + link + "' target='_blank'>" +
|
||||
"<img width='64px' src='./assets/wikipedia.svg' alt='wikipedia'" +
|
||||
"</a></span>";
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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: "<h2>Fixme</h2>{fixme}",
|
||||
}),
|
||||
new TagMappingOptions({
|
||||
key: "addr:street",
|
||||
template: "Straat: <b>{addr:street}</b>",
|
||||
missing: "<b>Geen straat bekend</b>"
|
||||
}),
|
||||
new TagMappingOptions({
|
||||
key: "addr:housenumber",
|
||||
template: "Nummer: <b>{addr:housenumber}</b>",
|
||||
missing: "<b>Geen huisnummer bekend</b>"
|
||||
}),
|
||||
CommonTagMappings.osmLink
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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" +
|
||||
"<img src='assets/groen.svg' alt='logo-groen' class='logo'> <br />" +
|
||||
"<h3>Breng jouw buurtnatuur in kaart</h3>" +
|
||||
"<b>Natuur maakt gelukkig.</b> 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" +
|
||||
"<ul>" +
|
||||
"<li>In welke natuurgebieden kan jij terecht? Hoe toegankelijk zijn ze?</li>" +
|
||||
"<li>In welke bossen kan een gezin in jouw gemeente opnieuw op adem komen?</li>" +
|
||||
"<li>Op welke onbekende plekjes is het zalig spelen?</li>" +
|
||||
"</ul>" +
|
||||
"<p>Samen kleuren we heel Vlaanderen en Brussel groen.</p>" +
|
||||
"<p>Blijf op de hoogte van de resultaten van buurtnatuur.be: <a href=\"https://www.groen.be/buurtnatuur\" target='_blank'>meld je aan voor e-mailupdates</a>.</p> \n"
|
||||
,
|
||||
|
||||
"<b>Begin meteen door <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">een account te maken\n" +
|
||||
" te maken</a> of\n" +
|
||||
" <span onclick=\"authOsm()\" class=\"activate-osm-authentication\">in te loggen</span>.</b>",
|
||||
"",
|
||||
|
||||
"<h4>Tips</h4>" +
|
||||
|
||||
"<ul>" +
|
||||
"<li>Over groen ingekleurde gebieden weten we alles wat we willen weten.</li>" +
|
||||
"<li>Bij rood ingekleurde gebieden ontbreekt nog heel wat info: klik een gebied aan en beantwoord de vragen.</li>" +
|
||||
"<li>Je kan altijd een foto toevoegen</li>" +
|
||||
"<li>Je kan ook zelf een gebied toevoegen door op de kaart te klikken</li>" +
|
||||
"</ul>" +
|
||||
"<small>" +
|
||||
"<p>" +
|
||||
"De oorspronkelijke data komt van <b>OpenStreetMap</b> en je antwoorden worden daar bewaard.<br/> Omdat iedereen vrij kan meewerken aan dit project, kunnen we niet garanderen dat er geen fouten opduiken." +
|
||||
"</p>" +
|
||||
"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." +
|
||||
"</small>"
|
||||
);
|
||||
|
||||
static openToiletMap = new KnownSet(
|
||||
"toilets",
|
||||
"Open Toilet Map",
|
||||
[new Toilets()],
|
||||
12,
|
||||
51.2,
|
||||
3.2,
|
||||
|
||||
|
||||
" <h3>Open Toilet Map</h3>\n" +
|
||||
"\n" +
|
||||
"<p>Help us to create the most complete map about <i>all</i> 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.</p>"
|
||||
,
|
||||
" <p>Start by <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">creating an account\n" +
|
||||
" </a> or by " +
|
||||
" <span onclick=\"authOsm()\" class=\"activate-osm-authentication\">logging in</span>.</p>",
|
||||
"Start by clicking a pin and answering the questions"
|
||||
);
|
||||
|
||||
static bookcases = new KnownSet(
|
||||
"bookcases",
|
||||
"Open Bookcase Map",
|
||||
[new Bookcases()],
|
||||
14,
|
||||
51.2,
|
||||
3.2,
|
||||
|
||||
|
||||
" <h3>Open BoekenkastjesKaart</h3>\n" +
|
||||
"\n" +
|
||||
"<p>" +
|
||||
"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."+
|
||||
"</p>"
|
||||
,
|
||||
" <p>Begin met <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">het aanmaken van een account\n" +
|
||||
" </a> of door je " +
|
||||
" <span onclick=\"authOsm()\" class=\"activate-osm-authentication\">aan te melden</span>.</p>",
|
||||
"Klik op een boekenruilkastje om vragen te beantwoorden"
|
||||
);
|
||||
|
||||
static statues = new KnownSet(
|
||||
"statues",
|
||||
"Open Artwork Map",
|
||||
[new Artwork()],
|
||||
10,
|
||||
50.8435,
|
||||
4.3688,
|
||||
|
||||
|
||||
" <h3>Open Statue Map</h3>\n" +
|
||||
"\n" +
|
||||
"<p>" +
|
||||
"Help with creating a map of all statues all over the world!"
|
||||
|
||||
,
|
||||
" <p>Start by <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">creating an account\n" +
|
||||
" </a> or by " +
|
||||
" <span onclick=\"authOsm()\" class=\"activate-osm-authentication\">logging in</span>.</p>",
|
||||
"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,
|
||||
|
||||
|
||||
"<h3>GRB Fix tool</h3>\n" +
|
||||
"\n" +
|
||||
"Expert use only"
|
||||
|
||||
,
|
||||
"",""
|
||||
);
|
||||
|
||||
}
|
|
@ -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: "<h2>{name}</h2>",
|
||||
missing: "<h2>Naamloos park</h2>"
|
||||
}),
|
||||
|
||||
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
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,17 +22,32 @@ export class Changes {
|
|||
public readonly pendingChangesES = new UIEventSource<number>(this._pendingChanges.length);
|
||||
public readonly isSaving = new UIEventSource(false);
|
||||
private readonly _changesetComment: string;
|
||||
private readonly _centerMessage: UIEventSource<string>;
|
||||
|
||||
constructor(
|
||||
changesetComment: string,
|
||||
login: OsmConnection,
|
||||
allElements: ElementStorage,
|
||||
centerMessage: UIEventSource<string>) {
|
||||
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";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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(
|
||||
"<p>Bedankt voor je antwoord!</p>" +
|
||||
"<p>Gelieve <span class='activate-osm-authentication'>in te loggen op OpenStreetMap</span> om dit op te slaan.</p>" +
|
||||
"<p>Nog geen account? <a href=\'https://www.openstreetmap.org/user/new\' target=\'_blank\'>Registreer hier</a></p>"
|
||||
);
|
||||
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;
|
||||
|
|
|
@ -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' } },
|
||||
|
|
|
@ -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 += ' <tag k="' + key + '" v="' + this.tags[key] + '"/>\n'
|
||||
tags += ' <tag k="' + this.Escape(key) + '" v="' + this.Escape(this.tags[key]) + '"/>\n'
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +1,32 @@
|
|||
import {UIElement} from "../UIElement";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
import {UIInputElement} from "./UIInputElement";
|
||||
|
||||
|
||||
export class TextField extends UIElement {
|
||||
export class TextField<T> extends UIInputElement<T> {
|
||||
|
||||
public value = new UIEventSource("");
|
||||
public value: UIEventSource<string> = new UIEventSource<string>("");
|
||||
/**
|
||||
* Pings and has the value data
|
||||
*/
|
||||
public enterPressed = new UIEventSource<string>(undefined);
|
||||
private _placeholder: UIEventSource<string>;
|
||||
private _mapping: (string) => T;
|
||||
|
||||
constructor(placeholder : UIEventSource<string>) {
|
||||
constructor(placeholder: UIEventSource<string>,
|
||||
mapping: ((string) => T)) {
|
||||
super(placeholder);
|
||||
this._placeholder = placeholder;
|
||||
this._mapping = mapping;
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<T> {
|
||||
return this.value.map(this._mapping);
|
||||
}
|
||||
|
||||
protected InnerRender(): string {
|
||||
return "<form onSubmit='return false' class='form-text-field'>" +
|
||||
"<input type='text' placeholder='"+this._placeholder.data+"' id='text-" + this.id + "'>" +
|
||||
"<input type='text' placeholder='" + (this._placeholder.data ?? "") + "' id='text-" + this.id + "'>" +
|
||||
"</form>";
|
||||
}
|
||||
|
||||
|
@ -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 = "";
|
||||
}
|
||||
}
|
||||
|
|
8
UI/Base/UIInputElement.ts
Normal file
8
UI/Base/UIInputElement.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import {UIElement} from "../UIElement";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
|
||||
export abstract class UIInputElement<T> extends UIElement{
|
||||
|
||||
abstract GetValue() : UIEventSource<T>;
|
||||
|
||||
}
|
|
@ -1,31 +1,34 @@
|
|||
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<T> extends UIInputElement<T> {
|
||||
|
||||
public readonly SelectedElementIndex: UIEventSource<{ index: number, value: string }>
|
||||
= new UIEventSource<{ index: number, value: string }>(null);
|
||||
public readonly SelectedElementIndex: UIEventSource<number>
|
||||
= new UIEventSource<number>(null);
|
||||
|
||||
private readonly _elements: UIEventSource<{ element: UIElement, value: string }[]>
|
||||
private readonly _elements: UIEventSource<UIElement[]>
|
||||
private _selectFirstAsDefault: boolean;
|
||||
private _valueMapping: (i: number) => T;
|
||||
|
||||
constructor(elements: UIEventSource<{ element: UIElement, value: string }[]>) {
|
||||
constructor(elements: UIEventSource<UIElement[]>,
|
||||
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);
|
||||
})
|
||||
}
|
||||
|
||||
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))
|
||||
GetValue(): UIEventSource<T> {
|
||||
return this.SelectedElementIndex.map(this._valueMapping);
|
||||
}
|
||||
|
||||
|
||||
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 =
|
||||
'<input type="radio" id="' + this.IdFor(i) + '" name="radiogroup-' + this.id + '" value="' + value + '">' +
|
||||
'<label for="' + this.IdFor(i) + '">' + uielement.Render() + '</label>' +
|
||||
'<input type="radio" id="' + this.IdFor(i) + '" name="radiogroup-' + this.id + '">' +
|
||||
'<label for="' + this.IdFor(i) + '">' + el.Render() + '</label>' +
|
||||
'<br>';
|
||||
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,23 +72,25 @@ export class UIRadioButton extends UIElement {
|
|||
);
|
||||
|
||||
if (this.SelectedElementIndex.data == null) {
|
||||
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
|
||||
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));
|
||||
|
@ -98,6 +98,7 @@ export class UIRadioButton extends UIElement {
|
|||
el.checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
72
UI/Base/UIRadioButtonWithOther.ts
Normal file
72
UI/Base/UIRadioButtonWithOther.ts
Normal file
|
@ -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<T> extends UIInputElement<T> {
|
||||
private readonly _radioSelector: UIRadioButton<T>;
|
||||
private readonly _freeformText: TextField<T>;
|
||||
private readonly _value: UIEventSource<T> = new UIEventSource<T>(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<string>(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<T> {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
protected InnerRender(): string {
|
||||
return this._radioSelector.Render();
|
||||
}
|
||||
|
||||
InnerUpdate(htmlElement: HTMLElement) {
|
||||
super.InnerUpdate(htmlElement);
|
||||
this._radioSelector.Update();
|
||||
this._freeformText.Update();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<any>,
|
||||
elementsToShow: (TagMappingOptions | QuestionDefinition | UIElement)[],
|
||||
questions: QuestionDefinition[],
|
||||
title: TagRenderingOptions,
|
||||
elementsToShow: TagRenderingOptions[],
|
||||
changes: Changes,
|
||||
userDetails: UIEventSource<UserDetails>,
|
||||
preferedPictureLicense : UIEventSource<string>
|
||||
preferedPictureLicense: UIEventSource<string>
|
||||
) {
|
||||
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))
|
||||
}
|
||||
|
||||
title = title ?? new TagRenderingOptions(
|
||||
{
|
||||
mappings: [{k: new And([]), txt: ""}]
|
||||
}
|
||||
)
|
||||
|
||||
this._title = infoboxes.shift();
|
||||
this._infoElements = infoboxes;
|
||||
this._title = new TagRendering(this._tagsES, this._changes, title.options);
|
||||
|
||||
this._osmLink = new TagMapping(CommonTagMappings.osmLink, this._tagsES);
|
||||
this._wikipedialink = new TagMapping(CommonTagMappings.wikipediaLink, this._tagsES);
|
||||
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 = "<span>"+this._questions.HideOnEmpty(true).Render()+"</span>";
|
||||
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 "<div class='featureinfobox'>" +
|
||||
"<div class='featureinfoboxtitle'>" +
|
||||
"<span>" + this._title.Render() + "</span>" +
|
||||
"<span>" +
|
||||
this._title.Render() +
|
||||
"</span>" +
|
||||
this._wikipedialink.Render() +
|
||||
this._osmLink.Render() +
|
||||
"</div>" +
|
||||
|
@ -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 +
|
||||
|
||||
|
||||
"</div>" +
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ export class QuestionPicker extends UIElement {
|
|||
return "Er zijn geen vragen meer!";
|
||||
}
|
||||
|
||||
return "<div class='infobox-questions'>" +
|
||||
return "<div class='question'>" +
|
||||
highestQ.CreateHtml(this.source).Render() +
|
||||
"</div>";
|
||||
}
|
||||
|
|
25
UI/SaveButton.ts
Normal file
25
UI/SaveButton.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import {UIEventSource} from "./UIEventSource";
|
||||
import {UIElement} from "./UIElement";
|
||||
|
||||
export class SaveButton extends UIElement {
|
||||
private _value: UIEventSource<any>;
|
||||
|
||||
constructor(value: UIEventSource<any>) {
|
||||
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 "<span class='save-non-active'>Opslaan</span>"
|
||||
}
|
||||
return "<span class='save'>Opslaan</span>";
|
||||
}
|
||||
|
||||
}
|
|
@ -60,8 +60,8 @@ export class SearchAndGo extends UIElement {
|
|||
|
||||
protected InnerRender(): string {
|
||||
// "<img class='search' src='./assets/search.svg' alt='Search'> " +
|
||||
return this._goButton.Render() +
|
||||
this._searchField.Render();
|
||||
return this._searchField.Render() +
|
||||
this._goButton.Render();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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<any>) {
|
||||
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) {
|
||||
}
|
||||
|
||||
}
|
96
index.css
96
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;
|
||||
}
|
26
index.ts
26
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 = "<a href='https://github.com/pietervdvn/MapComplete' target='_blank'>Mapcomple</a> " +
|
||||
|
@ -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) => {
|
||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
39
test.ts
39
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<string>("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);
|
||||
new OsmLink(tagsES, changes).AttachTo("maindiv");
|
Loading…
Reference in a new issue