forked from MapComplete/MapComplete
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;
|
||||
}
|
||||
}
|
45
Customizations/LayerDefinition.ts
Normal file
45
Customizations/LayerDefinition.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
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 {
|
||||
|
||||
|
||||
name: string;
|
||||
newElementTags: Tag[]
|
||||
icon: string;
|
||||
minzoom: number;
|
||||
overpassFilter: TagsFilter;
|
||||
|
||||
title: TagRenderingOptions;
|
||||
elementsToShow: TagRenderingOptions[];
|
||||
|
||||
style: (tags: any) => { color: string, icon: any };
|
||||
|
||||
/**
|
||||
* If an object of the next layer is contained for this many percent in this feature, it is eaten and not shown
|
||||
*/
|
||||
maxAllowedOverlapPercentage: number = undefined;
|
||||
|
||||
|
||||
asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>, selectedElement: UIEventSource<any>,
|
||||
showOnPopup: (tags: UIEventSource<(any)>) => UIElement):
|
||||
FilteredLayer {
|
||||
return new FilteredLayer(
|
||||
this.name,
|
||||
basemap, allElements, changes,
|
||||
this.overpassFilter,
|
||||
this.maxAllowedOverlapPercentage,
|
||||
this.style,
|
||||
selectedElement,
|
||||
showOnPopup);
|
||||
|
||||
}
|
||||
|
||||
}
|
87
Customizations/Layers/Artwork.ts
Normal file
87
Customizations/Layers/Artwork.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
import {QuestionDefinition} from "../../Logic/Question";
|
||||
import {Tag} from "../../Logic/TagsFilter";
|
||||
import L from "leaflet";
|
||||
|
||||
export class Artwork extends LayerDefinition {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "artwork";
|
||||
this.newElementTags = [new Tag("tourism", "artwork")];
|
||||
this.icon = "./assets/statue.svg";
|
||||
this.overpassFilter = new Tag("tourism", "artwork");
|
||||
this.minzoom = 13;
|
||||
this.questions = [
|
||||
QuestionDefinition.radioAndTextQuestion("What kind of artwork is this?", 10, "artwork_type",
|
||||
[
|
||||
{text: "A statue", value: "statue"},
|
||||
{text: "A bust (thus a statue, but only of the head and shoulders)", value: "bust"},
|
||||
{text: "A sculpture", value: "sculpture"},
|
||||
{text: "A mural painting", value: "mural"},
|
||||
{text: "A painting", value: "painting"},
|
||||
{text: "A graffiti", value: "graffiti"},
|
||||
{text: "A relief", value: "relief"},
|
||||
{text: "An installation", value: "installation"}]),
|
||||
QuestionDefinition.textQuestion("Whom or what is depicted in this statue?", "subject", 20).addUnrequiredTag("subject:wikidata","*"),
|
||||
QuestionDefinition.textQuestion("Is there an inscription on this artwork?", "inscription", 16),
|
||||
QuestionDefinition.textQuestion("What is the name of this artwork? If there is no explicit name, skip the question", "name", 15),
|
||||
|
||||
|
||||
];
|
||||
|
||||
this.style = function (tags) {
|
||||
return {
|
||||
icon: new L.icon({
|
||||
iconUrl: "assets/statue.svg",
|
||||
iconSize: [40, 40],
|
||||
text: "hi"
|
||||
}),
|
||||
color: "#0000ff"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
this.elementsToShow = [
|
||||
|
||||
|
||||
new TagMappingOptions(
|
||||
{
|
||||
key: "name",
|
||||
template: "<h2>Artwork '{name}'</h2>",
|
||||
missing: "Artwork"
|
||||
}),
|
||||
new TagMappingOptions({
|
||||
key: "artwork_type",
|
||||
template: "This artwork is a {artwork_type}"
|
||||
}),
|
||||
new TagMappingOptions({
|
||||
key: "artist_name",
|
||||
template: "This artwork was made by {artist_name}"
|
||||
}),
|
||||
new TagMappingOptions({
|
||||
key: "subject",
|
||||
template: "This artwork depicts {subject}"
|
||||
}),
|
||||
|
||||
new TagMappingOptions({
|
||||
key: "subject:wikidata",
|
||||
template: "<a href='https://www.wikidata.org/wiki/{subject:wikidata}' target='_blank'>See more data about the subject</a>"
|
||||
}),
|
||||
|
||||
new TagMappingOptions({
|
||||
key: "website",
|
||||
template: "<a href='{website}' target='_blank'>Website of the statue</a>"
|
||||
}),
|
||||
|
||||
|
||||
|
||||
|
||||
new TagMappingOptions({key: "image", template: "<img class='popupImg' alt='image' src='{image}' />"})
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
65
Customizations/Layers/Bookcases.ts
Normal file
65
Customizations/Layers/Bookcases.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
import L from "leaflet";
|
||||
import {Tag} from "../../Logic/TagsFilter";
|
||||
import {QuestionDefinition} from "../../Logic/Question";
|
||||
import {TagRenderingOptions} from "../TagRendering";
|
||||
|
||||
export class Bookcases extends LayerDefinition {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "boekenkast";
|
||||
this.newElementTags = [new Tag( "amenity", "public_bookcase")];
|
||||
this.icon = "./assets/bookcase.svg";
|
||||
this.overpassFilter = new Tag("amenity","public_bookcase");
|
||||
this.minzoom = 13;
|
||||
|
||||
|
||||
this.questions = [
|
||||
QuestionDefinition.noNameOrNameQuestion("Wat is de naam van dit boekenruilkastje?", "Dit boekenruilkastje heeft niet echt een naam", 20),
|
||||
QuestionDefinition.textQuestion("Hoeveel boeken kunnen er in?", "capacity", 15),
|
||||
QuestionDefinition.textQuestion("Heeft dit boekenkastje een peter, meter of voogd?", "operator", 10),
|
||||
// QuestionDefinition.textQuestion("Wie kunnen we (per email) contacteren voor dit boekenruilkastje?", "email", 5),
|
||||
|
||||
|
||||
]
|
||||
;
|
||||
|
||||
this.style = function (tags) {
|
||||
return {
|
||||
icon: new L.icon({
|
||||
iconUrl: "assets/bookcase.svg",
|
||||
iconSize: [40, 40]
|
||||
}),
|
||||
color: "#0000ff"
|
||||
};
|
||||
}
|
||||
|
||||
this.elementsToShow = [
|
||||
|
||||
|
||||
|
||||
new TagMappingOptions({
|
||||
key: "name",
|
||||
template: "{name}",
|
||||
missing: "Boekenruilkastje"
|
||||
}
|
||||
),
|
||||
new TagMappingOptions({key: "capacity", template: "Plaats voor {capacity} boeken"}),
|
||||
new TagMappingOptions({key: "operator", template: "Onder de hoede van {operator}"}),
|
||||
new TagMappingOptions({
|
||||
key: "website",
|
||||
mapping: "Meer informatie beschikbaar op <a href='{website}'>{website}</a>"
|
||||
}),
|
||||
new TagMappingOptions({key: "start_date", template: "Geplaatst op {start_date}"}),
|
||||
new TagMappingOptions({key: "brand", template: "Deel van het netwerk {brand}"}),
|
||||
new TagMappingOptions({key: "ref", template: "Referentienummer {ref}"}),
|
||||
|
||||
new TagMappingOptions({key: "description", template: "Extra beschrijving: <br /> <p>{description}</p>"}),
|
||||
]
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
}
|
73
Customizations/Layers/Bos.ts
Normal file
73
Customizations/Layers/Bos.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
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 {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "bos";
|
||||
this.icon = "./assets/tree_white_background.svg";
|
||||
|
||||
this.overpassFilter = new Or([
|
||||
new Tag("natural", "wood"),
|
||||
new Tag("landuse", "forest"),
|
||||
new Tag("natural", "scrub")
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
this.newElementTags = [
|
||||
new Tag("landuse", "forest"),
|
||||
new Tag("fixme", "Toegevoegd met MapComplete, geometry nog uit te tekenen")
|
||||
];
|
||||
this.maxAllowedOverlapPercentage = 10;
|
||||
|
||||
this.minzoom = 13;
|
||||
this.style = this.generateStyleFunction();
|
||||
this.title = new NameInline("bos");
|
||||
this.elementsToShow = [
|
||||
new NameQuestion(),
|
||||
new AccessTag(),
|
||||
new OperatorTag()
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
|
||||
private generateStyleFunction() {
|
||||
const self = this;
|
||||
return function (properties: any) {
|
||||
let questionSeverity = 0;
|
||||
for (const qd of self.elementsToShow) {
|
||||
if (qd.IsQuestioning(properties)) {
|
||||
questionSeverity = Math.max(questionSeverity, qd.options.priority ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
let colormapping = {
|
||||
0: "#00bb00",
|
||||
1: "#00ff00",
|
||||
10: "#dddd00",
|
||||
20: "#ff0000"
|
||||
};
|
||||
|
||||
let colour = colormapping[questionSeverity];
|
||||
while (colour == undefined) {
|
||||
questionSeverity--;
|
||||
colour = colormapping[questionSeverity];
|
||||
}
|
||||
|
||||
return {
|
||||
color: colour,
|
||||
icon: undefined
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
}
|
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
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
62
Customizations/Layers/NatureReserves.ts
Normal file
62
Customizations/Layers/NatureReserves.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
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 {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "natuurgebied";
|
||||
this.icon = "./assets/tree_white_background.svg";
|
||||
this.overpassFilter =
|
||||
new Or([new Tag("leisure", "nature_reserve"), new Tag("boundary","protected_area")]);
|
||||
this.maxAllowedOverlapPercentage = 10;
|
||||
|
||||
this.newElementTags = [new Tag("leisure", "nature_reserve"),
|
||||
new Tag("fixme", "Toegevoegd met MapComplete, geometry nog uit te tekenen")]
|
||||
this.minzoom = 13;
|
||||
this.title = new NameInline("natuurreservaat");
|
||||
this.style = this.generateStyleFunction();
|
||||
this.elementsToShow = [
|
||||
new NameQuestion(),
|
||||
new AccessTag(),
|
||||
new OperatorTag(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
private generateStyleFunction() {
|
||||
const self = this;
|
||||
return function (properties: any) {
|
||||
let questionSeverity = 0;
|
||||
for (const qd of self.elementsToShow) {
|
||||
if (qd.IsQuestioning(properties)) {
|
||||
questionSeverity = Math.max(questionSeverity, qd.options.priority ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
let colormapping = {
|
||||
0: "#00bb00",
|
||||
1: "#00ff00",
|
||||
10: "#dddd00",
|
||||
20: "#ff0000"
|
||||
};
|
||||
|
||||
let colour = colormapping[questionSeverity];
|
||||
while (colour == undefined) {
|
||||
questionSeverity--;
|
||||
colour = colormapping[questionSeverity];
|
||||
}
|
||||
|
||||
return {
|
||||
color: colour,
|
||||
icon: undefined
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
}
|
62
Customizations/Layers/Park.ts
Normal file
62
Customizations/Layers/Park.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
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 {
|
||||
|
||||
constructor() {
|
||||
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"),
|
||||
new Tag("fixme", "Toegevoegd met MapComplete, geometry nog uit te tekenen")];
|
||||
this.maxAllowedOverlapPercentage = 25;
|
||||
|
||||
this.minzoom = 13;
|
||||
this.style = this.generateStyleFunction();
|
||||
this.title = new NameInline("park");
|
||||
this.elementsToShow = [new NameQuestion()];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private generateStyleFunction() {
|
||||
const self = this;
|
||||
return function (properties: any) {
|
||||
let questionSeverity = 0;
|
||||
for (const qd of self.elementsToShow) {
|
||||
if (qd.IsQuestioning(properties)) {
|
||||
questionSeverity = Math.max(questionSeverity, qd.options.priority ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
let colormapping = {
|
||||
0: "#00bb00",
|
||||
1: "#00ff00",
|
||||
10: "#dddd00",
|
||||
20: "#ff0000"
|
||||
};
|
||||
|
||||
let colour = colormapping[questionSeverity];
|
||||
while (colour == undefined) {
|
||||
questionSeverity--;
|
||||
colour = colormapping[questionSeverity];
|
||||
}
|
||||
|
||||
return {
|
||||
color: colour,
|
||||
icon: undefined
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
}
|
85
Customizations/Layers/Toilets.ts
Normal file
85
Customizations/Layers/Toilets.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import {LayerDefinition} from "../LayerDefinition";
|
||||
import {Quests} from "../../Quests";
|
||||
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
|
||||
import L from "leaflet";
|
||||
import {Tag} from "../../Logic/TagsFilter";
|
||||
|
||||
export class Toilets extends LayerDefinition{
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name="toilet";
|
||||
this.newElementTags = [new Tag( "amenity", "toilets")];
|
||||
this.icon = "./assets/toilets.svg";
|
||||
this.overpassFilter = new Tag("amenity","toilets");
|
||||
this.minzoom = 13;
|
||||
this.questions = [Quests.hasFee,
|
||||
Quests.toiletsWheelChairs,
|
||||
Quests.toiletsChangingTable,
|
||||
Quests.toiletsChangingTableLocation,
|
||||
Quests.toiletsPosition];
|
||||
|
||||
this.style = function(tags){
|
||||
if(tags.wheelchair == "yes"){
|
||||
|
||||
return {icon : new L.icon({
|
||||
iconUrl: "assets/wheelchair.svg",
|
||||
iconSize: [40, 40]
|
||||
})};
|
||||
}
|
||||
return {icon : new L.icon({
|
||||
iconUrl: "assets/toilets.svg",
|
||||
iconSize: [40, 40]
|
||||
})};
|
||||
}
|
||||
|
||||
this.elementsToShow = [
|
||||
new FixedUiElement("Toiletten"),
|
||||
|
||||
new TagMappingOptions({
|
||||
key: "access",
|
||||
mapping: {
|
||||
yes: "Toegankelijk",
|
||||
no: "Niet toegankelijk",
|
||||
private: "Niet toegankelijk",
|
||||
customers: "Enkel voor klanten",
|
||||
}
|
||||
}),
|
||||
|
||||
new TagMappingOptions({
|
||||
key: "fee",
|
||||
mapping: {
|
||||
yes: "Betalend",
|
||||
no: "Gratis",
|
||||
["0"]: "Gratis"
|
||||
},
|
||||
template: "Betalend, men vraagt {fee}"
|
||||
}),
|
||||
|
||||
new TagMappingOptions({
|
||||
key: "toilets:position",
|
||||
mapping: {
|
||||
seated: 'Gewone zittoiletten',
|
||||
urinal: 'Een enkele urinoir',
|
||||
urinals: 'Urinoirs',
|
||||
['urinals;seated']: "Urinoirs en gewone toiletten",
|
||||
['seated;urinals']: "Urinoirs en gewone toiletten",
|
||||
|
||||
}
|
||||
}),
|
||||
|
||||
new TagMappingOptions({
|
||||
key: "wheelchair",
|
||||
mapping: {
|
||||
yes: "Rolstoeltoegankelijk",
|
||||
no: "Niet Rolstoeltoegankelijk",
|
||||
limited: "Beperkt rolstoeltoegankelijk",
|
||||
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
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();
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue