diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts
index b0dd76a79..b5165c4ef 100644
--- a/Customizations/AllKnownLayouts.ts
+++ b/Customizations/AllKnownLayouts.ts
@@ -14,9 +14,9 @@ import {NatureReserves} from "./Layers/NatureReserves";
import {Natuurpunt} from "./Layouts/Natuurpunt";
export class AllKnownLayouts {
- public static allSets: any = AllKnownLayouts.AllLayouts();
+ public static allSets = AllKnownLayouts.AllLayouts();
- private static AllLayouts(): any {
+ private static AllLayouts(): Map {
const all = new All();
const layouts: Layout[] = [
new Groen(),
@@ -32,7 +32,7 @@ export class AllKnownLayouts {
new Statues(),
*/
];
- const allSets = {};
+ const allSets: Map = new Map();
for (const layout of layouts) {
allSets[layout.name] = layout;
all.layers = all.layers.concat(layout.layers);
diff --git a/Customizations/LayerDefinition.ts b/Customizations/LayerDefinition.ts
index 41ef0095e..ce3fbe6ab 100644
--- a/Customizations/LayerDefinition.ts
+++ b/Customizations/LayerDefinition.ts
@@ -15,7 +15,7 @@ export class LayerDefinition {
/**
* This name is shown in the 'add XXX button'
*/
- name: string;
+ name: string | UIElement;
/**
* These tags are added whenever a new point is added by the user on the map.
* This is the ideal place to add extra info, such as "fixme=added by MapComplete, geometry should be checked"
@@ -72,7 +72,15 @@ export class LayerDefinition {
*/
maxAllowedOverlapPercentage: number = undefined;
-
+ /**
+ * If true, then ways (and polygons) will be converted to a 'point' at the center instead before further processing
+ */
+ wayHandling: number = 0;
+
+ static WAYHANDLING_DEFAULT = 0;
+ static WAYHANDLING_CENTER_ONLY = 1;
+ static WAYHANDLING_CENTER_AND_WAY = 2;
+
constructor(options: {
name: string,
newElementTags: Tag[],
@@ -82,13 +90,13 @@ export class LayerDefinition {
title?: TagRenderingOptions,
elementsToShow?: TagDependantUIElementConstructor[],
maxAllowedOverlapPercentage?: number,
+ wayHandling?: number,
style?: (tags: any) => {
color: string,
icon: any
}
} = undefined) {
if (options === undefined) {
- console.log("No options!")
return;
}
this.name = options.name;
@@ -100,11 +108,12 @@ export class LayerDefinition {
this.title = options.title;
this.elementsToShow = options.elementsToShow;
this.style = options.style;
- console.log(this)
+ this.wayHandling = options.wayHandling ?? LayerDefinition.WAYHANDLING_DEFAULT;
}
- asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource, selectedElement: UIEventSource,
- showOnPopup: (tags: UIEventSource<(any)>) => UIElement):
+ asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource,
+ selectedElement: UIEventSource<{feature: any}>,
+ showOnPopup: (tags: UIEventSource<(any)>, feature: any) => UIElement):
FilteredLayer {
return new FilteredLayer(
this,
diff --git a/Customizations/Layers/BikeParkings.ts b/Customizations/Layers/BikeParkings.ts
index ccd737edb..3f07869fb 100644
--- a/Customizations/Layers/BikeParkings.ts
+++ b/Customizations/Layers/BikeParkings.ts
@@ -5,12 +5,15 @@ import * as L from "leaflet";
import FixedText from "../Questions/FixedText";
import ParkingType from "../Questions/bike/ParkingType";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
+import BikeStationOperator from "../Questions/bike/StationOperator";
+import Translations from "../../UI/i18n/Translations";
+import ParkingOperator from "../Questions/bike/ParkingOperator";
export default class BikeParkings extends LayerDefinition {
constructor() {
super();
- this.name = "bike_parking";
+ this.name = Translations.t.cyclofix.parking.name;
this.icon = "./assets/bike/parking.svg";
this.overpassFilter = new Tag("amenity", "bicycle_parking");
this.newElementTags = [
@@ -20,12 +23,13 @@ export default class BikeParkings extends LayerDefinition {
this.minzoom = 13;
this.style = this.generateStyleFunction();
- this.title = new FixedText("Fietsparking");
+ this.title = new FixedText(Translations.t.cyclofix.parking.title)
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
- new OperatorTag(),
+ //new ParkingOperator(),
new ParkingType()
];
+ this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY;
}
@@ -36,7 +40,8 @@ export default class BikeParkings extends LayerDefinition {
color: "#00bb00",
icon: L.icon({
iconUrl: self.icon,
- iconSize: [50, 50]
+ iconSize: [50, 50],
+ iconAnchor: [25,50]
})
};
};
diff --git a/Customizations/Layers/BikeShops.ts b/Customizations/Layers/BikeShops.ts
new file mode 100644
index 000000000..0cec27fe0
--- /dev/null
+++ b/Customizations/Layers/BikeShops.ts
@@ -0,0 +1,82 @@
+import { LayerDefinition } from "../LayerDefinition";
+import Translations from "../../UI/i18n/Translations";
+import {And, Tag} from "../../Logic/TagsFilter";
+import FixedText from "../Questions/FixedText";
+import { ImageCarouselWithUploadConstructor } from "../../UI/Image/ImageCarouselWithUpload";
+import * as L from "leaflet";
+import ShopRetail from "../Questions/bike/ShopRetail";
+import ShopPump from "../Questions/bike/ShopPump";
+import ShopRental from "../Questions/bike/ShopRental";
+import ShopRepair from "../Questions/bike/ShopRepair";
+import ShopDiy from "../Questions/bike/ShopDiy";
+import ShopName from "../Questions/bike/ShopName";
+import ShopSecondHand from "../Questions/bike/ShopSecondHand";
+import { TagRenderingOptions } from "../TagRendering";
+
+
+export default class BikeShops extends LayerDefinition {
+ private readonly sellsBikes = new Tag("service:bicycle:retail", "yes")
+ private readonly repairsBikes = new Tag("service:bicycle:repair", "yes")
+
+ constructor() {
+ super();
+ this.name = Translations.t.cyclofix.shop.name
+ this.icon = "./assets/bike/repair_shop.svg"
+ this.overpassFilter = new Tag("shop", "bicycle");
+ this.newElementTags = [
+ new Tag("shop", "bicycle"),
+ ]
+ this.maxAllowedOverlapPercentage = 10
+ this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
+
+ this.minzoom = 13;
+ this.style = this.generateStyleFunction();
+ this.title = new TagRenderingOptions({
+ mappings: [
+ {k: new And([new Tag("name", "*"), this.sellsBikes]), txt: Translations.t.cyclofix.shop.titleShopNamed},
+ {
+ k: new And([new Tag("name", "*"), new Tag("service:bicycle:retail", "")]),
+ txt: Translations.t.cyclofix.shop.titleShop
+ },
+ {
+ k: new And([new Tag("name", "*"), new Tag("service:bicycle:retail", "no")]),
+ txt: Translations.t.cyclofix.shop.titleRepairNamed
+ },
+ {k: this.sellsBikes, txt: Translations.t.cyclofix.shop.titleShop},
+ {k: new Tag("service:bicycle:retail", " "), txt: Translations.t.cyclofix.shop.title},
+ {k: new Tag("service:bicycle:retail", "no"), txt: Translations.t.cyclofix.shop.titleRepair},
+ ]
+ })
+
+ this.elementsToShow = [
+ new ImageCarouselWithUploadConstructor(),
+ new ShopName(),
+ new ShopRetail(),
+ new ShopRental(),
+ new ShopRepair(),
+ new ShopPump(),
+ new ShopDiy(),
+ new ShopSecondHand()
+ ]
+ }
+
+ private generateStyleFunction() {
+ const self = this;
+ return function (tags: any) {
+ let icon = "assets/bike/repair_shop.svg";
+
+ if (self.sellsBikes.matchesProperties(tags)) {
+ icon = "assets/bike/shop.svg";
+ }
+
+ return {
+ color: "#00bb00",
+ icon: L.icon({
+ iconUrl: self.icon,
+ iconSize: [50, 50],
+ iconAnchor: [25, 50]
+ })
+ }
+ }
+ }
+}
diff --git a/Customizations/Layers/BikeStations.ts b/Customizations/Layers/BikeStations.ts
index 89f79b372..2fd80c2b5 100644
--- a/Customizations/Layers/BikeStations.ts
+++ b/Customizations/Layers/BikeStations.ts
@@ -12,6 +12,7 @@ import PumpManometer from "../Questions/bike/PumpManometer";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
import PumpOperational from "../Questions/bike/PumpOperational";
import PumpValves from "../Questions/bike/PumpValves";
+import Translations from "../../UI/i18n/Translations";
export default class BikeStations extends LayerDefinition {
@@ -22,7 +23,7 @@ export default class BikeStations extends LayerDefinition {
constructor() {
super();
- this.name = "bike station or pump";
+ this.name = Translations.t.cyclofix.station.name;
this.icon = "./assets/wrench.svg";
this.overpassFilter = new And([
@@ -36,7 +37,8 @@ export default class BikeStations extends LayerDefinition {
this.minzoom = 13;
this.style = this.generateStyleFunction();
- this.title = new FixedText("Bike station");
+ this.title = new FixedText(Translations.t.cyclofix.station.title)
+ this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
@@ -50,7 +52,7 @@ export default class BikeStations extends LayerDefinition {
new PumpValves().OnlyShowIf(this.pump),
new PumpOperational().OnlyShowIf(this.pump),
- new BikeStationOperator(),
+ // new BikeStationOperator(),
// new BikeStationBrand() DISABLED
];
}
@@ -61,26 +63,26 @@ export default class BikeStations extends LayerDefinition {
const hasPump = self.pump.matchesProperties(properties)
const isOperational = self.pumpOperationalOk.matchesProperties(properties)
const hasTools = self.tools.matchesProperties(properties)
- let iconName = ""
- if (hasPump) {
- if (hasTools) {
- iconName = "repair_station_pump.svg"
+ let iconName = "repair_station.svg";
+ if (hasTools && hasPump && isOperational) {
+ iconName = "repair_station_pump.svg"
+ }else if(hasTools){
+ iconName = "repair_station.svg"
+ }else if(hasPump){
+ if (isOperational) {
+ iconName = "pump.svg"
} else {
- if (isOperational) {
- iconName = "pump.svg"
- } else {
- iconName = "pump_broken.svg"
- }
+ iconName = "broken_pump.svg"
}
- } else {
- iconName = "repair_station.svg"
}
+
const iconUrl = `./assets/bike/${iconName}`
return {
color: "#00bb00",
icon: L.icon({
iconUrl: iconUrl,
- iconSize: [50, 50]
+ iconSize: [50, 50],
+ iconAnchor: [25,50]
})
};
};
diff --git a/Customizations/Layers/Bookcases.ts b/Customizations/Layers/Bookcases.ts
index 25953f44c..7a2311c7a 100644
--- a/Customizations/Layers/Bookcases.ts
+++ b/Customizations/Layers/Bookcases.ts
@@ -1,10 +1,8 @@
import {LayerDefinition} from "../LayerDefinition";
import L from "leaflet";
-import {And, Or, Regex, Tag} from "../../Logic/TagsFilter";
-import {QuestionDefinition} from "../../Logic/Question";
+import {And, Or, Tag} from "../../Logic/TagsFilter";
import {TagRenderingOptions} from "../TagRendering";
import {NameInline} from "../Questions/NameInline";
-import {NameQuestion} from "../Questions/NameQuestion";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
export class Bookcases extends LayerDefinition {
@@ -121,7 +119,10 @@ export class Bookcases extends LayerDefinition {
key: "ref",
template: "Het referentienummer is $$$",
renderTemplate: "Gekend als {brand} {ref} "
- }
+ },
+ mappings: [
+ {k: new And([new Tag("brand",""), new Tag("nobrand","yes"), new Tag("ref", "")]), txt: "Maakt geen deel uit van een groter netwerk"}
+ ]
}).OnlyShowIf(new Tag("brand","*")),
new TagRenderingOptions({
diff --git a/Customizations/Layers/DrinkingWater.ts b/Customizations/Layers/DrinkingWater.ts
index 92e633894..1dd0c8906 100644
--- a/Customizations/Layers/DrinkingWater.ts
+++ b/Customizations/Layers/DrinkingWater.ts
@@ -10,8 +10,8 @@ export class DrinkingWater extends LayerDefinition {
constructor() {
super();
- this.name = "drinking_water";
- this.icon = "./assets/bug.svg";
+ this.name = "drinking water";
+ this.icon = "./assets/bike/drinking_water.svg";
this.overpassFilter = new Or([
new And([
@@ -24,6 +24,7 @@ export class DrinkingWater extends LayerDefinition {
new Tag("amenity", "drinking_water"),
];
this.maxAllowedOverlapPercentage = 10;
+ this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
this.minzoom = 13;
this.style = this.generateStyleFunction();
@@ -52,7 +53,8 @@ export class DrinkingWater extends LayerDefinition {
color: "#00bb00",
icon: new L.icon({
iconUrl: self.icon,
- iconSize: [40, 40]
+ iconSize: [50, 50],
+ iconAnchor: [25,50]
})
};
};
diff --git a/Customizations/Layers/NatureReserves.ts b/Customizations/Layers/NatureReserves.ts
index 6adde3134..a0a1d94dd 100644
--- a/Customizations/Layers/NatureReserves.ts
+++ b/Customizations/Layers/NatureReserves.ts
@@ -25,6 +25,13 @@ export class NatureReserves extends LayerDefinition {
this.style = this.generateStyleFunction();
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
+ new TagRenderingOptions({
+ freeform: {
+ key: "_surface",
+ renderTemplate: "{_surface}m²",
+ template: "$$$"
+ }
+ }),
new NameQuestion(),
new AccessTag(),
new OperatorTag(),
diff --git a/Customizations/Layout.ts b/Customizations/Layout.ts
index 7c7292790..eb13be3da 100644
--- a/Customizations/Layout.ts
+++ b/Customizations/Layout.ts
@@ -1,20 +1,30 @@
import {LayerDefinition} from "./LayerDefinition";
+import {UIElement} from "../UI/UIElement";
+import {FixedUiElement} from "../UI/Base/FixedUiElement";
+import Translation from "../UI/i18n/Translation";
+import Translations from "../UI/i18n/Translations";
+import Locale from "../UI/i18n/Locale";
+import {VariableUiElement} from "../UI/Base/VariableUIElement";
+import {OsmConnection, UserDetails} from "../Logic/OsmConnection";
+import {UIEventSource} from "../UI/UIEventSource";
/**
* 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 title: UIElement;
public layers: LayerDefinition[];
- public welcomeMessage: string;
- public gettingStartedPlzLogin: string;
- public welcomeBackMessage: string;
+ public welcomeMessage: UIElement;
+ public gettingStartedPlzLogin: UIElement;
+ public welcomeBackMessage: UIElement;
+ public welcomeTail: UIElement;
public startzoom: number;
+ public supportedLanguages: string[];
public startLon: number;
public startLat: number;
- public welcomeTail: string;
public locationContains: string[];
@@ -33,26 +43,79 @@ export class Layout {
*/
constructor(
name: string,
- title: string,
+ supportedLanguages: string[],
+ title: UIElement | string,
layers: LayerDefinition[],
startzoom: number,
startLat: number,
startLon: number,
- welcomeMessage: string,
- gettingStartedPlzLogin: string = "Please login to get started",
- welcomeBackMessage: string = "You are logged in. Welcome back!",
- welcomeTail: string = ""
+ welcomeMessage: UIElement | string,
+ gettingStartedPlzLogin: UIElement | string = Translations.t.general.getStarted,
+ welcomeBackMessage: UIElement | string = Translations.t.general.welcomeBack,
+ welcomeTail: UIElement | string = ""
) {
- this.title = title;
+ this.supportedLanguages = supportedLanguages;
+ this.title = typeof (title) === 'string' ? new FixedUiElement(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;
+ this.welcomeMessage = Translations.W(welcomeMessage)
+ this.gettingStartedPlzLogin = Translations.W(gettingStartedPlzLogin);
+ this.welcomeBackMessage = Translations.W(welcomeBackMessage);
+ this.welcomeTail = Translations.W(welcomeTail);
+ }
+
+
+}
+
+export class WelcomeMessage extends UIElement {
+ private readonly layout: Layout;
+ private readonly userDetails: UIEventSource;
+ private osmConnection: OsmConnection;
+
+ private readonly description: UIElement;
+ private readonly plzLogIn: UIElement;
+ private readonly welcomeBack: UIElement;
+ private readonly tail: UIElement;
+
+
+ constructor(layout: Layout, osmConnection: OsmConnection) {
+ super(osmConnection.userDetails);
+ this.ListenTo(Locale.language);
+ this.osmConnection = osmConnection;
+ this.layout = layout;
+ this.userDetails = osmConnection.userDetails;
+
+ this.description = layout.welcomeMessage;
+ console.log(" >>>>",this.description, "DESCR ")
+ this.plzLogIn = layout.gettingStartedPlzLogin;
+ this.welcomeBack = layout.welcomeBackMessage;
+ this.tail = layout.welcomeTail;
+ }
+
+ InnerRender(): string {
+ return "" +
+ this.description.Render() +
+ " "+
+ (this.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render() +
+ " "+
+ this.tail.Render() +
+ "
"
+
+ ;
+ /*
+ return new VariableUiElement(
+ this.userDetails.map((userdetails) => {
+ }),
+ function () {
+
+ }).ListenTo(Locale.language);*/
+ }
+
+ protected InnerUpdate(htmlElement: HTMLElement) {
+ this.osmConnection.registerActivateOsmAUthenticationClass()
}
}
diff --git a/Customizations/Layouts/All.ts b/Customizations/Layouts/All.ts
index a415c2ea0..54065b4b6 100644
--- a/Customizations/Layouts/All.ts
+++ b/Customizations/Layouts/All.ts
@@ -4,6 +4,7 @@ export class All extends Layout{
constructor() {
super(
"all",
+ ["en"],
"All quest layers",
[],
15,
diff --git a/Customizations/Layouts/Bookcases.ts b/Customizations/Layouts/Bookcases.ts
index 833150b30..d833d6edd 100644
--- a/Customizations/Layouts/Bookcases.ts
+++ b/Customizations/Layouts/Bookcases.ts
@@ -4,6 +4,7 @@ import * as Layer from "../Layers/Bookcases";
export class Bookcases extends Layout{
constructor() {
super( "bookcases",
+ ["nl"],
"Open Bookcase Map",
[new Layer.Bookcases()],
14,
diff --git a/Customizations/Layouts/Cyclofix.ts b/Customizations/Layouts/Cyclofix.ts
index 548981ba4..82d56dde8 100644
--- a/Customizations/Layouts/Cyclofix.ts
+++ b/Customizations/Layouts/Cyclofix.ts
@@ -1,30 +1,30 @@
import {Layout} from "../Layout";
import BikeParkings from "../Layers/BikeParkings";
import BikeServices from "../Layers/BikeStations";
-import {GhostBike} from "../Layers/GhostBike";
-import {DrinkingWater, DrinkingWaterLayer} from "../Layers/DrinkingWater";
+import BikeShops from "../Layers/BikeShops";
+import Translations from "../../UI/i18n/Translations";
+import {DrinkingWater} from "../Layers/DrinkingWater";
+import Combine from "../../UI/Base/Combine";
export default class Cyclofix extends Layout {
constructor() {
super(
"pomp",
- "Cyclofix bicycle infrastructure",
- [new GhostBike(), new BikeServices(), new BikeParkings(), new DrinkingWater()],
+ ["en", "nl", "fr"],
+ Translations.t.cyclofix.title,
+ [new BikeServices(), new BikeShops(), new DrinkingWater(), new BikeParkings()],
16,
50.8465573,
4.3516970,
-
-
- "Cyclofix bicycle infrastructure \n" +
- "\n" +
- "EN> On this map we want to collect data about the whereabouts of bicycle pumps and public racks in Brussels." +
- "As a result, cyclists will be able to quickly find the nearest infrastructure for their needs.
" +
- "NL> Op deze kaart willen we gegevens verzamelen over de locatie van fietspompen en openbare stelplaatsen in Brussel." +
- "Hierdoor kunnen fietsers snel de dichtstbijzijnde infrastructuur vinden die voldoet aan hun behoeften.
" +
- "FR> Sur cette carte, nous voulons collecter des données sur la localisation des pompes à vélo et des supports publics à Bruxelles." +
- "Les cyclistes pourront ainsi trouver rapidement l'infrastructure la plus proche de leurs besoins.
"
- ,
- "", "");
+ /* Translations.t.cyclofix.title/*/
+ new Combine([
+ "",
+ Translations.t.cyclofix.title,
+ " ",
+ Translations.t.cyclofix.description,
+ "
"
+ ])//*/
+ );
}
}
diff --git a/Customizations/Layouts/GRB.ts b/Customizations/Layouts/GRB.ts
index 93514a1de..21ba2ab4b 100644
--- a/Customizations/Layouts/GRB.ts
+++ b/Customizations/Layouts/GRB.ts
@@ -4,6 +4,7 @@ import {GrbToFix} from "../Layers/GrbToFix";
export class GRB extends Layout {
constructor() {
super("grb",
+ ["en"],
"Grb import fix tool",
[new GrbToFix()],
15,
diff --git a/Customizations/Layouts/Groen.ts b/Customizations/Layouts/Groen.ts
index 7f3d0f934..991a61893 100644
--- a/Customizations/Layouts/Groen.ts
+++ b/Customizations/Layouts/Groen.ts
@@ -7,6 +7,7 @@ export class Groen extends Layout {
constructor() {
super("buurtnatuur",
+ ["nl"],
"Buurtnatuur",
[new NatureReserves(), new Park(), new Bos()],
10,
diff --git a/Customizations/Layouts/MetaMap.ts b/Customizations/Layouts/MetaMap.ts
index 8302f985d..c174bc6db 100644
--- a/Customizations/Layouts/MetaMap.ts
+++ b/Customizations/Layouts/MetaMap.ts
@@ -5,6 +5,7 @@ import {Map} from "../Layers/Map";
export class MetaMap extends Layout{
constructor() {
super( "metamap",
+ ["en"],
"Open Map Map",
[new Map()],
1,
diff --git a/Customizations/Layouts/Natuurpunt.ts b/Customizations/Layouts/Natuurpunt.ts
index 8216fcb7e..78c0c7a0c 100644
--- a/Customizations/Layouts/Natuurpunt.ts
+++ b/Customizations/Layouts/Natuurpunt.ts
@@ -7,6 +7,7 @@ export class Natuurpunt extends Layout{
constructor() {
super(
"natuurpunt",
+ ["nl"],
"De natuur in",
[new Birdhide(), new InformationBoard(), new NatureReserves(true)],
12,
diff --git a/Customizations/Layouts/Statues.ts b/Customizations/Layouts/Statues.ts
index 76a46dd26..d355fa4cf 100644
--- a/Customizations/Layouts/Statues.ts
+++ b/Customizations/Layouts/Statues.ts
@@ -5,6 +5,7 @@ export class Statues extends Layout{
constructor() {
super( "statues",
"Open Artwork Map",
+ ["en"],
[new Artwork()],
10,
50.8435,
diff --git a/Customizations/Layouts/StreetWidth.ts b/Customizations/Layouts/StreetWidth.ts
index f93e8a1ad..4d72b6e3a 100644
--- a/Customizations/Layouts/StreetWidth.ts
+++ b/Customizations/Layouts/StreetWidth.ts
@@ -7,6 +7,7 @@ export class StreetWidth extends Layout{
constructor() {
super( "width",
+ ["nl"],
"Straatbreedtes in Brugge",
[new Widths(
2,
diff --git a/Customizations/Layouts/Toilets.ts b/Customizations/Layouts/Toilets.ts
index c6c585e90..1598c0f5a 100644
--- a/Customizations/Layouts/Toilets.ts
+++ b/Customizations/Layouts/Toilets.ts
@@ -4,6 +4,7 @@ import * as Layer from "../Layers/Toilets";
export class Toilets extends Layout{
constructor() {
super( "toilets",
+ ["en"],
"Open Toilet Map",
[new Layer.Toilets()],
12,
diff --git a/Customizations/Layouts/WalkByBrussels.ts b/Customizations/Layouts/WalkByBrussels.ts
index ca8fdc4b4..499b7740f 100644
--- a/Customizations/Layouts/WalkByBrussels.ts
+++ b/Customizations/Layouts/WalkByBrussels.ts
@@ -6,6 +6,7 @@ import { Park } from "../Layers/Park";
export class WalkByBrussels extends Layout {
constructor() {
super("walkbybrussels",
+ ["en","fr","nl"],
"Drinking Water Spots",
[new DrinkingWater(), new Park(), new NatureReserves()],
10,
diff --git a/Customizations/Questions/FixedText.ts b/Customizations/Questions/FixedText.ts
index 178fb53f4..d3f51001d 100644
--- a/Customizations/Questions/FixedText.ts
+++ b/Customizations/Questions/FixedText.ts
@@ -1,7 +1,8 @@
import { TagRenderingOptions } from "../TagRendering";
+import {UIElement} from "../../UI/UIElement";
export default class FixedText extends TagRenderingOptions {
- constructor(category: string) {
+ constructor(category: string | UIElement) {
super({
mappings: [
{
diff --git a/Customizations/Questions/NameInline.ts b/Customizations/Questions/NameInline.ts
index 83458a5b7..5c9d4e69a 100644
--- a/Customizations/Questions/NameInline.ts
+++ b/Customizations/Questions/NameInline.ts
@@ -1,5 +1,6 @@
import {TagRenderingOptions} from "../TagRendering";
import {And, Tag} from "../../Logic/TagsFilter";
+import {UIElement} from "../../UI/UIElement";
export class NameInline extends TagRenderingOptions{
@@ -8,7 +9,7 @@ export class NameInline extends TagRenderingOptions{
return string.charAt(0).toUpperCase() + string.slice(1);
}
- constructor(category: string) {
+ constructor(category: string ) {
super({
question: "",
diff --git a/Customizations/Questions/bike/ParkingOperator.ts b/Customizations/Questions/bike/ParkingOperator.ts
new file mode 100644
index 000000000..251bcb502
--- /dev/null
+++ b/Customizations/Questions/bike/ParkingOperator.ts
@@ -0,0 +1,27 @@
+import {TagRenderingOptions} from "../../TagRendering";
+import {Tag, And} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
+
+
+export default class ParkingOperator extends TagRenderingOptions {
+ constructor() {
+ const to = Translations.t.cyclofix.parking.operator
+ super({
+ priority: 15,
+ question: to.question.Render(),
+ freeform: {
+ key: "operator",
+ template: to.template,
+ renderTemplate: to.render,
+ placeholder: Translations.t.cyclofix.freeFormPlaceholder
+ },
+ mappings: [
+ {k: new Tag("operator", "KU Leuven"), txt: "KU Leuven"},
+ {k: new Tag("operator", "Stad Halle"), txt: "Stad Halle"},
+ {k: new Tag("operator", "Saint Gilles - Sint Gillis"), txt: "Saint Gilles - Sint Gillis"},
+ {k: new Tag("operator", "Jette"), txt: "Jette"},
+ {k: new And([new Tag("operator", ""), new Tag("operator:type", "private")]), txt: to.private.Render()}
+ ]
+ });
+ }
+}
diff --git a/Customizations/Questions/bike/ParkingType.ts b/Customizations/Questions/bike/ParkingType.ts
index 4b5074ff1..fa8ab508a 100644
--- a/Customizations/Questions/bike/ParkingType.ts
+++ b/Customizations/Questions/bike/ParkingType.ts
@@ -1,38 +1,61 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
+import Combine from "../../../UI/Base/Combine";
+class ParkingTypeHelper {
+ static GenerateMappings() {
+ const images = {
+ stands: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dc/Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg/100px-Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg",
+ wall_loops: "https://wiki.openstreetmap.org/w/images/thumb/c/c2/Bike-parking-wheelbender.jpg/100px-Bike-parking-wheelbender.jpg",
+ handlebar_holder: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Bicycle_parking_handlebar_holder.jpg/100px-Bicycle_parking_handlebar_holder.jpg",
+ shed: "https://wiki.openstreetmap.org/w/images/thumb/b/b2/Bike-shelter.jpg/100px-Bike-shelter.jpg",
+ rack: "https://wiki.openstreetmap.org/w/images/thumb/4/41/Triton_Bike_Rack.png/100px-Triton_Bike_Rack.png",
+ "two-tier": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG/100px-Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG"
+ };
+
+
+ const toImg = (url) => ` `
+ const mappings = [];
+ const to = Translations.t.cyclofix.parking.type
+
+ for (const imagesKey in images) {
+ const mapping =
+ {
+ k: new Tag("bicycle_parking", imagesKey),
+ txt: new Combine([
+ to[imagesKey],
+ to.eg,
+ toImg(images[imagesKey])
+ ])
+ };
+
+ mappings.push(mapping);
+
+ }
+
+ return mappings;
+ }
+}
export default class ParkingType extends TagRenderingOptions {
- private static images = {
- stands: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dc/Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg/100px-Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg",
- wall_loops: "https://wiki.openstreetmap.org/w/images/thumb/c/c2/Bike-parking-wheelbender.jpg/100px-Bike-parking-wheelbender.jpg",
- handlebar_holder: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Bicycle_parking_handlebar_holder.jpg/100px-Bicycle_parking_handlebar_holder.jpg",
- shed: "https://wiki.openstreetmap.org/w/images/thumb/b/b2/Bike-shelter.jpg/100px-Bike-shelter.jpg",
- "two-tier": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG/100px-Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG"
- }
-
- private static toImgTxt(url: string) {
- return ` `
- }
-
constructor() {
+
+ const to = Translations.t.cyclofix.parking.type
+
+
super({
priority: 5,
- question: "Van welk type is deze fietsenparking?",
+ question: to.question,
freeform: {
key: "bicycle_parking",
extraTags: new Tag("fixme", "Freeform bicycle_parking= tag used: possibly a wrong value"),
- template: "Iets anders: $$$",
- renderTemplate: "Dit is een fietsenparking van het type: {bicycle_parking}",
- placeholder: "Specifieer"
+ template: to.template.txt,
+ renderTemplate: to.render.txt,
+ placeholder: Translations.t.cyclofix.freeFormPlaceholder,
},
- mappings: [
- {k: new Tag("bicycle_parking", "stands"), txt: ParkingType.toImgTxt(ParkingType.images.stands)},
- {k: new Tag("bicycle_parking", "wall_loops"), txt: ParkingType.toImgTxt(ParkingType.images.wall_loops)},
- {k: new Tag("bicycle_parking", "handlebar_holder"), txt: ParkingType.toImgTxt(ParkingType.images.handlebar_holder)},
- {k: new Tag("bicycle_parking", "shed"), txt: ParkingType.toImgTxt(ParkingType.images.shed)},
- {k: new Tag("bicycle_parking", "two-tier"), txt: ParkingType.toImgTxt(ParkingType.images["two-tier"])}
- ]
+ mappings: ParkingTypeHelper.GenerateMappings()
+
});
}
}
diff --git a/Customizations/Questions/bike/PumpManometer.ts b/Customizations/Questions/bike/PumpManometer.ts
index bc97a032b..1ae416efd 100644
--- a/Customizations/Questions/bike/PumpManometer.ts
+++ b/Customizations/Questions/bike/PumpManometer.ts
@@ -1,16 +1,18 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
export default class PumpManometer extends TagRenderingOptions {
constructor() {
+ const to = Translations.t.cyclofix.station.manometer
super({
- question: "Does the pump have a pressure indicator or manometer?",
+ question: to.question,
mappings: [
- {k: new Tag("manometer", "yes"), txt: "Yes, there is a manometer"},
- {k: new Tag("manometer","broken"), txt: "Yes, but it is broken"},
- {k: new Tag("manometer", "yes"), txt: "No"}
+ {k: new Tag("manometer", "yes"), txt: to.yes},
+ {k: new Tag("manometer", "no"), txt: to.no},
+ {k: new Tag("manometer", "broken"), txt: to.broken}
]
});
- }
+ }
}
diff --git a/Customizations/Questions/bike/PumpManual.ts b/Customizations/Questions/bike/PumpManual.ts
index 18b79e68e..44aa4ee7d 100644
--- a/Customizations/Questions/bike/PumpManual.ts
+++ b/Customizations/Questions/bike/PumpManual.ts
@@ -1,15 +1,17 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
export default class PumpManual extends TagRenderingOptions {
constructor() {
+ const to = Translations.t.cyclofix.station.electric
super({
priority: 5,
- question: "Is this an electric bike pump?",
+ question: to.question,
mappings: [
- {k: new Tag("manual", "yes"), txt: "Manual pump"},
- {k: new Tag("manual", "no"), txt: "Electric pump"}
+ {k: new Tag("manual", "yes"), txt: to.manual},
+ {k: new Tag("manual", "no"), txt: to.electric}
]
});
}
diff --git a/Customizations/Questions/bike/PumpOperational.ts b/Customizations/Questions/bike/PumpOperational.ts
index d7abe0f16..ac84f0c80 100644
--- a/Customizations/Questions/bike/PumpOperational.ts
+++ b/Customizations/Questions/bike/PumpOperational.ts
@@ -1,14 +1,16 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
export default class PumpOperational extends TagRenderingOptions {
constructor() {
+ const to = Translations.t.cyclofix.station.operational
super({
- question: "Is the bicycle pump still operational?",
+ question: to.question,
mappings: [
- {k: new Tag("service:bicycle:pump:operational_status","broken"), txt: "This pump is broken"},
- {k: new Tag("service:bicycle:pump:operational_status",""), txt: "This pump is operational"}
+ {k: new Tag("service:bicycle:pump:operational_status","broken"), txt: to.broken},
+ {k: new Tag("service:bicycle:pump:operational_status",""), txt: to.operational}
]
});
}
diff --git a/Customizations/Questions/bike/PumpValves.ts b/Customizations/Questions/bike/PumpValves.ts
index ae9ebb4bf..51247d052 100644
--- a/Customizations/Questions/bike/PumpValves.ts
+++ b/Customizations/Questions/bike/PumpValves.ts
@@ -1,24 +1,27 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
export default class PumpValves extends TagRenderingOptions{
constructor() {
+ const to = Translations.t.cyclofix.station.valves
super({
- question: "What valves are supported?",
+ question: to.question,
mappings: [
{
k: new Tag("valves", " sclaverand;schrader;dunlop"),
- txt: "There is a default head, so Presta, Dunlop and Auto"
+ txt: to.default
},
- {k: new Tag("valves", "dunlop"), txt: "Only dunlop"},
- {k: new Tag("valves", "sclaverand"), txt: "Only Sclaverand (also known as Dunlop)"},
- {k: new Tag("valves", "auto"), txt: "Only auto"},
+ {k: new Tag("valves", "dunlop"), txt: to.dunlop},
+ {k: new Tag("valves", "sclaverand"), txt: to.sclaverand},
+ {k: new Tag("valves", "auto"), txt: to.auto},
],
freeform: {
+ extraTags: new Tag("fixme", "Freeform valves= tag used: possibly a wrong value"),
key: "valves",
- template: "Supported valves are $$$",
- renderTemplate: "Supported valves are {valves}"
+ template: to.template,
+ renderTemplate: to.render
}
});
}
diff --git a/Customizations/Questions/bike/ShopDiy.ts b/Customizations/Questions/bike/ShopDiy.ts
new file mode 100644
index 000000000..48217314a
--- /dev/null
+++ b/Customizations/Questions/bike/ShopDiy.ts
@@ -0,0 +1,19 @@
+import {TagRenderingOptions} from "../../TagRendering";
+import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
+
+
+export default class ShopPump extends TagRenderingOptions {
+ constructor() {
+ const key = 'service:bicycle:diy'
+ const to = Translations.t.cyclofix.shop.diy
+ super({
+ priority: 5,
+ question: to.question,
+ mappings: [
+ {k: new Tag(key, "yes"), txt: to.yes},
+ {k: new Tag(key, "no"), txt: to.no},
+ ]
+ });
+ }
+}
diff --git a/Customizations/Questions/bike/ShopName.ts b/Customizations/Questions/bike/ShopName.ts
new file mode 100644
index 000000000..f270268ab
--- /dev/null
+++ b/Customizations/Questions/bike/ShopName.ts
@@ -0,0 +1,18 @@
+import {TagRenderingOptions} from "../../TagRendering";
+import Translations from "../../../UI/i18n/Translations";
+
+
+export default class ShopPump extends TagRenderingOptions {
+ constructor() {
+ const to = Translations.t.cyclofix.shop.qName
+ super({
+ priority: 5,
+ question: to.question,
+ freeform: {
+ key: "name",
+ renderTemplate: to.render,
+ template: to.template
+ }
+ })
+ }
+}
diff --git a/Customizations/Questions/bike/ShopPump.ts b/Customizations/Questions/bike/ShopPump.ts
new file mode 100644
index 000000000..6ed14dabb
--- /dev/null
+++ b/Customizations/Questions/bike/ShopPump.ts
@@ -0,0 +1,19 @@
+import {TagRenderingOptions} from "../../TagRendering";
+import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
+
+
+export default class ShopPump extends TagRenderingOptions {
+ constructor() {
+ const key = 'service:bicycle:pump'
+ const to = Translations.t.cyclofix.shop.pump
+ super({
+ priority: 5,
+ question: to.question,
+ mappings: [
+ {k: new Tag(key, "yes"), txt: to.yes},
+ {k: new Tag(key, "no"), txt: to.no},
+ ]
+ });
+ }
+}
diff --git a/Customizations/Questions/bike/ShopRental.ts b/Customizations/Questions/bike/ShopRental.ts
new file mode 100644
index 000000000..eac267774
--- /dev/null
+++ b/Customizations/Questions/bike/ShopRental.ts
@@ -0,0 +1,19 @@
+import {TagRenderingOptions} from "../../TagRendering";
+import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
+
+
+export default class ShopRental extends TagRenderingOptions {
+ constructor() {
+ const key = 'service:bicycle:rental'
+ const to = Translations.t.cyclofix.shop.rental
+ super({
+ priority: 5,
+ question: to.question,
+ mappings: [
+ {k: new Tag(key, "yes"), txt: to.yes},
+ {k: new Tag(key, "no"), txt: to.no},
+ ]
+ });
+ }
+}
diff --git a/Customizations/Questions/bike/ShopRepair.ts b/Customizations/Questions/bike/ShopRepair.ts
new file mode 100644
index 000000000..2a1a3759d
--- /dev/null
+++ b/Customizations/Questions/bike/ShopRepair.ts
@@ -0,0 +1,21 @@
+import {TagRenderingOptions} from "../../TagRendering";
+import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
+
+
+export default class ShopRepair extends TagRenderingOptions {
+ constructor() {
+ const key = 'service:bicycle:repair'
+ const to = Translations.t.cyclofix.shop.repair
+ super({
+ priority: 5,
+ question: to.question,
+ mappings: [
+ {k: new Tag(key, "yes"), txt: to.yes},
+ {k: new Tag(key, "only_sold"), txt: to.sold},
+ {k: new Tag(key, "brand"), txt: to.brand},
+ {k: new Tag(key, "no"), txt: to.no},
+ ]
+ });
+ }
+}
diff --git a/Customizations/Questions/bike/ShopRetail.ts b/Customizations/Questions/bike/ShopRetail.ts
new file mode 100644
index 000000000..3ad3ea396
--- /dev/null
+++ b/Customizations/Questions/bike/ShopRetail.ts
@@ -0,0 +1,19 @@
+import {TagRenderingOptions} from "../../TagRendering";
+import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
+
+
+export default class ShopRetail extends TagRenderingOptions {
+ constructor() {
+ const key = 'service:bicycle:retail'
+ const to = Translations.t.cyclofix.shop.retail
+ super({
+ priority: 5,
+ question: to.question,
+ mappings: [
+ {k: new Tag(key, "yes"), txt: to.yes},
+ {k: new Tag(key, "no"), txt: to.no},
+ ]
+ });
+ }
+}
diff --git a/Customizations/Questions/bike/ShopSecondHand.ts b/Customizations/Questions/bike/ShopSecondHand.ts
new file mode 100644
index 000000000..b95da4929
--- /dev/null
+++ b/Customizations/Questions/bike/ShopSecondHand.ts
@@ -0,0 +1,20 @@
+import {TagRenderingOptions} from "../../TagRendering";
+import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
+
+
+export default class ShopPump extends TagRenderingOptions {
+ constructor() {
+ const key = 'service:bicycle:second_hand'
+ const to = Translations.t.cyclofix.shop.secondHand
+ super({
+ priority: 5,
+ question: to.question,
+ mappings: [
+ {k: new Tag(key, "yes"), txt: to.yes},
+ {k: new Tag(key, "no"), txt: to.no},
+ {k: new Tag(key, "only"), txt: to.only},
+ ]
+ });
+ }
+}
diff --git a/Customizations/Questions/bike/StationBrand.ts b/Customizations/Questions/bike/StationBrand.ts
index 61dbc44db..b763bf650 100644
--- a/Customizations/Questions/bike/StationBrand.ts
+++ b/Customizations/Questions/bike/StationBrand.ts
@@ -4,6 +4,8 @@ import {Tag} from "../../../Logic/TagsFilter";
/**
* Currently not used in Cyclofix because it's a little vague
+ *
+ * TODO: Translations
*/
export default class BikeStationBrand extends TagRenderingOptions {
private static options = {
diff --git a/Customizations/Questions/bike/StationChain.ts b/Customizations/Questions/bike/StationChain.ts
index eb5c36ade..7930d3bc6 100644
--- a/Customizations/Questions/bike/StationChain.ts
+++ b/Customizations/Questions/bike/StationChain.ts
@@ -1,15 +1,17 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
export default class StationChain extends TagRenderingOptions {
constructor() {
+ const to = Translations.t.cyclofix.station.chain
super({
priority: 5,
- question: "Does this bike station have a special tool to repair your bike chain?",
+ question: to.question,
mappings: [
- {k: new Tag("service:bicycle:chain_tool", "yes"), txt: "There is a chain tool."},
- {k: new Tag("service:bicycle:chain_tool", "no"), txt: "There is no chain tool."},
+ {k: new Tag("service:bicycle:chain_tool", "yes"), txt: to.yes},
+ {k: new Tag("service:bicycle:chain_tool", "no"), txt: to.no},
]
});
}
diff --git a/Customizations/Questions/bike/StationOperator.ts b/Customizations/Questions/bike/StationOperator.ts
index 001f920cf..5d09434e0 100644
--- a/Customizations/Questions/bike/StationOperator.ts
+++ b/Customizations/Questions/bike/StationOperator.ts
@@ -1,25 +1,27 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
export default class BikeStationOperator extends TagRenderingOptions {
constructor() {
+ const to = Translations.t.cyclofix.station.operator
super({
priority: 15,
- question: "Who operates this bike station (name of university, shop, city...)?",
- freeform: {
- key: "operator",
- template: "This bike station is operated by $$$",
- renderTemplate: "This bike station is operated by {operator}",
- placeholder: "organisatie"
- },
+ question: to.question,
mappings: [
{k: new Tag("operator", "KU Leuven"), txt: "KU Leuven"},
{k: new Tag("operator", "Stad Halle"), txt: "Stad Halle"},
{k: new Tag("operator", "Saint Gilles - Sint Gillis"), txt: "Saint Gilles - Sint Gillis"},
{k: new Tag("operator", "Jette"), txt: "Jette"},
- {k: new Tag("operator", "private"), txt: "Beheer door een privépersoon"}
- ]
+ {k: new Tag("operator", "private"), txt: to.private}
+ ],
+ freeform: {
+ key: "operator",
+ template: to.template,
+ renderTemplate: to.render,
+ placeholder: "organisatie"
+ }
});
}
}
diff --git a/Customizations/Questions/bike/StationPumpTools.ts b/Customizations/Questions/bike/StationPumpTools.ts
index be81c8b27..5442eba51 100644
--- a/Customizations/Questions/bike/StationPumpTools.ts
+++ b/Customizations/Questions/bike/StationPumpTools.ts
@@ -1,16 +1,18 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag, And} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
export default class BikeStationPumpTools extends TagRenderingOptions {
constructor() {
+ const to = Translations.t.cyclofix.station.services
super({
priority: 15,
- question: "Which services are available at this bike station?",
+ question: to.question,
mappings: [
- {k: new And([new Tag("service:bicycle:tools", "no"), new Tag("service:bicycle:pump", "yes")]), txt: "There is only a pump available."},
- {k: new And([new Tag("service:bicycle:tools", "yes"), new Tag("service:bicycle:pump", "no")]), txt: "There are only tools (screwdrivers, pliers...) available."},
- {k: new And([new Tag("service:bicycle:tools", "yes"), new Tag("service:bicycle:pump", "yes")]), txt: "There are both tools and a pump available."}
+ {k: new And([new Tag("service:bicycle:tools", "no"), new Tag("service:bicycle:pump", "yes")]), txt: to.pump},
+ {k: new And([new Tag("service:bicycle:tools", "yes"), new Tag("service:bicycle:pump", "no")]), txt: to.tools},
+ {k: new And([new Tag("service:bicycle:tools", "yes"), new Tag("service:bicycle:pump", "yes")]), txt: to.both}
]
});
}
diff --git a/Customizations/Questions/bike/StationStand.ts b/Customizations/Questions/bike/StationStand.ts
index 0b6fce196..be6ac4a1e 100644
--- a/Customizations/Questions/bike/StationStand.ts
+++ b/Customizations/Questions/bike/StationStand.ts
@@ -1,14 +1,16 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
+import Translations from "../../../UI/i18n/Translations";
export default class BikeStationStand extends TagRenderingOptions {
constructor() {
+ const to = Translations
super({
priority: 10,
question: "Does this bike station have a hook to suspend your bike with or a stand to elevate it?",
mappings: [
- {k: new Tag("service:bicycle:stand", "yes"), txt: "There is a hook or stand."},
+ {k: new Tag("service:bicycle:stand", "yes"), txt: "There is a hook or stand"},
{k: new Tag("service:bicycle:stand", "no"), txt: "There is no hook or stand"},
]
});
diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts
index ea8ca3290..a426fa326 100644
--- a/Customizations/TagRendering.ts
+++ b/Customizations/TagRendering.ts
@@ -1,17 +1,20 @@
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";
import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
import {OnlyShowIfConstructor} from "./OnlyShowIf";
import {UserDetails} from "../Logic/OsmConnection";
+import {TextField} from "../UI/Input/TextField";
+import {InputElement} from "../UI/Input/InputElement";
+import {InputElementWrapper} from "../UI/Input/InputElementWrapper";
+import {FixedInputElement} from "../UI/Input/FixedInputElement";
+import {RadioButton} from "../UI/Input/RadioButton";
+import Translations from "../UI/i18n/Translations";
+import Locale from "../UI/i18n/Locale";
export class TagRenderingOptions implements TagDependantUIElementConstructor {
@@ -20,8 +23,17 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
*/
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 }[]
+ priority?: number;
+ question?: string | UIElement;
+ freeform?: {
+ key: string;
+ tagsPreprocessor?: (tags: any) => any;
+ template: string | UIElement;
+ renderTemplate: string | UIElement;
+ placeholder?: string | UIElement;
+ extraTags?: TagsFilter
+ };
+ mappings?: { k: TagsFilter; txt: string | UIElement; priority?: number, substitute?: boolean }[]
};
@@ -35,7 +47,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
* If 'question' is undefined, then the question is never asked at all
* If the question is "" (empty string) then the question is
*/
- question?: string,
+ question?: UIElement | string,
/**
* What is the priority of the question.
@@ -56,7 +68,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
*
*
*/
- mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[],
+ mappings?: { k: TagsFilter, txt: UIElement | string, priority?: number, substitute?: boolean }[],
/**
@@ -65,19 +77,14 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
* In the question, it'll offer a textfield
*/
freeform?: {
- key: string, template: string,
- renderTemplate: string
- placeholder?: string,
+ key: string,
+ template: string | UIElement,
+ renderTemplate: string | UIElement
+ placeholder?: string | UIElement,
extraTags?: TagsFilter,
},
- /**
- * Optional:
- * if defined, this a common piece of tag that is shown in front of every mapping (except freeform)
- */
- primer?: string,
-
/**
* In some very rare cases, tags have to be rewritten before displaying
* This function can be used for that.
@@ -85,6 +92,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
*/
tagsPreprocessor?: ((tags: any) => void)
}) {
+
this.options = options;
}
@@ -129,29 +137,24 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
class TagRendering extends UIElement implements TagDependantUIElement {
- private _priority: number;
private _userDetails: UIEventSource;
+ private _priority: number;
- Priority(): number {
- return this._priority;
- }
- private _question: string;
- private _primer: string;
- private _mapping: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[];
- private _renderMapping: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[];
+ private _question: UIElement;
+ private _mapping: { k: TagsFilter, txt: string | UIElement, priority?: number }[];
private _tagsPreprocessor?: ((tags: any) => any);
private _freeform: {
- key: string, template: string,
- renderTemplate: string,
-
- placeholder?: string,
+ key: string,
+ template: string | UIElement,
+ renderTemplate: string | UIElement,
+ placeholder?: string | UIElement,
extraTags?: TagsFilter
};
- private readonly _questionElement: UIElement;
- private readonly _textField: TextField; // Only here to update
+
+ private readonly _questionElement: InputElement;
private readonly _saveButton: UIElement;
private readonly _skipButton: UIElement;
@@ -165,19 +168,20 @@ class TagRendering extends UIElement implements TagDependantUIElement {
constructor(tags: UIEventSource, changes: Changes, options: {
priority?: number
- question?: string,
- primer?: string,
+ question?: string | UIElement,
freeform?: {
- key: string, template: string,
- renderTemplate: string
- placeholder?: string,
+ key: string,
+ template: string | UIElement,
+ renderTemplate: string | UIElement,
+ placeholder?: string | UIElement,
extraTags?: TagsFilter,
},
tagsPreprocessor?: ((tags: any) => any),
- mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[]
+ mappings?: { k: TagsFilter, txt: string | UIElement, priority?: number, substitute?: boolean }[]
}) {
super(tags);
+ this.ListenTo(Locale.language);
const self = this;
this.ListenTo(this._questionSkipped);
this.ListenTo(this._editMode);
@@ -185,9 +189,10 @@ class TagRendering extends UIElement implements TagDependantUIElement {
this._userDetails = changes.login.userDetails;
this.ListenTo(this._userDetails);
- this._question = options.question;
+ if (options.question !== undefined) {
+ this._question = Translations.W(options.question);
+ }
this._priority = options.priority ?? 0;
- this._primer = options.primer ?? "";
this._tagsPreprocessor = function (properties) {
if (options.tagsPreprocessor === undefined) {
return properties;
@@ -201,97 +206,38 @@ class TagRendering extends UIElement implements TagDependantUIElement {
};
this._mapping = [];
- this._renderMapping = [];
this._freeform = options.freeform;
- // Prepare the choices for the Radio buttons
- const choices: UIElement[] = [];
- const usedChoices: string [] = [];
for (const choice of options.mappings ?? []) {
- if (choice.k === null) {
- this._mapping.push(choice);
- continue;
- }
- let choiceSubbed = choice;
+ let choiceSubbed = {
+ k: choice.k,
+ txt: choice.txt,
+ priority: choice.priority
+ };
+
if (choice.substitute) {
choiceSubbed = {
k: choice.k.substituteValues(
options.tagsPreprocessor(this._source.data)),
- txt: this.ApplyTemplate(choice.txt),
- substitute: false,
+ txt: choice.txt,
priority: choice.priority
}
}
- const txt = choiceSubbed.txt
- // Choices is what is shown in the radio buttons
- if (usedChoices.indexOf(txt) < 0) {
-
- choices.push(new FixedUiElement(txt));
- usedChoices.push(txt);
- // This is used to convert the radio button index into tags needed to add
- this._mapping.push(choiceSubbed);
- } else {
- this._renderMapping.push(choiceSubbed); // only used while rendering
- }
+ this._mapping.push({
+ k: choiceSubbed.k,
+ txt: choiceSubbed.txt
+ });
}
- // Map radiobutton choice and textfield answer onto tagfilter. That tagfilter will be pushed into the changes later on
- const pickChoice = (i => {
- if (i === undefined || i === null) {
- return undefined
- }
- return self._mapping[i].k
- });
- const pickString =
- (string) => {
- if (string === "" || string === undefined) {
- return undefined;
- }
- const tag = new Tag(self._freeform.key, string);
- if (self._freeform.extraTags === undefined) {
- return tag;
- }
- return new And([
- self._freeform.extraTags,
- tag
- ]
- );
- };
-
// Prepare the actual input element -> pick an appropriate implementation
- let inputElement: UIInputElement;
-
-
- if (this._freeform !== undefined && this._mapping !== undefined) {
- // Radio buttons with 'other'
- inputElement = new UIRadioButtonWithOther(
- choices,
- this._freeform.template,
- this._freeform.placeholder,
- pickChoice,
- pickString
- );
- this._questionElement = inputElement;
- } else if (this._mapping !== [] && this._mapping.length > 0) {
- // This is a classic radio selection element
- inputElement = new UIRadioButton(new UIEventSource(choices), pickChoice, false)
- this._questionElement = inputElement;
- } else if (this._freeform !== undefined) {
- this._textField = new TextField(new UIEventSource(this._freeform.placeholder), pickString);
- inputElement = this._textField;
- this._questionElement = new FixedUiElement(
- "" + this._freeform.template.replace("$$$", inputElement.Render()) + "
")
- } else {
- throw "Invalid questionRendering, expected at least choices or a freeform"
- }
-
+ this._questionElement = this.InputElementFor(options);
const save = () => {
- const selection = inputElement.GetValue().data;
+ const selection = self._questionElement.GetValue().data;
if (selection) {
changes.addTag(tags.data.id, selection);
}
@@ -305,54 +251,157 @@ class TagRendering extends UIElement implements TagDependantUIElement {
}
// Setup the save button and it's action
- this._saveButton = new SaveButton(inputElement.GetValue())
+ this._saveButton = new SaveButton(this._questionElement.GetValue())
.onClick(save);
+ this._editButton = new FixedUiElement("");
if (this._question !== undefined) {
this._editButton = new FixedUiElement(" ")
.onClick(() => {
- console.log("Click", self._editButton);
- if (self._textField) {
- self._textField.value.setData(self._source.data["name"] ?? "");
- }
-
self._editMode.setData(true);
+ self._questionElement.GetValue().setData(self.CurrentValue());
});
- } else {
- this._editButton = new FixedUiElement("");
}
const cancelContents = this._editMode.map((isEditing) => {
if (isEditing) {
- return "Annuleren ";
+ return ""+Translations.t.general.cancel.R()+" ";
} else {
- return "Overslaan (Ik weet het niet zeker...) ";
+ return ""+Translations.t.general.skip.R()+" ";
}
- });
+ }, [Locale.language]);
// And at last, set up the skip button
- this._skipButton = new VariableUiElement(cancelContents).onClick(cancel);
+ this._skipButton = new VariableUiElement(cancelContents).onClick(cancel) ;
+ }
+ private InputElementFor(options: {
+ freeform?: {
+ key: string,
+ template: string | UIElement,
+ renderTemplate: string | UIElement,
+ placeholder?: string | UIElement,
+ extraTags?: TagsFilter,
+ },
+ mappings?: { k: TagsFilter, txt: string | UIElement, priority?: number, substitute?: boolean }[]
+ }):
+ InputElement {
+
+ const elements = [];
+
+ if (options.mappings !== undefined) {
+
+ const previousTexts= [];
+ for (const mapping of options.mappings) {
+ if(mapping.k === null){
+ continue;
+ }
+ if(previousTexts.indexOf(mapping.txt) >= 0){
+ continue;
+ }
+ previousTexts.push(mapping.txt);
+
+ elements.push(this.InputElementForMapping(mapping));
+ }
+ }
+
+ if (options.freeform !== undefined) {
+ elements.push(this.InputForFreeForm(options.freeform));
+ }
+
+
+ if (elements.length == 0) {
+ console.warn("WARNING: no tagrendering with following options:", options);
+ return new FixedInputElement("This should not happen: no tag renderings defined", undefined);
+ }
+ if (elements.length == 1) {
+ return elements[0];
+ }
+
+ return new RadioButton(elements, false);
+
}
- private ApplyTemplate(template: string): string {
- const tags = this._tagsPreprocessor(this._source.data);
- return TagUtils.ApplyTemplate(template, tags);
+
+ private InputElementForMapping(mapping: { k: TagsFilter, txt: string | UIElement }) {
+ return new FixedInputElement(mapping.txt, mapping.k);
}
+
+ private InputForFreeForm(freeform): InputElement {
+ if (freeform === undefined) {
+ return undefined;
+ }
+
+
+ const pickString =
+ (string) => {
+ if (string === "" || string === undefined) {
+ return undefined;
+ }
+ const tag = new Tag(freeform.key, string);
+ if (freeform.extraTags === undefined) {
+ return tag;
+ }
+ return new And([
+ tag,
+ freeform.extraTags
+ ]
+ );
+ };
+
+ const toString =
+ (tag) => {
+ if (tag instanceof And) {
+ return toString(tag.and[0])
+ } else if (tag instanceof Tag) {
+ return tag.value
+ }
+ return undefined;
+ }
+
+
+ let inputElement: InputElement;
+ const textField = new TextField({
+ placeholder: this._freeform.placeholder,
+ fromString: pickString,
+ toString: toString
+ });
+
+ const prepost = Translations.W(freeform.template).InnerRender().split("$$$");
+ return new InputElementWrapper(prepost[0], textField, prepost[1]);
+ }
+
+
IsKnown(): boolean {
const tags = TagUtils.proprtiesToKV(this._source.data);
- for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) {
+ 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;
}
+ private CurrentValue(): TagsFilter {
+ const tags = TagUtils.proprtiesToKV(this._source.data);
+
+ for (const oneOnOneElement of this._mapping) {
+ if (oneOnOneElement.k !== null && oneOnOneElement.k.matches(tags)) {
+ return oneOnOneElement.k;
+ }
+ }
+ if (this._freeform === undefined) {
+ return undefined;
+ }
+
+ return new Tag(this._freeform.key, this._source.data[this._freeform.key]);
+ }
+
+
IsQuestioning(): boolean {
if (this.IsKnown()) {
return false;
@@ -368,10 +417,10 @@ class TagRendering extends UIElement implements TagDependantUIElement {
return true;
}
- private RenderAnwser(): string {
+ private RenderAnwser(): UIElement {
const tags = TagUtils.proprtiesToKV(this._source.data);
- let freeform = "";
+ let freeform: UIElement = new FixedUiElement("");
let freeformScore = -10;
if (this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined) {
freeform = this.ApplyTemplate(this._freeform.renderTemplate);
@@ -379,58 +428,59 @@ class TagRendering extends UIElement implements TagDependantUIElement {
}
- let highestScore = -100;
- let highestTemplate = undefined;
- for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) {
- 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
- }
+ 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 (freeformScore > highestScore) {
+ return freeform;
+ }
+
+ if (highestTemplate !== undefined) {
+ // we render the found template
+ return this.ApplyTemplate(highestTemplate);
+ }
- if (highestTemplate !== undefined) {
- // we render the found template
- return this._primer + this.ApplyTemplate(highestTemplate);
- }
-
}
- protected InnerRender(): string {
-
+ InnerRender(): string {
if (this.IsQuestioning() || this._editMode.data) {
// Not yet known or questioning, we have to ask a question
+ const question = this._question.Render();
return "" +
- "
" + this._question + " " +
- (this._question !== "" ? "
" : "") +
- this._questionElement.Render() +
+ "
" + question + " " +
+ (this._question.IsEmpty() ? "" : "
") +
+ "
" + this._questionElement.Render() + "
" +
this._skipButton.Render() +
this._saveButton.Render() +
"
"
}
if (this.IsKnown()) {
- const html = this.RenderAnwser();
- if (html == "") {
+ const answer = this.RenderAnwser()
+ if (answer.IsEmpty()) {
return "";
}
+ const html = answer.Render();
let editButton = "";
- if(this._userDetails.data.loggedIn){
+ if (this._userDetails.data.loggedIn && this._question !== undefined) {
editButton = this._editButton.Render();
}
-
+
return "" +
"" + html + " " +
editButton +
@@ -441,13 +491,26 @@ class TagRendering extends UIElement implements TagDependantUIElement {
}
+
+ Priority(): number {
+ return this._priority;
+ }
+
+ private ApplyTemplate(template: string | UIElement): UIElement {
+ if(template === undefined || template === null){
+ throw "Trying to apply a template, but the template is null/undefined"
+ }
+ const tags = this._tagsPreprocessor(this._source.data);
+ if (template instanceof UIElement) {
+ template = template.Render();
+ }
+ return new FixedUiElement(TagUtils.ApplyTemplate(template, tags));
+ }
+
+
InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
- this._questionElement.Update();
- this._saveButton.Update();
- this._skipButton.Update();
- this._textField?.Update();
- this._editButton.Update();
+ this._questionElement.Update(); // Another manual update for them
}
}
\ No newline at end of file
diff --git a/Logic/Changes.ts b/Logic/Changes.ts
index e0dd9ce8d..488ea50d5 100644
--- a/Logic/Changes.ts
+++ b/Logic/Changes.ts
@@ -6,7 +6,6 @@ import {OsmConnection} from "./OsmConnection";
import {OsmNode, OsmObject} from "./OsmObject";
import {ElementStorage} from "./ElementStorage";
import {UIEventSource} from "../UI/UIEventSource";
-import {Question, QuestionDefinition} from "./Question";
import {And, Tag, TagsFilter} from "./TagsFilter";
export class Changes {
diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts
index 2bf8e1ac5..cbfdc0c2c 100644
--- a/Logic/FilteredLayer.ts
+++ b/Logic/FilteredLayer.ts
@@ -19,7 +19,7 @@ import { LayerDefinition } from "../Customizations/LayerDefinition";
*/
export class FilteredLayer {
- public readonly name: string;
+ public readonly name: string | UIElement;
public readonly filters: TagsFilter;
public readonly isDisplayed: UIEventSource = new UIEventSource(true);
public readonly layerDef: LayerDefinition;
@@ -33,6 +33,7 @@ export class FilteredLayer {
/** The featurecollection from overpass
*/
private _dataFromOverpass;
+ private _wayHandling: number;
/** List of new elements, geojson features
*/
private _newElements = [];
@@ -40,8 +41,8 @@ export class FilteredLayer {
* The leaflet layer object which should be removed on rerendering
*/
private _geolayer;
- private _selectedElement: UIEventSource;
- private _showOnPopup: (tags: UIEventSource) => UIElement;
+ private _selectedElement: UIEventSource<{ feature: any }>;
+ private _showOnPopup: (tags: UIEventSource, feature: any) => UIElement;
constructor(
layerDef: LayerDefinition,
@@ -51,6 +52,8 @@ export class FilteredLayer {
showOnPopup: ((tags: UIEventSource) => UIElement)
) {
this.layerDef = layerDef;
+
+ this._wayHandling = layerDef.wayHandling;
this._selectedElement = selectedElement;
this._showOnPopup = showOnPopup;
this._style = layerDef.style;
@@ -84,10 +87,18 @@ export class FilteredLayer {
public SetApplicableData(geojson: any): any {
const leftoverFeatures = [];
const selfFeatures = [];
- for (const feature of geojson.features) {
+ for (let feature of geojson.features) {
// feature.properties contains all the properties
var tags = TagUtils.proprtiesToKV(feature.properties);
if (this.filters.matches(tags)) {
+ feature.properties["_surface"] = GeoOperations.surfaceAreaInSqMeters(feature);
+ if (feature.geometry.type !== "Point") {
+ if (this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_AND_WAY) {
+ selfFeatures.push(GeoOperations.centerpoint(feature));
+ } else if (this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_ONLY) {
+ feature = GeoOperations.centerpoint(feature);
+ }
+ }
selfFeatures.push(feature);
} else {
leftoverFeatures.push(feature);
@@ -199,8 +210,8 @@ export class FilteredLayer {
layer.on("click", function (e) {
console.log("Selected ", feature)
- self._selectedElement.setData(feature.properties);
- const uiElement = self._showOnPopup(eventSource);
+ self._selectedElement.setData({feature: feature});
+ const uiElement = self._showOnPopup(eventSource, feature);
const popup = L.popup()
.setContent(uiElement.Render())
.setLatLng(e.latlng)
diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts
index 91687e950..4ed677ebc 100644
--- a/Logic/GeoOperations.ts
+++ b/Logic/GeoOperations.ts
@@ -6,6 +6,15 @@ export class GeoOperations {
return turf.area(feature);
}
+ static centerpoint(feature: any)
+ {
+ const newFeature= turf.center(feature);
+ newFeature.properties = feature.properties;
+ newFeature.id = feature.id;
+
+ return newFeature;
+ }
+
static featureIsContainedInAny(feature: any,
shouldNotContain: any[],
maxOverlapPercentage: number): boolean {
diff --git a/Logic/Imgur.ts b/Logic/Imgur.ts
index bfba380cd..7d1b0ed49 100644
--- a/Logic/Imgur.ts
+++ b/Logic/Imgur.ts
@@ -8,6 +8,7 @@ export class Imgur {
title: string, description: string, blobs: FileList,
handleSuccessfullUpload: ((imageURL: string) => void),
allDone: (() => void),
+ onFail: ((reason: string) => void),
offset:number = 0) {
if (blobs.length == offset) {
@@ -24,7 +25,8 @@ export class Imgur {
handleSuccessfullUpload,
allDone,
offset + 1);
- }
+ },
+ onFail
);
@@ -60,7 +62,6 @@ export class Imgur {
}
- console.log(data);
const licenseInfo = new LicenseInfo();
licenseInfo.licenseShortName = data.license;
@@ -75,7 +76,8 @@ export class Imgur {
}
static uploadImage(title: string, description: string, blob,
- handleSuccessfullUpload: ((imageURL: string) => void)) {
+ handleSuccessfullUpload: ((imageURL: string) => void),
+ onFail: (reason:string) => void) {
const apiUrl = 'https://api.imgur.com/3/image';
const apiKey = '7070e7167f0a25a';
@@ -106,7 +108,8 @@ export class Imgur {
response = JSON.parse(response);
handleSuccessfullUpload(response.data.link);
}).fail((reason) => {
- console.log("Uploading to IMGUR failed", reason)
+ console.log("Uploading to IMGUR failed", reason);
+ onFail(reason)
});
}
diff --git a/Logic/LayerUpdater.ts b/Logic/LayerUpdater.ts
index 408d75623..5478e8944 100644
--- a/Logic/LayerUpdater.ts
+++ b/Logic/LayerUpdater.ts
@@ -57,8 +57,7 @@ export class LayerUpdater {
}
private handleFail(reason: any) {
- console.log("QUERY FAILED", reason);
- console.log("Retrying in 1s")
+ console.log("QUERY FAILED (retrying in 1 sec)", reason);
this.previousBounds = undefined;
const self = this;
window.setTimeout(
@@ -73,7 +72,6 @@ export class LayerUpdater {
}
console.log("Zoom level: ",this._map.map.getZoom(), "Least needed zoom:", this._minzoom)
if (this._map.map.getZoom() < this._minzoom || this._map.Location.data.zoom < this._minzoom) {
- console.log("Not running query: zoom not sufficient");
return;
}
diff --git a/Logic/OsmConnection.ts b/Logic/OsmConnection.ts
index 648d9bbb5..e07751d33 100644
--- a/Logic/OsmConnection.ts
+++ b/Logic/OsmConnection.ts
@@ -17,7 +17,6 @@ export class UserDetails {
export class OsmConnection {
-
private auth = new osmAuth({
oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem',
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
@@ -123,6 +122,7 @@ export class OsmConnection {
public preferenceSources : any = {}
public GetPreference(key: string) : UIEventSource{
+ key = "mapcomplete-"+key;
if (this.preferenceSources[key] !== undefined) {
return this.preferenceSources[key];
}
diff --git a/Logic/OsmImageUploadHandler.ts b/Logic/OsmImageUploadHandler.ts
index 3cabdbc40..cd242c777 100644
--- a/Logic/OsmImageUploadHandler.ts
+++ b/Logic/OsmImageUploadHandler.ts
@@ -48,12 +48,18 @@ export class OsmImageUploadHandler {
title: title,
description: description,
handleURL: function (url) {
- let freeIndex = 0;
- while (tags["image:" + freeIndex] !== undefined) {
- freeIndex++;
+
+ let key = "image";
+ if (tags["image"] !== undefined) {
+
+ let freeIndex = 0;
+ while (tags["image:" + freeIndex] !== undefined) {
+ freeIndex++;
+ }
+ key = "image:" + freeIndex;
}
- console.log("Adding image:" + freeIndex, url);
- changes.addChange(tags.id, "image:" + freeIndex, url);
+ console.log("Adding image:" + key, url);
+ changes.addChange(tags.id, key, url);
self._slideShow.MoveTo(-1); // set the last (thus newly added) image) to view
},
allDone: function () {
diff --git a/Logic/Overpass.ts b/Logic/Overpass.ts
index 28efa0b62..f9eafaf8e 100644
--- a/Logic/Overpass.ts
+++ b/Logic/Overpass.ts
@@ -32,7 +32,7 @@ export class Overpass {
queryGeoJson(bbox: string, continuation: ((any) => void), onFail: ((reason) => void)): void {
let query = this.buildQuery(bbox);
-
+
if(Overpass.testUrl !== null){
console.log("Using testing URL")
query = Overpass.testUrl;
@@ -44,7 +44,7 @@ export class Overpass {
console.log("Query failed")
onFail(status);
}
-
+
if(json.elements === [] && json.remarks.indexOf("runtime error") > 0){
console.log("Timeout or other runtime error");
return;
diff --git a/Logic/Question.ts b/Logic/Question.ts
deleted file mode 100644
index 3ffd6469a..000000000
--- a/Logic/Question.ts
+++ /dev/null
@@ -1,508 +0,0 @@
-import {Changes} from "./Changes";
-import {UIElement} from "../UI/UIElement";
-import {UIEventSource} from "../UI/UIEventSource";
-
-export class QuestionUI extends UIElement {
- private readonly _q: Question;
- private readonly _tags: UIEventSource;
- /**
- * The ID of the calling question - used to trigger it's onsave
- */
- private readonly _qid;
-
- constructor(q: Question, qid: number, tags: UIEventSource) {
- super(tags);
- this._q = q;
- this._tags = tags;
- this._qid = qid;
- }
-
-
- private RenderRadio() {
- let radios = "";
- let c = 0;
- for (let answer of this._q.question.answers) {
- const human = answer.text;
- const ansId = "q" + this._qid + "-answer" + c;
- radios +=
- " " +
- "" + human + " " +
- " ";
- c++;
- }
- return radios;
- }
-
- private RenderRadioText() {
- let radios = "";
- let c = 0;
- for (let answer of this._q.question.answers) {
- const human = answer.text;
- const ansId = "q" + this._qid + "-answer" + c;
- radios +=
- " " +
- "" + human + " " +
- " ";
- c++;
- }
- const ansId = "q" + this._qid + "-answer" + c;
-
- radios +=
- " " +
- " " +
- " ";
-
- return radios;
- }
-
-
- InnerRender(): string {
-
- if (!this._q.Applicable(this._tags.data)) {
- return "";
- }
-
-
- const q = this._q.question;
-
-
- let answers = "";
- if (q.type == "radio") {
- answers += this.RenderRadio();
- } else if (q.type == "text") {
- answers += " "
- } else if (q.type == "radio+text") {
- answers += this.RenderRadioText();
- } else {
- alert("PLZ RENDER TYPE " + q.type);
- }
-
-
- const embeddedScriptSave = 'questionAnswered(' + this._qid + ', "' + this._tags.data.id + '", false )';
- const embeddedScriptSkip = 'questionAnswered(' + this._qid + ', "' + this._tags.data.id + '", true )';
- const saveButton = " ";
- const skip = " ";
- return q.question + " " + answers + saveButton + skip;
- }
-
- InnerUpdate(htmlElement: HTMLElement) {
- }
-}
-
-
-export class QuestionDefinition {
-
-
- static noNameOrNameQuestion(question: string, noExplicitName : string, severity : number) : QuestionDefinition{
- const q = new QuestionDefinition(question);
-
- q.type = 'radio+text';
- q.addAnwser(noExplicitName, "noname","yes");
- q.addUnrequiredTag("name", "*");
- q.addUnrequiredTag("noname", "yes");
-
- q.key = "name";
- q.severity = severity;
- return q;
- }
-
- static textQuestion(
- question: string,
- key: string,
- severity: number
- ): QuestionDefinition {
- const q = new QuestionDefinition(question);
- q.type = 'text';
- q.key = key;
- q.severity = severity;
- q.addUnrequiredTag(key, '*');
- return q;
- }
-
- static radioQuestionSimple(
- question: string,
- severity: number,
- key: string,
- answers: { text: string, value: string }[]) {
-
-
- const answers0: {
- text: string,
- tags: { k: string, v: string }[],
- }[] = [];
- for (const i in answers) {
- const answer = answers[i];
- answers0.push({text: answer.text, tags: [{k: key, v: answer.value}]})
- }
-
- var q = this.radioQuestion(question, severity, answers0);
- q.key = key;
- q.addUnrequiredTag(key, '*');
- return q;
- }
-
- static radioAndTextQuestion(
- question: string,
- severity: number,
- key: string,
- answers: { text: string, value: string }[]) {
-
- const q = this.radioQuestionSimple(question, severity, key, answers);
- q.type = 'radio+text';
- return q;
-
- }
-
- static radioQuestion(
- question: string,
- severity: number,
- answers:
- {
- text: string,
- tags: { k: string, v: string }[],
- }[]
- ): QuestionDefinition {
-
-
- const q = new QuestionDefinition(question);
- q.severity = severity;
- q.type = 'radio';
- q.answers = answers;
- for (const i in answers) {
- const answer = answers[i];
- for (const j in answer.tags) {
- const tag = answer.tags[j];
- q.addUnrequiredTag(tag.k, tag.v);
- }
- }
-
- return q;
- }
-
-
- static GrbNoNumberQuestion() : QuestionDefinition{
- const q = new QuestionDefinition("Heeft dit gebouw een huisnummer?");
- q.type = "radio";
- q.severity = 10;
- q.answers = [{
- text: "Ja, het OSM-huisnummer is correct",
- tags: [{k: "fixme", v: ""}]
- }, {
-
- text: "Nee, het is een enkele garage",
- tags: [{k: "building", v: "garage"}, {k: "fixme", v: ""}]
- }, {
-
- text: "Nee, het zijn meerdere garages",
- tags: [{k: "building", v: "garages"}, {k: "fixme", v: ""}]
- }
-
-
- ];
- q.addRequiredTag("fixme", "GRB thinks that this has number no number")
- return q;
- }
-
- static GrbHouseNumberQuestion() : QuestionDefinition{
-
-
- const q = new QuestionDefinition("Wat is het huisnummer?");
- q.type = "radio+text";
- q.severity = 10;
-
- q.answers = [{
- text: "Het OSM-huisnummer is correct",
- tags: [{k: "fixme", v: ""}],
- }]
- q.key = "addr:housenumber";
-
-
- q.addRequiredTag("fixme", "*");
-
- return q;
- }
-
-
- private constructor(question: string) {
- this.question = question;
- }
-
- /**
- * Question for humans
- */
- public question: string;
-
- /**
- * 'type' indicates how the answers are rendered and must be one of:
- * 'text' for a free to fill text field
- * 'radio' for radiobuttons
- * 'radio+text' for radiobuttons and a freefill text field
- * 'dropdown' for a dropdown menu
- * 'number' for a number field
- *
- * If 'text' or 'number' is specified, 'key' is used as tag for the answer.
- * If 'radio' or 'dropdown' is specified, the answers are used from 'tags'
- *
- */
- public type: string = 'radio';
- /**
- * Only used for 'text' or 'number' questions
- */
- public key: string = null;
-
- public answers: {
- text: string,
- tags: { k: string, v: string }[]
- }[];
-
- /**
- * Indicates that the element must have _all_ the tags defined below
- * Dictionary 'key' => [values]; empty list is wildcard
- */
- private mustHaveAllTags = [];
-
- /**
- * Indicates that the element must _not_ have any of the tags defined below.
- * Dictionary 'key' => [values]
- */
- private mustNotHaveTags = [];
-
- /**
- * Severity: how important the question is
- * The higher, the sooner it'll be shown
- */
- public severity: number = 0;
-
- addRequiredTag(key: string, value: string) {
- if (this.mustHaveAllTags[key] === undefined) {
- this.mustHaveAllTags[key] = [value];
- } else {
- if(this.mustHaveAllTags[key] === []){
- // Wildcard
- return;
- }
- this.mustHaveAllTags[key].push(value);
- }
-
- if (value === '*') {
- this.mustHaveAllTags[key] = [];
- }
- return this;
- }
-
- addUnrequiredTag(key: string, value: string) {
- let valueList = this.mustNotHaveTags[key];
-
- if (valueList === undefined) {
- valueList = [value];
- this.mustNotHaveTags[key] = valueList;
- } else {
- if (valueList === []) {
- return;
- }
- valueList.push(value);
- }
-
- if (value === '*') {
- this.mustNotHaveTags[key] = [];
- }
- return this;
- }
-
- private addAnwser(anwser: string, key: string, value: string) {
- if (this.answers === undefined) {
- this.answers = [{text: anwser, tags: [{k: key, v: value}]}];
- } else {
- this.answers.push({text: anwser, tags: [{k: key, v: value}]});
- }
- this.addUnrequiredTag(key, value);
- }
-
- public isApplicable(alreadyExistingTags): boolean {
- for (let k in this.mustHaveAllTags) {
-
- var actual = alreadyExistingTags[k];
- if (actual === undefined) {
- return false;
- }
-
- let possibleVals = this.mustHaveAllTags[k];
- if (possibleVals.length == 0) {
- // Wildcard
- continue;
- }
-
- let index = possibleVals.indexOf(actual);
- if (index < 0) {
- return false
- }
- }
-
- for (var k in this.mustNotHaveTags) {
- var actual = alreadyExistingTags[k];
- if (actual === undefined) {
- continue;
- }
- let impossibleVals = this.mustNotHaveTags[k];
- if (impossibleVals.length == 0) {
- // Wildcard
- return false;
- }
-
- let index = impossibleVals.indexOf(actual);
- if (index >= 0) {
- return false
- }
- }
-
- return true;
-
- }
-}
-
-
-export class Question {
-
-
- // All the questions are stored in here, to be able to retrieve them globaly. This is a workaround, see below
- static questions = Question.InitCallbackFunction();
-
- static InitCallbackFunction(): Question[] {
-
- // This needs some explanation, as it is a workaround
- Question.questions = [];
- // The html in a popup is only created when the user actually clicks to open it
- // This means that we can not bind code to an HTML-element (as it doesn't exist yet)
- // We work around this, by letting the 'save' button just call the function 'questionAnswered' with the ID of the question
- // THis defines and registers this global function
-
-
- /**
- * Calls back to the question with either the answer or 'skip'
- * @param questionId
- * @param elementId
- */
- function questionAnswered(questionId, elementId, dontKnow) {
- if (dontKnow) {
- Question.questions[questionId].Skip(elementId);
- } else {
- Question.questions[questionId].OnSave(elementId);
- }
- }
-
-
- function checkRadioButton(id) {
- // @ts-ignore
- document.getElementById(id).checked = true;
- }
-
- // must cast as any to set property on window
- // @ts-ignore
- const _global = (window /* browser */ || global /* node */) as any;
- _global.questionAnswered = questionAnswered;
- _global.checkRadioButton = checkRadioButton;
- return [];
- }
-
-
- public readonly question: QuestionDefinition;
- private _changeHandler: Changes;
- private readonly _qId;
- public skippedElements: string[] = [];
-
- constructor(
- changeHandler: Changes,
- question: QuestionDefinition) {
-
- this.question = question;
-
- this._qId = Question.questions.length;
- this._changeHandler = changeHandler;
- Question.questions.push(this);
- }
-
- /**
- * SHould this question be asked?
- * Returns false if question is already there or if a premise is missing
- */
- public Applicable(tags): boolean {
-
- if (this.skippedElements.indexOf(tags.id) >= 0) {
- return false;
- }
-
- return this.question.isApplicable(tags);
- }
-
- /**
- *
- * @param elementId: the OSM-id of the element to perform the change on, format 'way/123', 'node/456' or 'relation/789'
- * @constructor
- */
- protected OnSave(elementId: string) {
- let tagsToApply: { k: string, v: string }[] = [];
- const q: QuestionDefinition = this.question;
- let tp = this.question.type;
- if (tp === "radio") {
- const selected = document.querySelector('input[name="q' + this._qId + '"]:checked');
- if (selected === null) {
- console.log("No answer selected");
- return
- }
- let index = (selected as any).value;
- tagsToApply = q.answers[index].tags;
- } else if (tp === "text") {
- // @ts-ignore
- let value = document.getElementById("q-" + this._qId + "-textbox").value;
- if (value === undefined || value.length == 0) {
- console.log("Answer too short");
- return;
- }
- tagsToApply = [{k: q.key, v: value}];
- } else if (tp === "radio+text") {
- const selected = document.querySelector('input[name="q' + this._qId + '"]:checked');
- if (selected === null) {
- console.log("No answer selected");
- return
- }
- let index = (selected as any).value;
- if (index < q.answers.length) {
- // A 'proper' answer was selected
- tagsToApply = q.answers[index].tags;
- } else {
- // The textfield was selected
- // @ts-ignore
- let value = document.getElementById("q-" + this._qId + "-textbox").value;
- if (value === undefined || value.length < 3) {
- console.log("Answer too short");
- return;
- }
- tagsToApply = [{k: q.key, v: value}];
- }
-
- }
-
- console.log("Question.ts: Applying tags",tagsToApply," to element ", elementId);
-
- for (const toApply of tagsToApply) {
- this._changeHandler.addChange(elementId, toApply.k, toApply.v);
- }
-
- }
-
- /**
- * Creates the HTML question for this tag collection
- */
- public CreateHtml(tags: UIEventSource): UIElement {
- return new QuestionUI(this, this._qId, tags);
- }
-
-
- private Skip(elementId: any) {
- this.skippedElements.push(elementId);
- console.log("SKIP");
- // Yeah, this is cheating below
- // It is an easy way to notify the UIElement that something has changed
- this._changeHandler._allElements.getElement(elementId).ping();
- }
-}
\ No newline at end of file
diff --git a/Logic/StrayClickHandler.ts b/Logic/StrayClickHandler.ts
index 85ccc9d7b..59c50de96 100644
--- a/Logic/StrayClickHandler.ts
+++ b/Logic/StrayClickHandler.ts
@@ -10,16 +10,16 @@ import {UIElement} from "../UI/UIElement";
export class StrayClickHandler {
private _basemap: Basemap;
private _lastMarker;
- private _leftMessage: UIEventSource<() => UIElement>;
+ private _fullScreenMessage: UIEventSource;
private _uiToShow: (() => UIElement);
constructor(
basemap: Basemap,
- selectElement: UIEventSource,
- leftMessage: UIEventSource<() => UIElement>,
+ selectElement: UIEventSource<{ feature: any }>,
+ fullScreenMessage: UIEventSource,
uiToShow: (() => UIElement)) {
this._basemap = basemap;
- this._leftMessage = leftMessage;
+ this._fullScreenMessage = fullScreenMessage;
this._uiToShow = uiToShow;
const self = this;
const map = basemap.map;
@@ -32,15 +32,16 @@ export class StrayClickHandler {
self._lastMarker = L.marker([lastClick.lat, lastClick.lon]);
const uiElement = uiToShow();
const popup = L.popup().setContent(uiElement.Render());
- uiElement.Activate();
uiElement.Update();
+ uiElement.Activate();
self._lastMarker.addTo(map);
self._lastMarker.bindPopup(popup).openPopup();
self._lastMarker.on("click", () => {
- leftMessage.setData(self._uiToShow);
+ fullScreenMessage.setData(self._uiToShow());
});
-
+ uiElement.Update();
+ uiElement.Activate();
});
selectElement.addCallback(() => {
diff --git a/README.md b/README.md
index 4ca29f4ce..4a48c3a36 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,9 @@ When a map feature is clicked, a popup shows the information, images and questio
The answers given by the user are sent (after a few seconds) to OpenStreetMap directly - if the user is logged in. If not logged in, the user is prompted to do so.
+The UI-event-source is a class where the entire system is built upon, it acts as an observable object: another object can register for changes to update when needed.
+
+
### Searching images
Images are fetched from:
diff --git a/UI/AddButton.ts b/UI/AddButton.ts
index ac0a3434b..3b493a949 100644
--- a/UI/AddButton.ts
+++ b/UI/AddButton.ts
@@ -123,6 +123,7 @@ export class AddButton extends UIElement {
const self = this;
htmlElement.onclick = function (event) {
+ // @ts-ignore
if(event.consumed){
return;
}
diff --git a/UI/Base/Button.ts b/UI/Base/Button.ts
index 3026729c3..817de1158 100644
--- a/UI/Base/Button.ts
+++ b/UI/Base/Button.ts
@@ -18,7 +18,7 @@ export class Button extends UIElement {
}
- protected InnerRender(): string {
+ InnerRender(): string {
return " " +
this._logout.Render() +
+ this._languagePicker.Render() +
this._pendingChanges.Render() +
"
" +
diff --git a/UI/i18n/Locale.ts b/UI/i18n/Locale.ts
new file mode 100644
index 000000000..3bc55cb60
--- /dev/null
+++ b/UI/i18n/Locale.ts
@@ -0,0 +1,24 @@
+import {UIEventSource} from "../UIEventSource";
+import {OsmConnection} from "../../Logic/OsmConnection";
+
+
+export default class Locale {
+ public static language: UIEventSource = Locale.getInitialLanguage();
+
+ private static getInitialLanguage() {
+ // The key to save in local storage
+ const LANGUAGE_KEY = 'language'
+
+ const lng = new UIEventSource("en");
+ const saved = localStorage.getItem(LANGUAGE_KEY);
+ lng.setData(saved);
+
+
+ lng.addCallback(data => {
+ console.log("Selected language", data);
+ localStorage.setItem(LANGUAGE_KEY, data)
+ });
+
+ return lng;
+ }
+}
diff --git a/UI/i18n/Translation.ts b/UI/i18n/Translation.ts
new file mode 100644
index 000000000..c02f8423a
--- /dev/null
+++ b/UI/i18n/Translation.ts
@@ -0,0 +1,43 @@
+import { UIElement } from "../UIElement"
+import Locale from "./Locale"
+import {FixedUiElement} from "../Base/FixedUiElement";
+
+
+export default class Translation extends UIElement {
+ get txt(): string {
+ const txt = this.translations[Locale.language.data];
+ if (txt !== undefined) {
+ return txt;
+ }
+ const en = this.translations["en"];
+ console.warn("No translation for language ", Locale.language.data, "for", en);
+ if (en !== undefined) {
+ return en;
+ }
+ for (const i in this.translations) {
+ return this.translations[i]; // Return a random language
+ }
+ return "Missing translation"
+ }
+
+ InnerRender(): string {
+ return this.txt
+ }
+
+ public readonly translations: object
+
+ constructor(translations: object) {
+ super(Locale.language)
+ this.translations = translations
+ }
+
+
+ public R(): string {
+ return new Translation(this.translations).Render();
+ }
+
+ public Clone(){
+ return new Translation(this.translations)
+ }
+
+}
diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts
new file mode 100644
index 000000000..b954ac548
--- /dev/null
+++ b/UI/i18n/Translations.ts
@@ -0,0 +1,394 @@
+import Translation from "./Translation";
+import T from "./Translation";
+import {UIElement} from "../UIElement";
+import {FixedUiElement} from "../Base/FixedUiElement";
+
+
+export default class Translations {
+
+ constructor() {
+ throw "Translations is static. If you want to intitialize a new translation, use the singular form"
+ }
+
+
+ static t = {
+ cyclofix: {
+ title: new T({
+ en: 'Cyclofix bicycle infrastructure',
+ nl: 'Cyclofix fietsinfrastructuur',
+ fr: 'TODO: FRENCH TRANSLATION'
+ }),
+ description: new T({
+ en: "On this map we want to collect data about the whereabouts of bicycle pumps and public racks in Brussels." +
+ "As a result, cyclists will be able to quickly find the nearest infrastructure for their needs.",
+ nl: "Op deze kaart willen we gegevens verzamelen over de locatie van fietspompen en openbare stelplaatsen in Brussel." +
+ "Hierdoor kunnen fietsers snel de dichtstbijzijnde infrastructuur vinden die voldoet aan hun behoeften.",
+ fr: "Sur cette carte, nous voulons collecter des données sur la localisation des pompes à vélo et des supports publics à Bruxelles." +
+ "Les cyclistes pourront ainsi trouver rapidement l'infrastructure la plus proche de leurs besoins."
+ }),
+ freeFormPlaceholder: new T({en: 'specify', nl: 'specifieer', fr: 'TODO: fr'}),
+ parking: {
+ name: new T({en: 'bike parking', nl: 'fietsparking', fr: 'TODO: fr'}),
+ title: new T({en: 'Bike parking', nl: 'Fietsparking', fr: 'TODO: fr'}),
+ type: {
+ render: new T({
+ en: 'This is a bicycle parking of the type: {bicycle_parking}',
+ nl: 'Dit is een fietsenparking van het type: {bicycle_parking}',
+ fr: 'TODO: fr'
+ }),
+ template: new T({en: 'Some other type: $$$', nl: 'Een ander type: $$$', fr: 'TODO: fr'}),
+ question: new T({
+ en: 'What is the type of this bicycle parking?',
+ nl: 'Van welk type is deze fietsenparking?',
+ fr: 'TODO: fr'
+ }),
+ eg: new T({en: ", for example", nl: ", bijvoorbeeld"}),
+ stands: new T({en: 'Staple racks', nl: 'Nietjes', fr: 'TODO: fr'}),
+ wall_loops: new T({en: 'Wheel rack/loops', nl: 'Wielrek/lussen', fr: 'TODO: fr'}),
+ handlebar_holder: new T({en: 'Handlebar holder', nl: 'Stuurhouder', fr: 'TODO: fr'}),
+ shed: new T({en: 'Shed', nl: 'Schuur', fr: 'TODO: fr'}),
+ rack: new T({en: 'Rack', nl: 'Rek', fr: 'TODO: fr'}),
+ "two-tier": new T({en: 'Two-tiered', nl: 'Dubbel (twee verdiepingen)', fr: 'TODO: fr'}),
+ },
+
+ operator: {
+ render: new T({
+ en: 'This bike parking is operated by {operator}',
+ nl: 'Deze fietsenparking wordt beheerd door {operator}',
+ fr: 'TODO: fr'
+ }),
+ template: new T({en: 'A different operator: $$$', nl: 'Een andere beheerder: $$$', fr: 'TODO: fr'}),
+ question: new T({
+ en: 'Who operates this bike station (name of university, shop, city...)?',
+ nl: 'Wie beheert deze fietsenparking (naam universiteit, winkel, stad...)?',
+ fr: 'TODO: fr'
+ }),
+ private: new T({
+ en: 'Operated by a private person',
+ nl: 'Wordt beheerd door een privépersoon',
+ fr: 'TODO: fr'
+ }),
+ }
+ },
+ station: {
+ name: new T({
+ en: 'bike station (repair, pump or both)',
+ nl: 'fietsstation (herstel, pomp of allebei)',
+ fr: 'TODO: fr'
+ }),
+ title: new T({en: 'Bike station', nl: 'Fietsstation', fr: 'TODO: fr'}),
+ manometer: {
+ question: new T({
+ en: 'Does the pump have a pressure indicator or manometer?',
+ nl: 'Heeft deze pomp een luchtdrukmeter?',
+ fr: 'TODO: fr'
+ }),
+ yes: new T({en: 'There is a manometer', nl: 'Er is een luchtdrukmeter', fr: 'TODO: fr'}),
+ no: new T({en: 'There is no manometer', nl: 'Er is geen luchtdrukmeter', fr: 'TODO: fr'}),
+ broken: new T({
+ en: 'There is manometer but it is broken',
+ nl: 'Er is een luchtdrukmeter maar die is momenteel defect',
+ fr: 'TODO: fr'
+ })
+ },
+ electric: {
+ question: new T({
+ en: 'Is this an electric bike pump?',
+ nl: 'Is dit een electrische fietspomp?',
+ fr: 'TODO: fr'
+ }),
+ manual: new T({en: 'Manual pump', nl: 'Manuele pomp', fr: 'TODO: fr'}),
+ electric: new T({en: 'Electrical pump', nl: 'Electrische pomp', fr: 'TODO: fr'})
+ },
+ operational: {
+ question: new T({
+ en: 'Is the bike pump still operational?',
+ nl: 'Werkt de fietspomp nog?',
+ fr: 'TODO: fr'
+ }),
+ operational: new T({
+ en: 'The bike pump is operational',
+ nl: 'De fietspomp werkt nog',
+ fr: 'TODO: fr'
+ }),
+ broken: new T({en: 'The bike pump is broken', nl: 'De fietspomp is kapot', fr: 'TODO: fr'})
+ },
+ valves: {
+ question: new T({
+ en: 'What valves are supported?',
+ nl: 'Welke ventielen werken er met de pomp?',
+ fr: 'TODO: fr'
+ }),
+ default: new T({
+ en: 'There is a default head, so Dunlop, Sclaverand and auto',
+ nl: 'Er is een standaard aansluiting, die dus voor Dunlop, Sclaverand en auto\'s werkt',
+ fr: 'TODO: fr'
+ }),
+ dunlop: new T({en: 'Only Dunlop', nl: 'Enkel Dunlop', fr: 'TODO: fr'}),
+ sclaverand: new T({
+ en: 'Only Sclaverand (also known as Presta)',
+ nl: 'Enkel Sclaverand (ook gekend als Presta)',
+ fr: 'TODO: fr'
+ }),
+ auto: new T({en: 'Only for cars', nl: 'Enkel voor auto\'s', fr: 'TODO: fr'}),
+ render: new T({
+ en: 'This pump supports the following valves: {valves}',
+ nl: 'Deze pomp werkt met de volgende ventielen: {valves}',
+ fr: 'TODO: fr'
+ }),
+ template: new T({
+ en: 'Some other valve(s): $$$',
+ nl: 'Een ander type ventiel(en): $$$',
+ fr: 'TODO: fr'
+ })
+ },
+ chain: {
+ question: new T({
+ en: 'Does this bike station have a special tool to repair your bike chain?',
+ nl: 'Heeft dit fietsstation een speciale reparatieset voor je ketting?',
+ fr: 'TODO: fr'
+ }),
+ yes: new T({
+ en: 'There is a chain tool',
+ nl: 'Er is een reparatieset voor je ketting',
+ fr: 'TODO: fr'
+ }),
+ no: new T({
+ en: 'There is no chain tool',
+ nl: 'Er is geen reparatieset voor je ketting',
+ fr: 'TODO: fr'
+ }),
+ },
+ operator: {
+ render: new T({
+ en: 'This bike station is operated by {operator}',
+ nl: 'Dit fietsstation wordt beheerd door {operator}',
+ fr: 'TODO: fr'
+ }),
+ template: new T({en: 'A different operator: $$$', nl: 'Een andere beheerder: $$$', fr: 'TODO: fr'}),
+ question: new T({
+ en: 'Who operates this bike station (name of university, shop, city...)?',
+ nl: 'Wie beheert dit fietsstation (naam universiteit, winkel, stad...)?',
+ fr: 'TODO: fr'
+ }),
+ private: new T({
+ en: 'Operated by a private person',
+ nl: 'Wordt beheerd door een privépersoon',
+ fr: 'TODO: fr'
+ }),
+ },
+ services: {
+ question: new T({
+ en: 'Which services are available at this bike station?',
+ nl: 'Welke functies biedt dit fietsstation?',
+ fr: 'TODO: fr'
+ }),
+ pump: new T({
+ en: 'There is only a pump available',
+ nl: 'Er is enkel een pomp beschikbaar',
+ fr: 'TODO: fr'
+ }),
+ tools: new T({
+ en: 'There are only tools (screwdrivers, pliers...) available',
+ nl: 'Er is enkel gereedschap beschikbaar (schroevendraaier, tang...)',
+ fr: 'TODO: fr'
+ }),
+ both: new T({
+ en: 'There are both tools and a pump available',
+ nl: 'Er is zowel een pomp als gereedschap beschikbaar',
+ fr: 'TODO: fr'
+ }),
+ },
+ stand: {
+ question: new T({
+ en: 'Does this bike station have a hook to suspend your bike with or a stand to elevate it?',
+ nl: 'Heeft dit fietsstation een haak of standaard om je fiets op te hangen/zetten?',
+ fr: 'TODO: fr'
+ }),
+ yes: new T({en: 'There is a hook or stand', nl: 'Er is een haak of standaard', fr: 'TODO: fr'}),
+ no: new T({en: 'There is no hook or stand', nl: 'Er is geen haak of standaard', fr: 'TODO: fr'}),
+ }
+ },
+ shop: {
+ name: new T({en: 'bike shop', nl: 'fietswinkel', fr: 'TODO: fr'}),
+
+ title: new T({en: 'Bike shop', nl: 'Fietszaak', fr: 'TODO: fr'}),
+ titleRepair: new T({en: 'Bike repair', nl: 'Fietsenmaker', fr: 'TODO: fr'}),
+ titleShop: new T({en: 'Bike repair/shop', nl: 'Fietswinkel', fr: 'TODO: fr'}),
+
+ titleNamed: new T({en: 'Bike repair/shop', nl: 'Fietszaak {name}', fr: 'TODO: fr'}),
+ titleRepairNamed: new T({en: 'Bike shop', nl: 'Fietsenmaker {name}', fr: 'TODO: fr'}),
+ titleShopNamed: new T({en: 'Bike repair/shop', nl: 'Fietswinkel {name}', fr: 'TODO: fr'}),
+
+
+
+ retail: {
+ question: new T({
+ en: 'Does this shop sell bikes?',
+ nl: 'Verkoopt deze winkel fietsen?',
+ fr: 'TODO: fr'
+ }),
+ yes: new T({en: 'This shop sells bikes', nl: 'Deze winkel verkoopt fietsen', fr: 'TODO: fr'}),
+ no: new T({
+ en: 'This shop doesn\'t sell bikes',
+ nl: 'Deze winkel verkoopt geen fietsen',
+ fr: 'TODO: fr'
+ }),
+ },
+ repair: {
+ question: new T({
+ en: 'Does this shop repair bikes?',
+ nl: 'Verkoopt deze winkel fietsen?',
+ fr: 'TODO: fr'
+ }),
+ yes: new T({en: 'This shop repairs bikes', nl: 'Deze winkel herstelt fietsen', fr: 'TODO: fr'}),
+ no: new T({
+ en: 'This shop doesn\'t repair bikes',
+ nl: 'Deze winkel herstelt geen fietsen',
+ fr: 'TODO: fr'
+ }),
+ sold: new T({en: 'This shop only repairs bikes bought here', nl: 'Deze winkel herstelt enkel fietsen die hier werden gekocht', fr: 'TODO: fr'}),
+ brand: new T({en: 'This shop only repairs bikes of a certain brand', nl: 'Deze winkel herstelt enkel fietsen van een bepaald merk', fr: 'TODO: fr'}),
+ },
+ rental: {
+ question: new T({
+ en: 'Does this shop rent out bikes?',
+ nl: 'Verhuurt deze winkel fietsen?',
+ fr: 'TODO: fr'
+ }),
+ yes: new T({en: 'This shop rents out bikes', nl: 'Deze winkel verhuurt fietsen', fr: 'TODO: fr'}),
+ no: new T({
+ en: 'This shop doesn\'t rent out bikes',
+ nl: 'Deze winkel verhuurt geen fietsen',
+ fr: 'TODO: fr'
+ }),
+ },
+ pump: {
+ question: new T({
+ en: 'Does this shop offer a bike pump for use by anyone?',
+ nl: 'Biedt deze winkel een fietspomp aan voor iedereen?',
+ fr: 'TODO: fr'
+ }),
+ yes: new T({
+ en: 'This shop offers a bike pump for anyone',
+ nl: 'Deze winkel biedt geen fietspomp aan voor eender wie',
+ fr: 'TODO: fr'
+ }),
+ no: new T({
+ en: 'This shop doesn\'t offer a bike pump for anyone',
+ nl: 'Deze winkel biedt een fietspomp aan voor iedereen',
+ fr: 'TODO: fr'
+ })
+ },
+ qName: {
+ question: new T({en: 'What is the name of this bicycle shop?', nl: 'Wat is de naam van deze fietszaak?', fr: 'TODO: fr'}),
+ render: new T({en: 'This bicycle shop is called {name}', nl: 'Deze fietszaak heet {name} ', fr: 'TODO: fr'}),
+ template: new T({en: 'This bicycle shop is called: $$$', nl: 'Deze fietszaak heet: $$$ ', fr: 'TODO: fr'})
+ },
+ secondHand: {
+ question: new T({en: 'Does this shop sell second-hand bikes?', nl: 'Verkoopt deze winkel tweedehands fietsen?', fr: 'TODO: fr'}),
+ yes: new T({en: 'This shop sells second-hand bikes', nl: 'Deze winkel verkoopt tweedehands fietsen', fr: 'TODO: fr'}),
+ no: new T({en: 'This shop doesn\'t sell second-hand bikes', nl: 'Deze winkel verkoopt geen tweedehands fietsen', fr: 'TODO: fr'}),
+ only: new T({en: 'This shop only sells second-hand bikes', nl: 'Deze winkel verkoopt enkel tweedehands fietsen', fr: 'TODO: fr'}),
+ },
+ diy: {
+ question: new T({en: 'Are there tools here to repair your own bike?', nl: 'Biedt deze winkel gereedschap aan om je fiets zelf te herstellen?', fr: 'TODO: fr'}),
+ yes: new T({en: 'This shop offers tools for DIY repair', nl: 'Deze winkel biedt gereedschap aan om je fiets zelf te herstellen', fr: 'TODO: fr'}),
+ no: new T({en: 'This shop doesn\'t offer tools for DIY repair', nl: 'Deze winkel biedt geen gereedschap aan om je fiets zelf te herstellen', fr: 'TODO: fr'}),
+ }
+ }
+ },
+ image: {
+ addPicture: new T({en: 'Add picture', nl: 'Voeg foto toe', fr: 'TODO: fr'}),
+ uploadingPicture: new T({
+ en: 'Uploading your picture...',
+ nl: 'Bezig met een foto te uploaden...',
+ fr: 'TODO: fr'
+ }),
+ pleaseLogin: new T({
+ en: 'Please login to add a picure or to answer questions',
+ nl: 'Gelieve je aan te melden om een foto toe te voegen of vragen te beantwoorden',
+ fr: 'TODO: fr'
+ }),
+ willBePublished: new T({
+ en: 'Your picture will be published: ',
+ nl: 'Jouw foto wordt gepubliceerd: ',
+ fr: 'TODO: fr'
+ }),
+ cco: new T({en: 'in the public domain', nl: 'in het publiek domein', fr: 'TODO: fr'}),
+ ccbs: new T({en: 'under the CC-BY-SA-license', nl: 'onder de CC-BY-SA-licentie', fr: 'TODO: fr'}),
+ ccb: new T({en: 'under the CC-BY-license', nl: 'onder de CC-BY-licentie', fr: 'TODO: fr'})
+ },
+ centerMessage: {
+ loadingData: new T({en: 'Loading data...', nl: 'Data wordt geladen...', fr: 'TODO: fr'}),
+ zoomIn: new T({
+ en: 'Zoom in to view or edit the data',
+ nl: 'Zoom in om de data te zien en te bewerken',
+ fr: 'TODO: fr'
+ }),
+ ready: new T({en: 'Done!', nl: 'Klaar!', fr: 'TODO: fr'}),
+ },
+ general: {
+ loginWithOpenStreetMap: new T({en: "Login with OpenStreetMap", nl: "Aanmelden met OpenStreetMap"}),
+ getStarted: new T({
+ en: "Login with OpenStreetMap or make a free account to get started ",
+ nl: "Meld je aan met je OpenStreetMap-account of maak snel en gratis een account om te beginnen/a>",
+ }),
+ welcomeBack: new T({
+ en: "You are logged in, welcome back!",
+ nl: "Je bent aangemeld. Welkom terug!"
+ }),
+ search: {
+ search: new Translation({
+ en: "Search a location",
+ nl: "Zoek naar een locatie"
+ }),
+ searching: new Translation({
+ en: "Searching...",
+ nl: "Aan het zoeken..."
+ }),
+ nothing: new Translation({
+ en: "Nothing found...",
+ nl: "Niet gevonden..."
+ }),
+ error: new Translation({
+ en: "Something went wrong...",
+ nl: "Niet gelukt..."
+ })
+
+ },
+ returnToTheMap: new T({
+ en: "Return to the map",
+ nl: "Naar de kaart"
+ }),
+ save: new T({
+ en: "Save",
+ nl: "Opslaan"
+ }),
+ cancel: new T({
+ en: "Cancel",
+ nl: "Annuleren"
+ }),
+ skip: new T({
+ en: "Skip this question",
+ nl: "Vraag overslaan"
+ }),
+ oneSkippedQuestion: new T({
+ en: "One question is skipped",
+ nl: "Een vraag is overgeslaan"
+ }),
+ skippedQuestions: new T({
+ en: "Some questions are skipped",
+ nl: "Sommige vragen zijn overgeslaan"
+ })
+ }
+ }
+
+ public static W(s: string | UIElement): UIElement {
+ if (s instanceof UIElement) {
+ return s;
+ }
+ return new FixedUiElement(s);
+ }
+
+}
diff --git a/assets/bike/pump_broken.svg b/assets/bike/broken_pump.svg
similarity index 95%
rename from assets/bike/pump_broken.svg
rename to assets/bike/broken_pump.svg
index 2dcc14cbc..38868a504 100644
--- a/assets/bike/pump_broken.svg
+++ b/assets/bike/broken_pump.svg
@@ -9,9 +9,10 @@
-
+
-
+
+
diff --git a/assets/bike/cyclofix.jpeg b/assets/bike/cyclofix.jpeg
new file mode 100644
index 000000000..5a98a4080
Binary files /dev/null and b/assets/bike/cyclofix.jpeg differ
diff --git a/assets/bike/drinking_water.svg b/assets/bike/drinking_water.svg
new file mode 100644
index 000000000..4962afe3a
--- /dev/null
+++ b/assets/bike/drinking_water.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/bike/parking.svg b/assets/bike/parking.svg
index b095bc15b..ee2fb7214 100644
--- a/assets/bike/parking.svg
+++ b/assets/bike/parking.svg
@@ -2,85 +2,10 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/assets/bike/parking_old.svg b/assets/bike/parking_old.svg
new file mode 100644
index 000000000..b095bc15b
--- /dev/null
+++ b/assets/bike/parking_old.svg
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-02.svg b/assets/walkbybrussels/icons_wbb-02.svg
new file mode 100644
index 000000000..56d7eb4fc
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-02.svg
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-03.svg b/assets/walkbybrussels/icons_wbb-03.svg
new file mode 100644
index 000000000..333d222a0
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-03.svg
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-04.svg b/assets/walkbybrussels/icons_wbb-04.svg
new file mode 100644
index 000000000..a0f490e49
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-04.svg
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-05.svg b/assets/walkbybrussels/icons_wbb-05.svg
new file mode 100644
index 000000000..2d2d57d82
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-05.svg
@@ -0,0 +1,63 @@
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-06.svg b/assets/walkbybrussels/icons_wbb-06.svg
new file mode 100644
index 000000000..ad76c80ee
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-06.svg
@@ -0,0 +1,40 @@
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-07.svg b/assets/walkbybrussels/icons_wbb-07.svg
new file mode 100644
index 000000000..6ffdd62a4
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-07.svg
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-08.svg b/assets/walkbybrussels/icons_wbb-08.svg
new file mode 100644
index 000000000..a9fa40301
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-08.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-09.svg b/assets/walkbybrussels/icons_wbb-09.svg
new file mode 100644
index 000000000..ae5f0fb6e
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-09.svg
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-10.svg b/assets/walkbybrussels/icons_wbb-10.svg
new file mode 100644
index 000000000..8eba3245e
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-10.svg
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-11.svg b/assets/walkbybrussels/icons_wbb-11.svg
new file mode 100644
index 000000000..3023f6a6b
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-11.svg
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-12.svg b/assets/walkbybrussels/icons_wbb-12.svg
new file mode 100644
index 000000000..46d447f3d
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-12.svg
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-13.svg b/assets/walkbybrussels/icons_wbb-13.svg
new file mode 100644
index 000000000..ac73497a6
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-13.svg
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-14.svg b/assets/walkbybrussels/icons_wbb-14.svg
new file mode 100644
index 000000000..aa08bad59
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-14.svg
@@ -0,0 +1,27 @@
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-15.svg b/assets/walkbybrussels/icons_wbb-15.svg
new file mode 100644
index 000000000..03964b8c3
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-15.svg
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-16.svg b/assets/walkbybrussels/icons_wbb-16.svg
new file mode 100644
index 000000000..773759e06
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-16.svg
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-17.svg b/assets/walkbybrussels/icons_wbb-17.svg
new file mode 100644
index 000000000..94a9ac144
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-17.svg
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-18.svg b/assets/walkbybrussels/icons_wbb-18.svg
new file mode 100644
index 000000000..273cf422f
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-18.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-19.svg b/assets/walkbybrussels/icons_wbb-19.svg
new file mode 100644
index 000000000..b5302e68c
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-19.svg
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb-20.svg b/assets/walkbybrussels/icons_wbb-20.svg
new file mode 100644
index 000000000..34b8da898
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb-20.svg
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/walkbybrussels/icons_wbb_Tekengebied 1.svg b/assets/walkbybrussels/icons_wbb_Tekengebied 1.svg
new file mode 100644
index 000000000..fba5612f6
--- /dev/null
+++ b/assets/walkbybrussels/icons_wbb_Tekengebied 1.svg
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/clean.sh b/clean.sh
new file mode 100755
index 000000000..4fac85da8
--- /dev/null
+++ b/clean.sh
@@ -0,0 +1,12 @@
+#! /bin/bash
+rm *.js
+rm Logic/*.js
+rm Logic/*.js
+rm Logic/*/*.js
+rm Logic/*/*/*.js
+rm UI/*.js
+rm UI/*/*.js
+rm UI/*/*/*.js
+rm Customizations/*.js
+rm Customizations/*/*.js
+rm Customizations/*/*/*.js
diff --git a/docs/Create a theme.md b/docs/Create a theme.md
new file mode 100644
index 000000000..778b98a2b
--- /dev/null
+++ b/docs/Create a theme.md
@@ -0,0 +1,14 @@
+# Create a theme
+
+
+
+A _layer_ is a set of features of a single kind, e.g. 'bookcases'.
+A _layout_ is a set of layers, e.g. 'bookcases' + 'nature reserves'. They are shown together with some text on the left. They can be switched with the query parameter 'layout' in the url (thus by going to 'index.html?layout=bookcases')
+
+If you want to make your own version of MapComplete, you create your own layout
+
+- Clone the repo
+- Build it
+- Go into 'Customazations/Layouts' and copy a file there (e.g. bookcases)
+- Change the text and layer selection
+- Create you layers
\ No newline at end of file
diff --git a/icons_wbb.zip b/icons_wbb.zip
new file mode 100644
index 000000000..63e72193a
Binary files /dev/null and b/icons_wbb.zip differ
diff --git a/index.css b/index.css
index 0dae28ff8..e091fbcba 100644
--- a/index.css
+++ b/index.css
@@ -238,6 +238,13 @@ form {
height: 1em;
}
+#language-select {
+ pointer-events: all;
+ cursor: pointer;
+ position: absolute;
+ margin-left: 2em;
+ margin-top: 3em;
+}
#messagesbox-wrapper {
}
@@ -351,23 +358,26 @@ form {
}
}
-
#to-the-map {
+ position: relative;
+}
+
+#to-the-map h2{
+ position: absolute;
height: 4em;
+
padding: 0.5em;
margin: 0;
- position: absolute;
- bottom: 0;
- right: 0;
-
padding-right: 2em;
+ padding-top: 1em;
+ text-align: center;
width: 100%;
- text-align: right;
color: white;
background-color: #7ebc6f;
cursor: pointer;
+
}
diff --git a/index.html b/index.html
index aec8f1169..bb7c26ec5 100644
--- a/index.html
+++ b/index.html
@@ -9,17 +9,22 @@
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossorigin=""/>
-
+
-
-
+
+
+
+
+ Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is
+ blocking it.
+
-
Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is blocking it.
@@ -28,6 +33,7 @@
Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is
blocking it.
+
diff --git a/index.ts b/index.ts
index efdf09c00..022e0c21a 100644
--- a/index.ts
+++ b/index.ts
@@ -11,7 +11,7 @@ import {Tag, TagUtils} from "./Logic/TagsFilter";
import {FilteredLayer} from "./Logic/FilteredLayer";
import {LayerUpdater} from "./Logic/LayerUpdater";
import {UIElement} from "./UI/UIElement";
-import {MessageBoxHandler} from "./UI/MessageBoxHandler";
+import {FullScreenMessageBoxHandler} from "./UI/FullScreenMessageBoxHandler";
import {Overpass} from "./Logic/Overpass";
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
import {GeoLocationHandler} from "./Logic/GeoLocationHandler";
@@ -24,6 +24,14 @@ import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
import { All } from "./Customizations/Layouts/All";
import {CheckBox} from "./UI/Base/CheckBox";
import { DrinkingWater } from "./Customizations/Layers/DrinkingWater";
+import Translations from "./UI/i18n/Translations";
+import Translation from "./UI/i18n/Translation";
+import Locale from "./UI/i18n/Locale";
+import {Layout, WelcomeMessage} from "./Customizations/Layout";
+import {DropDown} from "./UI/Input/DropDown";
+import {FixedInputElement} from "./UI/Input/FixedInputElement";
+import {FixedUiElement} from "./UI/Base/FixedUiElement";
+import ParkingType from "./Customizations/Questions/bike/ParkingType";
// --------------------- Read the URL parameters -----------------
@@ -85,24 +93,35 @@ if (paramDict.test) {
dryRun = paramDict.test === "true";
}
-const layoutToUse = AllKnownLayouts.allSets[defaultLayout];
+const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout];
console.log("Using layout: ", layoutToUse.name);
-document.title = layoutToUse.title;
+document.title = layoutToUse.title.InnerRender();
+Locale.language.addCallback(e => {
+ document.title = layoutToUse.title.InnerRender();
+})
// ----------------- Setup a few event sources -------------
+// const LanguageSelect = document.getElementById('language-select') as HTMLOptionElement
+// eLanguageSelect.addEventListener('selectionchange')
+
+
// The message that should be shown at the center of the screen
const centerMessage = new UIEventSource("");
// The countdown: if set to e.g. ten, it'll start counting down. When reaching zero, changes will be saved. NB: this is implemented later, not in the eventSource
const secondsTillChangesAreSaved = new UIEventSource(0);
-const leftMessage = new UIEventSource<() => UIElement>(undefined);
+// const leftMessage = new UIEventSource<() => UIElement>(undefined);
-const selectedElement = new UIEventSource(undefined);
+// This message is shown full screen on mobile devices
+const fullScreenMessage = new UIEventSource(undefined);
+
+// The latest element that was selected - used to generate the right UI at the right place
+const selectedElement = new UIEventSource<{feature: any}>(undefined);
const locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>({
@@ -114,9 +133,19 @@ const locationControl = new UIEventSource<{ lat: number, lon: number, zoom: numb
// ----------------- Prepare the important objects -----------------
+const osmConnection = new OsmConnection(dryRun);
+
+
+Locale.language.syncWith(osmConnection.GetPreference("language"));
+
+// @ts-ignore
+window.setLanguage = function (language: string) {
+ Locale.language.setData(language)
+}
+
+
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 #" + layoutToUse.name,
osmConnection, allElements);
@@ -139,9 +168,8 @@ const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement(
// ------------- Setup the layers -------------------------------
-const controls = {};
const addButtons: {
- name: string,
+ name: UIElement,
icon: string,
tags: Tag[],
layerToAddTo: FilteredLayer
@@ -154,9 +182,10 @@ let minZoom = 0;
for (const layer of layoutToUse.layers) {
- const generateInfo = (tagsES) => {
+ const generateInfo = (tagsES, feature) => {
return new FeatureInfoBox(
+ feature,
tagsES,
layer.title,
layer.elementsToShow,
@@ -169,10 +198,8 @@ for (const layer of layoutToUse.layers) {
const flayer = layer.asLayer(bm, allElements, changes, osmConnection.userDetails, selectedElement, generateInfo);
- controls[layer.name] = flayer.isDisplayed;
-
const addButton = {
- name: layer.name,
+ name: Translations.W(layer.name),
icon: layer.icon,
tags: layer.newElementTags,
layerToAddTo: flayer
@@ -189,8 +216,13 @@ const layerUpdater = new LayerUpdater(bm, minZoom, flayers);
// ------------------ Setup various UI elements ------------
+let languagePicker = new DropDown(" ", layoutToUse.supportedLanguages.map(lang => {
+ return {value: lang, shown: lang}
+ }
+), Locale.language).AttachTo("language-select");
-new StrayClickHandler(bm, selectedElement, leftMessage, () => {
+
+new StrayClickHandler(bm, selectedElement, fullScreenMessage, () => {
return new SimpleAddUI(bm.Location,
bm.LastClickLocation,
changes,
@@ -202,23 +234,27 @@ new StrayClickHandler(bm, selectedElement, leftMessage, () => {
);
/**
- * Show the questions and information for the selected element on the leftMessage
+ * Show the questions and information for the selected element on the fullScreen
*/
-selectedElement.addCallback((data) => {
+selectedElement.addCallback((feature) => {
+ const data = feature.feature.properties;
// Which is the applicable set?
for (const layer of layoutToUse.layers) {
const applicable = layer.overpassFilter.matches(TagUtils.proprtiesToKV(data));
if (applicable) {
// This layer is the layer that gives the questions
- leftMessage.setData(() =>
- new FeatureInfoBox(
- allElements.getElement(data.id),
- layer.title,
- layer.elementsToShow,
- changes,
- osmConnection.userDetails
- ));
+
+ const featureBox = new FeatureInfoBox(
+ feature.feature,
+ allElements.getElement(data.id),
+ layer.title,
+ layer.elementsToShow,
+ changes,
+ osmConnection.userDetails
+ );
+
+ fullScreenMessage.setData(featureBox);
break;
}
}
@@ -229,36 +265,28 @@ selectedElement.addCallback((data) => {
const pendingChanges = new PendingChanges(
changes, secondsTillChangesAreSaved,);
-new UserBadge(osmConnection.userDetails, pendingChanges, bm)
+new UserBadge(osmConnection.userDetails,
+ pendingChanges,
+ new FixedUiElement(""),
+ bm)
.AttachTo('userbadge');
new SearchAndGo(bm).AttachTo("searchbox");
new CollapseButton("messagesbox")
.AttachTo("collapseButton");
-
-var welcomeMessage = () => {
- return new VariableUiElement(
- osmConnection.userDetails.map((userdetails) => {
- var login = layoutToUse.gettingStartedPlzLogin;
- if (userdetails.loggedIn) {
- login = layoutToUse.welcomeBackMessage;
- }
- return "" +
- layoutToUse.welcomeMessage + login + layoutToUse.welcomeTail +
- "
";
- }),
- function () {
- osmConnection.registerActivateOsmAUthenticationClass()
- });
-}
-leftMessage.setData(welcomeMessage);
-welcomeMessage().AttachTo("messagesbox");
+new WelcomeMessage(layoutToUse, osmConnection).AttachTo("messagesbox");
+fullScreenMessage.setData(
+ new WelcomeMessage(layoutToUse, osmConnection)
+);
-var messageBox = new MessageBoxHandler(leftMessage, () => {
+new FullScreenMessageBoxHandler(fullScreenMessage, () => {
selectedElement.setData(undefined)
-});
+}).update();
+
+// fullScreenMessage.setData(generateWelcomeMessage());
+
new CenterMessageBox(
minZoom,
@@ -281,7 +309,6 @@ new GeoLocationHandler(bm).AttachTo("geolocate-button");
// --------------- Send a ping to start various action --------
locationControl.ping();
-messageBox.update();
// --------------- Setting up filter ui --------
@@ -297,4 +324,4 @@ for (let i = 0; i < flayers.length; i++) {
document.querySelector(`#filter__button`).addEventListener(`click`, e => {
document.querySelector(`#filter__popup`).classList.toggle(`filter__popup--show`)
-});
\ No newline at end of file
+});
diff --git a/test.ts b/test.ts
index b7e9bd39c..51ad56bcf 100644
--- a/test.ts
+++ b/test.ts
@@ -1,17 +1,32 @@
+import {DropDown} from "./UI/Input/DropDown";
+import Locale from "./UI/i18n/Locale";
+import Combine from "./UI/Base/Combine";
+import Translations from "./UI/i18n/Translations";
+import {TagRenderingOptions} from "./Customizations/TagRendering";
import {UIEventSource} from "./UI/UIEventSource";
+import {Tag} from "./Logic/TagsFilter";
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";
-import {ConfirmDialog} from "./UI/ConfirmDialog";
-import {Imgur} from "./Logic/Imgur";
-import {VariableUiElement} from "./UI/Base/VariableUIElement";
+import Translation from "./UI/i18n/Translation";
+
+console.log("Hello world")
+Locale.language.setData("en");
+let languagePicker = new DropDown("", ["en", "nl"].map(lang => {
+ return {value: lang, shown: lang}
+ }
+), Locale.language).AttachTo("maindiv");
-const html = new UIEventSource("Some text");
+let tags = new UIEventSource({
+ x:"y"
+})
-const uielement = new VariableUiElement(html);
-uielement.AttachTo("maindiv")
-
-window.setTimeout(() => {html.setData("Different text")}, 1000)
\ No newline at end of file
+new TagRenderingOptions({
+ mappings: [{k: new Tag("x","y"), txt: new Translation({en: "ENG", nl: "NED"})}]
+}).construct({
+ tags: tags,
+ changes: new Changes(
+ "cs",
+ new OsmConnection(true)
+ )
+}).AttachTo("extradiv")
\ No newline at end of file