diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts index b0dd76a..b5165c4 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 1065f43..138102c 100644 --- a/Customizations/LayerDefinition.ts +++ b/Customizations/LayerDefinition.ts @@ -88,7 +88,6 @@ export class LayerDefinition { } } = undefined) { if (options === undefined) { - console.log("No options!") return; } this.name = options.name; @@ -100,7 +99,6 @@ export class LayerDefinition { this.title = options.title; this.elementsToShow = options.elementsToShow; this.style = options.style; - console.log(this) } asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource, selectedElement: UIEventSource, diff --git a/Customizations/Layers/BikeParkings.ts b/Customizations/Layers/BikeParkings.ts index ccd737e..56fab00 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.txt; this.icon = "./assets/bike/parking.svg"; this.overpassFilter = new Tag("amenity", "bicycle_parking"); this.newElementTags = [ @@ -20,10 +23,10 @@ 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() ]; @@ -36,7 +39,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/BikeShop.ts b/Customizations/Layers/BikeShop.ts new file mode 100644 index 0000000..d1b1bea --- /dev/null +++ b/Customizations/Layers/BikeShop.ts @@ -0,0 +1,122 @@ +import {TagRenderingOptions} from "../TagRendering"; +import {LayerDefinition} from "../LayerDefinition"; +import {And, Tag} from "../../Logic/TagsFilter"; +import L from "leaflet"; +import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; +import {NameQuestion} from "../Questions/NameQuestion"; + +export class BikeShop extends LayerDefinition { + + + private readonly sellsBikes = new Tag("service:bicycle:retail", "yes"); + private readonly repairsBikes = new Tag("service:bicycle:repair", "yes"); + + constructor() { + super( + { + name: "bike shop or repair", + icon: "assets/bike/repair_shop.svg", + minzoom: 14, + overpassFilter: new Tag("shop", "bicycle"), + newElementTags: [new Tag("shop", "bicycle")] + } + ); + + this.title = new TagRenderingOptions({ + mappings: [ + {k: new And([new Tag("name", "*"), this.sellsBikes]), txt: "Bicycle shop {name}"}, + { + k: new And([new Tag("name", "*"), new Tag("service:bicycle:retail", "no")]), + txt: "Bicycle repair {name}", + }, + { + k: new And([new Tag("name", "*"), new Tag("service:bicycle:retail", "")]), + txt: "Bicycle repair {name}" + }, + + {k: this.sellsBikes, txt: "Bicycle shop"}, + {k: new Tag("service:bicycle:retail", "no"), txt: "Bicycle repair"}, + {k: new Tag("service:bicycle:retail", ""), txt: "Bicycle repair/shop"}, + ] + }) + + + this.elementsToShow = [ + new ImageCarouselWithUploadConstructor(), + new TagRenderingOptions({ + question: "What is the name of this bicycle shop?", + freeform: { + key: "name", + renderTemplate: "The name of this bicycle shop is {name}", + template: "The name of this bicycle shop is $$$" + } + }), + + new TagRenderingOptions({ + question: "Can one buy a bike here?", + mappings: [ + {k: this.sellsBikes, txt: "Bikes are sold here"}, + {k: new Tag("service:bicycle:retail", "no"), txt: "No bikes are sold here"}, + ] + }), + + new TagRenderingOptions({ + question: "Can one buy a new bike here?", + mappings: [ + {k: new Tag("service:bicycle:second_hand", "yes"), txt: "Second-hand bikes are sold here"}, + {k: new Tag("service:bicycle:second_hand", "only"), txt: "All bicycles sold here are second-hand"}, + {k: new Tag("service:bicycle:second_hand", "no"), txt: "Only brand new bikes are sold here"}, + ] + }).OnlyShowIf(this.sellsBikes), + + + new TagRenderingOptions({ + question: "Does this shop repair bicycles?", + mappings: [ + {k: this.repairsBikes, txt: "Bikes are repaired here, by the shop owner (for a fee)"}, + {k: new Tag("service:bicycle:repair", "only_sold"), txt: "Only bikes that were bought here, are repaired"}, + {k: new Tag("service:bicycle:repair", "brand"), txt: "Only bikes of a fixed brand are repaired here"}, + {k: new Tag("service:bicycle:repair", "no"), txt: "Bikes are not repaired here"}, + ] + }), + + new TagRenderingOptions({ + question: "Can one hire a new bike here?", + mappings: [ + {k: new Tag("service:bicycle:rental", "yes"), txt: "Bikes can be rented here"}, + {k: new Tag("service:bicycle:rental", "no"), txt: "Bikes cannot be rented here"}, + ] + }).OnlyShowIf(this.sellsBikes), + + new TagRenderingOptions({ + question: "Are there tools here so that one can repair their own bike?", + mappings: [ + {k: new Tag("service:bicycle:diy", "yes"), txt: "Tools for DIY are available here"}, + {k: new Tag("service:bicycle:diy", "no"), txt: "No tools for DIY are available here"}, + ] + }), + ] + + + this.style = (tags) => { + let icon = "assets/bike/repair_shop.svg"; + + if (this.sellsBikes.matchesProperties(tags)) { + icon = "assets/bike/shop.svg"; + } + + return { + color: "#ff0000", + icon: L.icon({ + iconUrl: icon, + iconSize: [50, 50], + iconAnchor: [25, 50] + }) + } + } + + + } + + +} \ No newline at end of file diff --git a/Customizations/Layers/BikeShops.ts b/Customizations/Layers/BikeShops.ts new file mode 100644 index 0000000..9e51887 --- /dev/null +++ b/Customizations/Layers/BikeShops.ts @@ -0,0 +1,73 @@ +import { LayerDefinition } from "../LayerDefinition"; +import Translations from "../../UI/i18n/Translations"; +import { 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.txt + this.icon = "./assets/bike/repair_shop.svg" + this.overpassFilter = new Tag("shop", "bicycle"); + this.newElementTags = [ + new Tag("shop", "bicycle"), + ] + this.maxAllowedOverlapPercentage = 10 + + this.minzoom = 13; + this.style = this.generateStyleFunction(); + this.title = new TagRenderingOptions({ + mappings: [ + {k: this.sellsBikes, txt: "Bicycle shop"}, + {k: new Tag("service:bicycle:retail", "no"), txt: Translations.t.cyclofix.shop.titleRepair}, + {k: new Tag("service:bicycle:retail", ""), txt: Translations.t.cyclofix.shop.title}, + ] + }) + + this.elementsToShow = [ + new ImageCarouselWithUploadConstructor(), + //new ParkingOperator(), + new ShopRetail(), + new ShopRental(), + new ShopRepair(), + new ShopPump(), + new ShopDiy(), + new ShopName(), + 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 89f79b3..4d4ae71 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.txt; this.icon = "./assets/wrench.svg"; this.overpassFilter = new And([ @@ -36,7 +37,7 @@ 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.txt) this.elementsToShow = [ new ImageCarouselWithUploadConstructor(), @@ -50,7 +51,7 @@ export default class BikeStations extends LayerDefinition { new PumpValves().OnlyShowIf(this.pump), new PumpOperational().OnlyShowIf(this.pump), - new BikeStationOperator(), + // new BikeStationOperator(), // new BikeStationBrand() DISABLED ]; } @@ -73,14 +74,19 @@ export default class BikeStations extends LayerDefinition { } } } else { - iconName = "repair_station.svg" + if (!self.pump.matchesProperties(properties)) { + iconName = "repair_station.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 25953f4..7a2311c 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 92e6338..7bdd632 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([ @@ -52,7 +52,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/Layout.ts b/Customizations/Layout.ts index 7c72927..eb13be3 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 a415c2e..54065b4 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 833150b..d833d6e 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 548981b..87753c0 100644 --- a/Customizations/Layouts/Cyclofix.ts +++ b/Customizations/Layouts/Cyclofix.ts @@ -1,30 +1,32 @@ import {Layout} from "../Layout"; import BikeParkings from "../Layers/BikeParkings"; import BikeServices from "../Layers/BikeStations"; +import BikeShops from "../Layers/BikeShops"; import {GhostBike} from "../Layers/GhostBike"; -import {DrinkingWater, DrinkingWaterLayer} from "../Layers/DrinkingWater"; +import Translations from "../../UI/i18n/Translations"; +import {DrinkingWater} from "../Layers/DrinkingWater"; +import {BikeShop} from "../Layers/BikeShop" +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 BikeShop(), 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 93514a1..21ba2ab 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 7f3d0f9..991a618 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 8302f98..c174bc6 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 8216fcb..78c0c7a 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 76a46dd..d355fa4 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 f93e8a1..4d72b6e 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 c6c585e..1598c0f 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 ca8fdc4..499b774 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 178fb53..d3f5100 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 83458a5..5c9d4e6 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 0000000..effd863 --- /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.txt, + renderTemplate: to.render.txt, + placeholder: Translations.t.cyclofix.freeFormPlaceholder.txt + }, + 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 4b5074f..fa8ab50 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 bc97a03..1ae416e 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 18b79e6..c87ba23 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.Render(), 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.Render()}, + {k: new Tag("manual", "no"), txt: to.electric.Render()} ] }); } diff --git a/Customizations/Questions/bike/PumpOperational.ts b/Customizations/Questions/bike/PumpOperational.ts index d7abe0f..a539b0a 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.Render(), 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.txt}, + {k: new Tag("service:bicycle:pump:operational_status",""), txt: to.operational.txt} ] }); } diff --git a/Customizations/Questions/bike/PumpValves.ts b/Customizations/Questions/bike/PumpValves.ts index ae9ebb4..800a9b3 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.Render(), mappings: [ { k: new Tag("valves", " sclaverand;schrader;dunlop"), - txt: "There is a default head, so Presta, Dunlop and Auto" + txt: to.default.Render() }, - {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.Render()}, + {k: new Tag("valves", "sclaverand"), txt: to.sclaverand.Render()}, + {k: new Tag("valves", "auto"), txt: to.auto.Render()}, ], 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.txt, + renderTemplate: to.render.txt } }); } diff --git a/Customizations/Questions/bike/ShopDiy.ts b/Customizations/Questions/bike/ShopDiy.ts new file mode 100644 index 0000000..d4e7714 --- /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.cylofix.shop.diy + super({ + priority: 5, + question: to.question.Render(), + mappings: [ + {k: new Tag(key, "yes"), txt: to.yes.Render()}, + {k: new Tag(key, "no"), txt: to.no.Render()}, + ] + }); + } +} diff --git a/Customizations/Questions/bike/ShopName.ts b/Customizations/Questions/bike/ShopName.ts new file mode 100644 index 0000000..7f849fc --- /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.cylofix.shop.qName + super({ + priority: 5, + question: to.question.Render(), + freeform: { + key: "name", + renderTemplate: to.render.txt, + template: to.template.txt + } + }) + } +} diff --git a/Customizations/Questions/bike/ShopPump.ts b/Customizations/Questions/bike/ShopPump.ts new file mode 100644 index 0000000..a64f13b --- /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.Render(), + mappings: [ + {k: new Tag(key, "yes"), txt: to.yes.Render()}, + {k: new Tag(key, "no"), txt: to.no.Render()}, + ] + }); + } +} diff --git a/Customizations/Questions/bike/ShopRental.ts b/Customizations/Questions/bike/ShopRental.ts new file mode 100644 index 0000000..7eabfd3 --- /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.Render(), + mappings: [ + {k: new Tag(key, "yes"), txt: to.yes.Render()}, + {k: new Tag(key, "no"), txt: to.no.Render()}, + ] + }); + } +} diff --git a/Customizations/Questions/bike/ShopRepair.ts b/Customizations/Questions/bike/ShopRepair.ts new file mode 100644 index 0000000..e3caa32 --- /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.Render(), + mappings: [ + {k: new Tag(key, "yes"), txt: to.yes.Render()}, + {k: new Tag(key, "only_sold"), txt: to.sold.Render()}, + {k: new Tag(key, "brand"), txt: to.brand.Render()}, + {k: new Tag(key, "no"), txt: to.no.Render()}, + ] + }); + } +} diff --git a/Customizations/Questions/bike/ShopRetail.ts b/Customizations/Questions/bike/ShopRetail.ts new file mode 100644 index 0000000..3ad3ea3 --- /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 0000000..90b9780 --- /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.cylofix.shop.secondHand + super({ + priority: 5, + question: to.question.Render(), + mappings: [ + {k: new Tag(key, "yes"), txt: to.yes.Render()}, + {k: new Tag(key, "no"), txt: to.no.Render()}, + {k: new Tag(key, "only"), txt: to.only.Render()}, + ] + }); + } +} diff --git a/Customizations/Questions/bike/StationBrand.ts b/Customizations/Questions/bike/StationBrand.ts index 61dbc44..b763bf6 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 eb5c36a..2d76275 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.Render(), 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.Render()}, + {k: new Tag("service:bicycle:chain_tool", "no"), txt: to.no.Render()}, ] }); } diff --git a/Customizations/Questions/bike/StationOperator.ts b/Customizations/Questions/bike/StationOperator.ts index 001f920..94f1efb 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.txt, + renderTemplate: to.render.txt, + placeholder: "organisatie" + } }); } } diff --git a/Customizations/Questions/bike/StationPumpTools.ts b/Customizations/Questions/bike/StationPumpTools.ts index be81c8b..5442eba 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 0b6fce1..be6ac4a 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 ea8ca32..dad40bf 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; + renderTemplate: string; + 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 }[], /** @@ -67,17 +79,11 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { freeform?: { key: string, template: string, renderTemplate: string - placeholder?: string, + 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 +91,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { */ tagsPreprocessor?: ((tags: any) => void) }) { + this.options = options; } @@ -129,29 +136,25 @@ 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: UIElement, priority?: number }[]; + private _renderMapping: { k: TagsFilter, txt: UIElement, priority?: number }[]; private _tagsPreprocessor?: ((tags: any) => any); private _freeform: { key: string, template: string, renderTemplate: string, - placeholder?: string, + 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,19 @@ 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, + 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 +188,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; @@ -204,94 +208,36 @@ class TagRendering extends UIElement implements TagDependantUIElement { 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: this.ApplyTemplate(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, 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,42 +251,128 @@ 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("edit") .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()+""; } }); // 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, + renderTemplate: string + 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 = freeform.template.split("$$$"); + return new InputElementWrapper(prepost[0], textField, prepost[1]); + } + + IsKnown(): boolean { const tags = TagUtils.proprtiesToKV(this._source.data); @@ -349,10 +381,28 @@ class TagRendering extends UIElement implements TagDependantUIElement { return true; } } - + return this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined; } + private CurrentValue(): TagsFilter { + console.log("Creating a current value...") + const tags = TagUtils.proprtiesToKV(this._source.data); + + for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) { + if (oneOnOneElement.k !== null && oneOnOneElement.k.matches(tags)) { + return oneOnOneElement.k; + } + } + if (this._freeform === undefined) { + return undefined; + } + + console.log("Got a freeform tag:", new Tag(this._freeform.key, this._source.data[this._freeform.key])) + return new Tag(this._freeform.key, this._source.data[this._freeform.key]); + } + + IsQuestioning(): boolean { if (this.IsKnown()) { return false; @@ -368,10 +418,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 +429,60 @@ 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.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 } } + } - 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 +493,23 @@ class TagRendering extends UIElement implements TagDependantUIElement { } + + Priority(): number { + return this._priority; + } + + private ApplyTemplate(template: string | UIElement): UIElement { + if (template instanceof UIElement) { + return template; + } + const tags = this._tagsPreprocessor(this._source.data); + 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 e0dd9ce..488ea50 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/Imgur.ts b/Logic/Imgur.ts index bfba380..b41929f 100644 --- a/Logic/Imgur.ts +++ b/Logic/Imgur.ts @@ -60,7 +60,6 @@ export class Imgur { } - console.log(data); const licenseInfo = new LicenseInfo(); licenseInfo.licenseShortName = data.license; diff --git a/Logic/LayerUpdater.ts b/Logic/LayerUpdater.ts index 408d756..5478e89 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 648d9bb..04d2979 100644 --- a/Logic/OsmConnection.ts +++ b/Logic/OsmConnection.ts @@ -123,6 +123,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/Overpass.ts b/Logic/Overpass.ts index 28efa0b..f9eafaf 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 3ffd646..0000000 --- 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 += - "" + - "" + - "
"; - 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 += - "" + - "" + - "
"; - 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 85ccc9d..8509fd4 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>, + 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/UI/AddButton.ts b/UI/AddButton.ts index ac0a343..3b493a9 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 3026729..817de11 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 "
" + "" + diff --git a/UI/Base/CheckBox.ts b/UI/Base/CheckBox.ts index 6fd9214..9cdb21e 100644 --- a/UI/Base/CheckBox.ts +++ b/UI/Base/CheckBox.ts @@ -3,15 +3,17 @@ import {UIEventSource} from "../UIEventSource"; export class CheckBox extends UIElement{ + private data: UIEventSource; constructor(data: UIEventSource) { super(data); + this.data = data; } protected InnerRender(): string { - return ""; + return "Current val: "+this.data.data; } } \ No newline at end of file diff --git a/UI/Base/Combine.ts b/UI/Base/Combine.ts new file mode 100644 index 0000000..12b4338 --- /dev/null +++ b/UI/Base/Combine.ts @@ -0,0 +1,31 @@ +import {UIElement} from "../UIElement"; +import Translations from "../i18n/Translations"; + +export default class Combine extends UIElement { + private uiElements: (string | UIElement)[]; + + constructor(uiElements: (string | UIElement)[]) { + super(undefined); + this.uiElements = uiElements; + } + + InnerRender(): string { + let elements = ""; + for (const element of this.uiElements) { + if (element instanceof UIElement) { + elements += element.Render(); + } else { + elements += element; + } + } + return elements; + } + + protected InnerUpdate(htmlElement: HTMLElement) { + for (const element of this.uiElements) { + if (element instanceof UIElement) { + element.Update(); + } + } + } +} \ No newline at end of file diff --git a/UI/Base/DropDownUI.ts b/UI/Base/DropDownUI.ts deleted file mode 100644 index 208ea85..0000000 --- a/UI/Base/DropDownUI.ts +++ /dev/null @@ -1,61 +0,0 @@ -import {UIEventSource} from "../UIEventSource"; -import {UIElement} from "../UIElement"; - -export class DropDownUI extends UIElement { - - selectedElement: UIEventSource - private _label: string; - private _values: { value: string; shown: string }[]; - - constructor(label: string, values: { value: string, shown: string }[], - selectedElement: UIEventSource = undefined) { - super(undefined); - this._label = label; - this._values = values; - this.selectedElement = selectedElement ?? new UIEventSource(values[0].value); - if(selectedElement.data === undefined){ - this.selectedElement.setData(values[0].value) - } - const self = this; - this.selectedElement.addCallback(() => { - self.InnerUpdate(); - }); - } - - - protected InnerRender(): string { - - let options = ""; - for (const value of this._values) { - options += "" - } - - return "" + - "" + - "" + - ""; - } - - InnerUpdate() { - const self = this; - const e = document.getElementById("dropdown-" + this.id); - if(e === null){ - return; - } - // @ts-ignore - if (this.selectedElement.data !== e.value) { - // @ts-ignore - e.value = this.selectedElement.data; - } - e.onchange = function () { - // @ts-ignore - const selectedValue = e.options[e.selectedIndex].value; - console.log("Putting data", selectedValue) - self.selectedElement.setData(selectedValue); - } - - } - -} \ No newline at end of file diff --git a/UI/Base/FixedUiElement.ts b/UI/Base/FixedUiElement.ts index 39fee94..0579092 100644 --- a/UI/Base/FixedUiElement.ts +++ b/UI/Base/FixedUiElement.ts @@ -5,10 +5,10 @@ export class FixedUiElement extends UIElement { constructor(html: string) { super(undefined); - this._html = html; + this._html = html ?? ""; } - protected InnerRender(): string { + InnerRender(): string { return this._html; } diff --git a/UI/Base/TextField.ts b/UI/Base/TextField.ts deleted file mode 100644 index ca0c4ea..0000000 --- a/UI/Base/TextField.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {UIElement} from "../UIElement"; -import {UIEventSource} from "../UIEventSource"; -import {UIInputElement} from "./UIInputElement"; - - -export class TextField extends UIInputElement { - - public value: UIEventSource = new UIEventSource(""); - /** - * Pings and has the value data - */ - public enterPressed = new UIEventSource(undefined); - private _placeholder: UIEventSource; - private _mapping: (string) => T; - - constructor(placeholder: UIEventSource, - mapping: ((string) => T)) { - super(placeholder); - this._placeholder = placeholder; - this._mapping = mapping; - } - - GetValue(): UIEventSource { - return this.value.map(this._mapping); - } - - protected InnerRender(): string { - return "
" + - "" + - "
"; - } - - InnerUpdate(htmlElement: HTMLElement) { - super.InnerUpdate(htmlElement); - const field = document.getElementById('text-' + this.id); - const self = this; - field.oninput = () => { - // @ts-ignore - self.value.setData(field.value); - }; - - field.addEventListener("keyup", function (event) { - if (event.key === "Enter") { - // @ts-ignore - self.enterPressed.setData(field.value); - } - }); - - - } - - Clear() { - const field = document.getElementById('text-' + this.id); - if (field !== undefined) { - // @ts-ignore - field.value = ""; - } - } -} \ No newline at end of file diff --git a/UI/Base/UIInputElement.ts b/UI/Base/UIInputElement.ts deleted file mode 100644 index fd4a242..0000000 --- a/UI/Base/UIInputElement.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {UIElement} from "../UIElement"; -import {UIEventSource} from "../UIEventSource"; - -export abstract class UIInputElement extends UIElement{ - - abstract GetValue() : UIEventSource; - -} \ No newline at end of file diff --git a/UI/Base/UIRadioButton.ts b/UI/Base/UIRadioButton.ts deleted file mode 100644 index 2413073..0000000 --- a/UI/Base/UIRadioButton.ts +++ /dev/null @@ -1,109 +0,0 @@ -import {UIElement} from "../UIElement"; -import {UIEventSource} from "../UIEventSource"; -import {UIInputElement} from "./UIInputElement"; - -export class UIRadioButton extends UIInputElement { - - public readonly SelectedElementIndex: UIEventSource - = new UIEventSource(null); - - private readonly _elements: UIEventSource - private _selectFirstAsDefault: boolean; - private _valueMapping: (i: number) => T; - - constructor(elements: UIEventSource, - valueMapping: ((i: number) => T), - selectFirstAsDefault = true) { - super(elements); - this._elements = elements; - this._selectFirstAsDefault = selectFirstAsDefault; - const self = this; - this._valueMapping = valueMapping; - this.SelectedElementIndex.addCallback(() => { - self.InnerUpdate(undefined); - }) - } - - GetValue(): UIEventSource { - return this.SelectedElementIndex.map(this._valueMapping); - } - - - private IdFor(i) { - return 'radio-' + this.id + '-' + i; - } - - protected InnerRender(): string { - - let body = ""; - let i = 0; - for (const el of this._elements.data) { - const htmlElement = - '' + - '' + - '
'; - body += htmlElement; - - i++; - } - - return "
" + body + "
"; - } - - InnerUpdate(htmlElement: HTMLElement) { - const self = this; - - function checkButtons() { - for (let i = 0; i < self._elements.data.length; i++) { - const el = document.getElementById(self.IdFor(i)); - // @ts-ignore - if (el.checked) { - self.SelectedElementIndex.setData(i); - } - } - } - - - const el = document.getElementById(this.id); - el.addEventListener("change", - function () { - checkButtons(); - } - ); - - if (this.SelectedElementIndex.data == null) { - if (this._selectFirstAsDefault) { - const el = document.getElementById(this.IdFor(0)); - if (el) { - // @ts-ignore - el.checked = true; - checkButtons(); - } - } - } else { - - // We check that what is selected matches the previous rendering - var checked = -1; - var expected = this.SelectedElementIndex.data; - if (expected) { - - for (let i = 0; i < self._elements.data.length; i++) { - const el = document.getElementById(self.IdFor(i)); - // @ts-ignore - if (el.checked) { - checked = i; - } - } - if (expected != checked) { - const el = document.getElementById(this.IdFor(expected)); - // @ts-ignore - el.checked = true; - } - } - } - - - } - - -} \ No newline at end of file diff --git a/UI/Base/UIRadioButtonWithOther.ts b/UI/Base/UIRadioButtonWithOther.ts deleted file mode 100644 index 806764d..0000000 --- a/UI/Base/UIRadioButtonWithOther.ts +++ /dev/null @@ -1,72 +0,0 @@ -import {UIInputElement} from "./UIInputElement"; -import {UIEventSource} from "../UIEventSource"; -import {UIRadioButton} from "./UIRadioButton"; -import {UIElement} from "../UIElement"; -import {TextField} from "./TextField"; -import {FixedUiElement} from "./FixedUiElement"; - - -export class UIRadioButtonWithOther extends UIInputElement { - private readonly _radioSelector: UIRadioButton; - private readonly _freeformText: TextField; - private readonly _value: UIEventSource = new UIEventSource(undefined) - - constructor(choices: UIElement[], - otherChoiceTemplate: string, - placeholder: string, - choiceToValue: ((i: number) => T), - stringToValue: ((string: string) => T)) { - super(undefined); - const self = this; - - this._freeformText = new TextField( - new UIEventSource(placeholder), - stringToValue); - - - const otherChoiceElement = new FixedUiElement( - otherChoiceTemplate.replace("$$$", this._freeformText.Render())); - choices.push(otherChoiceElement); - - this._radioSelector = new UIRadioButton(new UIEventSource(choices), - (i) => { - if (i === undefined || i === null) { - return undefined; - } - if (i + 1 >= choices.length) { - return this._freeformText.GetValue().data - } - return choiceToValue(i); - }, - false); - - this._radioSelector.GetValue().addCallback( - (i) => { - self._value.setData(i); - }); - this._freeformText.GetValue().addCallback((str) => { - self._value.setData(str); - } - ); - this._freeformText.onClick(() => { - self._radioSelector.SelectedElementIndex.setData(choices.length - 1); - }) - - - } - - GetValue(): UIEventSource { - return this._value; - } - - protected InnerRender(): string { - return this._radioSelector.Render(); - } - - InnerUpdate(htmlElement: HTMLElement) { - super.InnerUpdate(htmlElement); - this._radioSelector.Update(); - this._freeformText.Update(); - } - -} \ No newline at end of file diff --git a/UI/Base/VariableUIElement.ts b/UI/Base/VariableUIElement.ts index b1f7f05..8afab13 100644 --- a/UI/Base/VariableUIElement.ts +++ b/UI/Base/VariableUIElement.ts @@ -12,16 +12,8 @@ export class VariableUiElement extends UIElement { } - protected InnerRender(): string { + InnerRender(): string { return this._html.data; } - InnerUpdate(htmlElement: HTMLElement) { - super.InnerUpdate(htmlElement); - if(this._innerUpdate !== undefined){ - this._innerUpdate(htmlElement); - } - } - - } \ No newline at end of file diff --git a/UI/CenterMessageBox.ts b/UI/CenterMessageBox.ts index 8bbb7a0..ff19db4 100644 --- a/UI/CenterMessageBox.ts +++ b/UI/CenterMessageBox.ts @@ -1,6 +1,7 @@ import {UIElement} from "./UIElement"; import {UIEventSource} from "./UIEventSource"; import {OsmConnection} from "../Logic/OsmConnection"; +import Translations from "./i18n/Translations"; export class CenterMessageBox extends UIElement { @@ -34,17 +35,17 @@ export class CenterMessageBox extends UIElement { } - protected InnerRender(): string { + InnerRender(): string { if (this._centermessage.data != "") { return this._centermessage.data; } if (this._queryRunning.data) { - return "Data wordt geladen..."; + return Translations.t.centerMessage.loadingData.txt; } else if (this._zoomInMore.data) { - return "Zoom in om de data te zien en te bewerken"; + return Translations.t.centerMessage.zoomIn.txt; } - return "Klaar!"; + return Translations.t.centerMessage.ready.txt; } diff --git a/UI/FeatureInfoBox.ts b/UI/FeatureInfoBox.ts index 441c63a..89d7bce 100644 --- a/UI/FeatureInfoBox.ts +++ b/UI/FeatureInfoBox.ts @@ -10,7 +10,7 @@ import {TagRenderingOptions} from "../Customizations/TagRendering"; import {OsmLink} from "../Customizations/Questions/OsmLink"; import {WikipediaLink} from "../Customizations/Questions/WikipediaLink"; import {And} from "../Logic/TagsFilter"; -import {TagDependantUIElement} from "../Customizations/UIElementConstructor"; +import {TagDependantUIElement, TagDependantUIElementConstructor} from "../Customizations/UIElementConstructor"; export class FeatureInfoBox extends UIElement { @@ -30,8 +30,8 @@ export class FeatureInfoBox extends UIElement { constructor( tagsES: UIEventSource, - title: TagRenderingOptions, - elementsToShow: TagRenderingOptions[], + title: TagRenderingOptions | UIElement, + elementsToShow: TagDependantUIElementConstructor[], changes: Changes, userDetails: UIEventSource ) { @@ -56,10 +56,14 @@ export class FeatureInfoBox extends UIElement { } ) - this._title = new TagRenderingOptions(title.options).construct(deps); - this._osmLink =new OsmLink().construct(deps); + if (title instanceof UIElement) { + this._title = title; + } else { + this._title = new TagRenderingOptions(title.options).construct(deps); + } + this._osmLink = new OsmLink().construct(deps); this._wikipedialink = new WikipediaLink().construct(deps); - + } @@ -115,4 +119,6 @@ export class FeatureInfoBox extends UIElement { ""; } + + } diff --git a/UI/MessageBoxHandler.ts b/UI/FullScreenMessageBoxHandler.ts similarity index 62% rename from UI/MessageBoxHandler.ts rename to UI/FullScreenMessageBoxHandler.ts index 5ce7bdb..88f6871 100644 --- a/UI/MessageBoxHandler.ts +++ b/UI/FullScreenMessageBoxHandler.ts @@ -1,14 +1,16 @@ -/** - * Keeps 'messagebox' and 'messageboxmobile' in sync, shows a 'close' button on the latter one - */ import {UIEventSource} from "./UIEventSource"; import {UIElement} from "./UIElement"; import {VariableUiElement} from "./Base/VariableUIElement"; +import Translations from "./i18n/Translations"; -export class MessageBoxHandler { - private _uielement: UIEventSource<() => UIElement>; +/** + * Handles the full screen popup on mobile + */ +export class FullScreenMessageBoxHandler { + + private _uielement: UIEventSource; - constructor(uielement: UIEventSource<() => UIElement>, + constructor(uielement: UIEventSource, onClear: (() => void)) { this._uielement = uielement; this.listenTo(uielement); @@ -22,14 +24,13 @@ export class MessageBoxHandler { } } - new VariableUiElement(new UIEventSource("

Naar de kaart

"), - () => { - document.getElementById("to-the-map").onclick = function () { - uielement.setData(undefined); - onClear(); - } - } - ).AttachTo("to-the-map"); + Translations.t.general.returnToTheMap + .onClick(() => { + console.log("Clicked 'return to the map'") + uielement.setData(undefined); + onClear(); + }) + .AttachTo("to-the-map-h2"); } @@ -45,7 +46,6 @@ export class MessageBoxHandler { update() { const wrapper = document.getElementById("messagesboxmobilewrapper"); const gen = this._uielement.data; - console.log("Generator: ", gen); if (gen === undefined) { wrapper.classList.add("hidden") if (location.hash !== "") { @@ -55,12 +55,8 @@ export class MessageBoxHandler { } location.hash = "#element" wrapper.classList.remove("hidden"); - /* gen() - ?.HideOnEmpty(true) - ?.AttachTo("messagesbox") - ?.Activate();*/ - gen() + gen ?.HideOnEmpty(true) ?.AttachTo("messagesboxmobile") ?.Activate(); diff --git a/UI/Image/ImageCarousel.ts b/UI/Image/ImageCarousel.ts index 9d4e1fd..b3f40d8 100644 --- a/UI/Image/ImageCarousel.ts +++ b/UI/Image/ImageCarousel.ts @@ -23,10 +23,10 @@ export class ImageCarouselConstructor implements TagDependantUIElementConstructo return 0; } - construct(tags: UIEventSource, changes: Changes): TagDependantUIElement { - return new ImageCarousel(tags, changes); + construct(dependencies: { tags: UIEventSource, changes: Changes }): TagDependantUIElement { + return new ImageCarousel(dependencies.tags, dependencies.changes); } - + } export class ImageCarousel extends TagDependantUIElement { diff --git a/UI/Image/ImageCarouselWithUpload.ts b/UI/Image/ImageCarouselWithUpload.ts index 38ebe20..8ce0225 100644 --- a/UI/Image/ImageCarouselWithUpload.ts +++ b/UI/Image/ImageCarouselWithUpload.ts @@ -34,14 +34,14 @@ class ImageCarouselWithUpload extends TagDependantUIElement { const changes = dependencies.changes; this._imageElement = new ImageCarousel(tags, changes); const userDetails = changes.login.userDetails; - const license = changes.login.GetPreference( "mapcomplete-pictures-license"); + const license = changes.login.GetPreference( "pictures-license"); this._pictureUploader = new OsmImageUploadHandler(tags, userDetails, license, changes, this._imageElement.slideshow).getUI(); } - protected InnerRender(): string { + InnerRender(): string { return this._imageElement.Render() + this._pictureUploader.Render(); } diff --git a/UI/ImageUploadFlow.ts b/UI/ImageUploadFlow.ts index 6542824..110fcd0 100644 --- a/UI/ImageUploadFlow.ts +++ b/UI/ImageUploadFlow.ts @@ -3,13 +3,13 @@ import {UIEventSource} from "./UIEventSource"; import $ from "jquery" import {Imgur} from "../Logic/Imgur"; import {UserDetails} from "../Logic/OsmConnection"; -import {DropDownUI} from "./Base/DropDownUI"; +import {DropDown} from "./Input/DropDown"; import {VariableUiElement} from "./Base/VariableUIElement"; +import Translations from "./i18n/Translations"; export class ImageUploadFlow extends UIElement { private _licensePicker: UIElement; private _selectedLicence: UIEventSource; - private _licenseExplanation: UIElement; private _isUploading: UIEventSource = new UIEventSource(0) private _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) }; private _userdetails: UIEventSource; @@ -31,61 +31,49 @@ export class ImageUploadFlow extends UIElement { this._uploadOptions = uploadOptions; this.ListenTo(this._isUploading); - const licensePicker = new DropDownUI("Jouw foto wordt gepubliceerd ", - + const licensePicker = new DropDown(Translations.t.image.willBePublished, [ - {value: "CC0", shown: "in het publiek domein"}, - {value: "CC-BY-SA 4.0", shown: "onder een CC-BY-SA-licentie"}, - {value: "CC-BY 4.0", shown: "onder een CC-BY-licentie"} + {value: "CC0", shown: Translations.t.image.cco}, + {value: "CC-BY-SA 4.0", shown: Translations.t.image.ccbs}, + {value: "CC-BY 4.0", shown: Translations.t.image.ccb} ], preferedLicense ); this._licensePicker = licensePicker; - this._selectedLicence = licensePicker.selectedElement; + this._selectedLicence = licensePicker.GetValue(); - const licenseExplanations = { - "CC-BY-SA 4.0": - "Creative Commonse met naamsvermelding en gelijk delen
" + - "Je foto mag door iedereen gratis gebruikt worden, als ze je naam vermelden én ze afgeleide werken met deze licentie en attributie delen.", - "CC-BY 4.0": - "Creative Commonse met naamsvermelding
" + - "Je foto mag door iedereen gratis gebruikt worden, als ze je naam vermelden", - "CC0": - "Geen copyright
Je foto mag door iedereen voor alles gebruikt worden" - } - this._licenseExplanation = new VariableUiElement( - this._selectedLicence.map((license) => { - return licenseExplanations[license] - }) - ); } - protected InnerRender(): string { + InnerRender(): string { if (!this._userdetails.data.loggedIn) { - return "
Gelieve je aan te melden om een foto toe te voegen of vragen te beantwoorden
"; + return `
${Translations.t.image.pleaseLogin.Render()}
`; } + + let uploadingMessage = ""; if (this._isUploading.data == 1) { - return "Bezig met een foto te uploaden..." + return `${Translations.t.image.uploadingPicture.Render()}` } if (this._isUploading.data > 0) { - return "Bezig met uploaden, nog " + this._isUploading.data + " foto's te gaan..." + uploadingMessage = "Uploading multiple pictures, " + this._isUploading.data + " left..." } return "" + "
" + - + "" + " extends InputElement { + + private readonly _label: UIElement; + private readonly _values: { value: T; shown: UIElement }[]; + + private readonly _value; + + constructor(label: string | UIElement, + values: { value: T, shown: string | UIElement }[], + value: UIEventSource = undefined) { + super(undefined); + this._value = value ?? new UIEventSource(undefined); + this._label = Translations.W(label); + this._values = values.map(v => { + return { + value: v.value, + shown: Translations.W(v.shown) + } + } + ); + for (const v of this._values) { + this.ListenTo(v.shown._source); + } + this.ListenTo(this._value) + + } + + GetValue(): UIEventSource { + return this._value; + } + + ShowValue(t: T): boolean { + if (!this.IsValid(t)) { + return false; + } + this._value.setData(t); + } + + IsValid(t: T): boolean { + for (const value of this._values) { + if (value.value === t) { + return true; + } + } + return false + } + + + InnerRender(): string { + if(this._values.length <=1){ + return ""; + } + + let options = ""; + for (let i = 0; i < this._values.length; i++) { + options += "" + + } + return "
" + + "" + + "" + + "
"; + } + + protected InnerUpdate(element) { + + + var e = document.getElementById("dropdown-" + this.id); + if(e === null){ + return; + } + const self = this; + e.onchange = (() => { + // @ts-ignore + var index = parseInt(e.selectedIndex); + self._value.setData(self._values[index].value); + + }); + + var t = this._value.data; + for (let i = 0; i < this._values.length ; i++) { + const value = this._values[i]; + if (value.value == t) { + // @ts-ignore + e.selectedIndex = i; + } + } + + } + +} \ No newline at end of file diff --git a/UI/Input/FixedInputElement.ts b/UI/Input/FixedInputElement.ts new file mode 100644 index 0000000..0cada25 --- /dev/null +++ b/UI/Input/FixedInputElement.ts @@ -0,0 +1,35 @@ +import {InputElement} from "./InputElement"; +import {UIEventSource} from "../UIEventSource"; +import {UIElement} from "../UIElement"; +import {FixedUiElement} from "../Base/FixedUiElement"; + + +export class FixedInputElement extends InputElement { + private rendering: UIElement; + private value: UIEventSource; + + constructor(rendering: UIElement | string, value: T) { + super(undefined); + this.value = new UIEventSource(value); + this.rendering = typeof (rendering) === 'string' ? new FixedUiElement(rendering) : rendering; + } + + GetValue(): UIEventSource { + return this.value; + } + + ShowValue(t: T): boolean { + return false; + } + + InnerRender(): string { + return this.rendering.Render(); + } + + IsValid(t: T): boolean { + return t == this.value.data; + } + + + +} \ No newline at end of file diff --git a/UI/Input/InputElement.ts b/UI/Input/InputElement.ts new file mode 100644 index 0000000..8278314 --- /dev/null +++ b/UI/Input/InputElement.ts @@ -0,0 +1,11 @@ +import {UIElement} from "../UIElement"; +import {UIEventSource} from "../UIEventSource"; +import {FixedUiElement} from "../Base/FixedUiElement"; + +export abstract class InputElement extends UIElement{ + + abstract GetValue() : UIEventSource; + + abstract IsValid(t: T) : boolean; + +} \ No newline at end of file diff --git a/UI/Input/InputElementWrapper.ts b/UI/Input/InputElementWrapper.ts new file mode 100644 index 0000000..905a948 --- /dev/null +++ b/UI/Input/InputElementWrapper.ts @@ -0,0 +1,41 @@ +import {InputElement} from "./InputElement"; +import {UIEventSource} from "../UIEventSource"; +import {UIElement} from "../UIElement"; +import {FixedUiElement} from "../Base/FixedUiElement"; + + +export class InputElementWrapper extends InputElement{ + private pre: UIElement ; + private input: InputElement; + private post: UIElement ; + + constructor( + pre: UIElement | string, + input: InputElement, + post: UIElement | string + + ) { + super(undefined); + this.pre = typeof(pre) === 'string' ? new FixedUiElement(pre) : pre + this.input = input; + this.post =typeof(post) === 'string' ? new FixedUiElement(post) : post + } + + + GetValue(): UIEventSource { + return this.input.GetValue(); + } + + ShowValue(t: T) { + return this.input.ShowValue(t); + } + + InnerRender(): string { + return this.pre.Render() + this.input.Render() + this.post.Render(); + } + + IsValid(t: T): boolean { + return this.input.IsValid(t); + } + +} \ No newline at end of file diff --git a/UI/Input/RadioButton.ts b/UI/Input/RadioButton.ts new file mode 100644 index 0000000..8d8b5cc --- /dev/null +++ b/UI/Input/RadioButton.ts @@ -0,0 +1,146 @@ +import {UIElement} from "../UIElement"; +import {UIEventSource} from "../UIEventSource"; +import {InputElement} from "./InputElement"; + +export class RadioButton extends InputElement { + + private readonly _selectedElementIndex: UIEventSource + = new UIEventSource(null); + + private value: UIEventSource; + private readonly _elements: InputElement[] + private _selectFirstAsDefault: boolean; + + + constructor(elements: InputElement[], + selectFirstAsDefault = true) { + super(undefined); + this._elements = elements; + this._selectFirstAsDefault = selectFirstAsDefault; + const self = this; + + + this.value = + UIEventSource.flatten(this._selectedElementIndex.map( + (selectedIndex) => { + if (selectedIndex !== undefined && selectedIndex !== null) { + return elements[selectedIndex].GetValue() + } + } + ), elements.map(e => e.GetValue())); + + this.value.addCallback((t) => { + self.ShowValue(t); + }) + + + for (let i = 0; i < elements.length; i++) { + // If an element is clicked, the radio button corresponding with it should be selected as well + elements[i].onClick(() => { + self._selectedElementIndex.setData(i); + }); + } + + } + + IsValid(t: T): boolean { + for (const inputElement of this._elements) { + if (inputElement.IsValid(t)) { + return true; + } + } + return false; + } + + GetValue(): UIEventSource { + return this.value; + } + + + private IdFor(i) { + return 'radio-' + this.id + '-' + i; + } + + InnerRender(): string { + + let body = ""; + let i = 0; + for (const el of this._elements) { + const htmlElement = + '' + + '' + + '
'; + body += htmlElement; + + i++; + } + + return "
" + body + "
"; + } + + public ShowValue(t: T): boolean { + if (t === undefined) { + return false; + } + if (!this.IsValid(t)) { + return false; + } + // We check that what is selected matches the previous rendering + for (let i = 0; i < this._elements.length; i++) { + const e = this._elements[i]; + if (e.IsValid(t)) { + this._selectedElementIndex.setData(i); + e.GetValue().setData(t); + const radio = document.getElementById(this.IdFor(i)); + // @ts-ignore + radio?.checked = true; + return; + } + + } + } + + InnerUpdate(htmlElement: HTMLElement) { + const self = this; + + function checkButtons() { + for (let i = 0; i < self._elements.length; i++) { + const el = document.getElementById(self.IdFor(i)); + // @ts-ignore + if (el.checked) { + self._selectedElementIndex.setData(i); + } + } + } + + + const el = document.getElementById(this.id); + el.addEventListener("change", + function () { + checkButtons(); + } + ); + if (this._selectedElementIndex.data !== null) { + const el = document.getElementById(this.IdFor(this._selectedElementIndex.data)); + if (el) { + // @ts-ignore + el.checked = true; + checkButtons(); + } + } else if (this._selectFirstAsDefault) { + this.ShowValue(this.value.data); + if (this._selectedElementIndex.data === null || this._selectedElementIndex.data === undefined) { + const el = document.getElementById(this.IdFor(0)); + if (el) { + // @ts-ignore + el.checked = true; + checkButtons(); + } + } + } + + + }; + + +} \ No newline at end of file diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts new file mode 100644 index 0000000..f025b28 --- /dev/null +++ b/UI/Input/TextField.ts @@ -0,0 +1,119 @@ +import {UIElement} from "../UIElement"; +import {UIEventSource} from "../UIEventSource"; +import {InputElement} from "./InputElement"; +import {FixedUiElement} from "../Base/FixedUiElement"; +import Translations from "../i18n/Translations"; + + +export class TextField extends InputElement { + + private value: UIEventSource; + private mappedValue: UIEventSource; + /** + * Pings and has the value data + */ + public enterPressed = new UIEventSource(undefined); + private _placeholder: UIElement; + private _fromString?: (string: string) => T; + private _toString: (t: T) => string; + + constructor(options: { + placeholder?: string | UIElement, + toString: (t: T) => string, + fromString: (string: string) => T, + value?: UIEventSource + }) { + super(undefined); + const self = this; + this.value = new UIEventSource(""); + + this.mappedValue = options?.value ?? new UIEventSource(undefined); + this.mappedValue.addCallback(() => self.InnerUpdate()); + + // @ts-ignore + this._fromString = options.fromString ?? ((str) => (str)) + this.value.addCallback((str) => this.mappedValue.setData(options.fromString(str))); + this.mappedValue.addCallback((t) => this.value.setData(options.toString(t))); + + + this._placeholder = Translations.W(options.placeholder ?? ""); + this.ListenTo(this._placeholder._source); + this._toString = options.toString ?? ((t) => ("" + t)); + + + this.mappedValue.addCallback((t) => { + if (t === undefined || t === null) { + return; + } + const field = document.getElementById('text-' + this.id); + if (field === undefined || field === null) { + return; + } + // @ts-ignore + field.value = options.toString(t); + }) + } + + GetValue(): UIEventSource { + return this.mappedValue; + } + + ShowValue(t: T): boolean { + if (!this.IsValid(t)) { + return false; + } + this.mappedValue.setData(t); + } + + InnerRender(): string { + return "
" + + "" + + "
"; + } + + InnerUpdate() { + const field = document.getElementById('text-' + this.id); + if (field === null) { + return; + } + const self = this; + field.oninput = () => { + // @ts-ignore + self.value.setData(field.value); + }; + + field.addEventListener("keyup", function (event) { + if (event.key === "Enter") { + // @ts-ignore + self.enterPressed.setData(field.value); + } + }); + + if (this.IsValid(this.mappedValue.data)) { + const expected = this._toString(this.mappedValue.data); + // @ts-ignore + if (field.value !== expected) { + // @ts-ignore + field.value = expected; + } + } + + + } + + IsValid(t: T): boolean { + if(t === undefined || t === null){ + return false; + } + const result = this._toString(t); + return result !== undefined && result !== null; + } + + Clear() { + const field = document.getElementById('text-' + this.id); + if (field !== undefined) { + // @ts-ignore + field.value = ""; + } + } +} \ No newline at end of file diff --git a/UI/PendingChanges.ts b/UI/PendingChanges.ts index e0411a5..cda6214 100644 --- a/UI/PendingChanges.ts +++ b/UI/PendingChanges.ts @@ -21,7 +21,7 @@ export class PendingChanges extends UIElement { }) } - protected InnerRender(): string { + InnerRender(): string { if (this._isSaving.data) { return "Saving"; } diff --git a/UI/SaveButton.ts b/UI/SaveButton.ts index 3bba390..a2728c4 100644 --- a/UI/SaveButton.ts +++ b/UI/SaveButton.ts @@ -1,5 +1,6 @@ import {UIEventSource} from "./UIEventSource"; import {UIElement} from "./UIElement"; +import Translations from "./i18n/Translations"; export class SaveButton extends UIElement { private _value: UIEventSource; @@ -12,14 +13,14 @@ export class SaveButton extends UIElement { this._value = value; } - protected InnerRender(): string { + InnerRender(): string { if (this._value.data === undefined || this._value.data === null || this._value.data === "" ) { - return "Opslaan" + return ""+Translations.t.general.save.Render()+"" } - return "Opslaan"; + return ""+Translations.t.general.save.Render()+""; } } \ No newline at end of file diff --git a/UI/SearchAndGo.ts b/UI/SearchAndGo.ts index 3916168..8e51f93 100644 --- a/UI/SearchAndGo.ts +++ b/UI/SearchAndGo.ts @@ -1,15 +1,26 @@ import {UIElement} from "./UIElement"; -import {TextField} from "./Base/TextField"; +import {TextField} from "./Input/TextField"; import {UIEventSource} from "./UIEventSource"; import {FixedUiElement} from "./Base/FixedUiElement"; import {Geocoding} from "../Logic/Geocoding"; import {Basemap} from "../Logic/Basemap"; +import {VariableUiElement} from "./Base/VariableUIElement"; +import Translation from "./i18n/Translation"; +import Locale from "./i18n/Locale"; +import Translations from "./i18n/Translations"; export class SearchAndGo extends UIElement { - private _placeholder = new UIEventSource("Zoek naar een locatie...") - private _searchField = new TextField(this._placeholder); + private _placeholder = new UIEventSource(Translations.t.general.search.search) + private _searchField = new TextField({ + placeholder: new VariableUiElement( + this._placeholder.map(uiElement => uiElement.InnerRender(), [Locale.language]) + ), + fromString: str => str, + toString: str => str + } + ); private _foundEntries = new UIEventSource([]); private _map: Basemap; @@ -33,14 +44,14 @@ export class SearchAndGo extends UIElement { // Triggered by 'enter' or onclick private RunSearch() { - const searchString = this._searchField.value.data; + const searchString = this._searchField.GetValue().data; this._searchField.Clear(); - this._placeholder.setData("Bezig met zoeken..."); + this._placeholder.setData(Translations.t.general.search.searching); const self = this; Geocoding.Search(searchString, this._map, (result) => { if (result.length == 0) { - this._placeholder.setData("Niets gevonden"); + this._placeholder.setData(Translations.t.general.search.nothing); return; } @@ -50,16 +61,15 @@ export class SearchAndGo extends UIElement { [bb[1], bb[3]] ] self._map.map.fitBounds(bounds); - this._placeholder.setData("Zoek naar een locatie..."); + this._placeholder.setData(Translations.t.general.search.search); }, () => { - this._placeholder.setData("Niets gevonden: er ging iets mis"); + this._placeholder.setData(Translations.t.general.search.error); }); } - protected InnerRender(): string { - // "Search " + + InnerRender(): string { return this._searchField.Render() + this._goButton.Render(); diff --git a/UI/SimpleAddUI.ts b/UI/SimpleAddUI.ts index b235437..2aa3e0e 100644 --- a/UI/SimpleAddUI.ts +++ b/UI/SimpleAddUI.ts @@ -42,7 +42,7 @@ export class SimpleAddUI extends UIElement { //
diff --git a/index.ts b/index.ts index 12c91c0..58a8b2a 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"; @@ -22,6 +22,14 @@ import {SearchAndGo} from "./UI/SearchAndGo"; import {CollapseButton} from "./UI/Base/CollapseButton"; import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; import {All} from "./Customizations/Layouts/All"; +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 ----------------- @@ -83,22 +91,32 @@ 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); + +// This message is shown full screen on mobile devices +const fullScreenMessage = new UIEventSource(undefined); const selectedElement = new UIEventSource(undefined); @@ -112,9 +130,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); @@ -137,7 +165,6 @@ const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement( // ------------- Setup the layers ------------------------------- -const controls = {}; const addButtons: { name: string, icon: string, @@ -167,8 +194,6 @@ 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, icon: layer.icon, @@ -184,8 +209,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, @@ -197,7 +227,7 @@ 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) => { // Which is the applicable set? @@ -206,14 +236,16 @@ selectedElement.addCallback((data) => { 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( + allElements.getElement(data.id), + layer.title, + layer.elementsToShow, + changes, + osmConnection.userDetails + ); + + fullScreenMessage.setData(featureBox); break; } } @@ -224,36 +256,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, @@ -276,4 +300,6 @@ new GeoLocationHandler(bm).AttachTo("geolocate-button"); // --------------- Send a ping to start various action -------- locationControl.ping(); -messageBox.update(); \ No newline at end of file + + +window.setTimeout(() => {Locale.language.setData("nl")}, 5000) \ No newline at end of file diff --git a/test.ts b/test.ts index b7e9bd3..71276c8 100644 --- a/test.ts +++ b/test.ts @@ -1,17 +1,13 @@ -import {UIEventSource} from "./UI/UIEventSource"; -import {Changes} from "./Logic/Changes"; -import {OsmConnection} from "./Logic/OsmConnection"; -import {ElementStorage} from "./Logic/ElementStorage"; -import {WikipediaLink} from "./Customizations/Questions/WikipediaLink"; -import {OsmLink} from "./Customizations/Questions/OsmLink"; -import {ConfirmDialog} from "./UI/ConfirmDialog"; -import {Imgur} from "./Logic/Imgur"; -import {VariableUiElement} from "./UI/Base/VariableUIElement"; +import {DropDown} from "./UI/Input/DropDown"; +import Locale from "./UI/i18n/Locale"; +import Combine from "./UI/Base/Combine"; +import Translations from "./UI/i18n/Translations"; +console.log("Hello world") -const html = new UIEventSource("Some text"); +let languagePicker = new DropDown("", ["en", "nl"].map(lang => { + return {value: lang, shown: lang} + } +), Locale.language).AttachTo("maindiv"); -const uielement = new VariableUiElement(html); -uielement.AttachTo("maindiv") - -window.setTimeout(() => {html.setData("Different text")}, 1000) \ No newline at end of file +new Combine(["abc",Translations.t.cyclofix.title, Translations.t.cyclofix.title]).AttachTo("extradiv"); \ No newline at end of file