From 369c19a58aeb31bbd597f1e7814d0b45b4abac8c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 21 Jul 2020 00:07:04 +0200 Subject: [PATCH] Translations --- Customizations/Layers/BikeShop.ts | 12 +- Customizations/Layout.ts | 3 + Customizations/Layouts/All.ts | 1 + Customizations/Layouts/Bookcases.ts | 1 + Customizations/Layouts/Cyclofix.ts | 9 +- Customizations/Layouts/GRB.ts | 1 + Customizations/Layouts/Groen.ts | 1 + Customizations/Layouts/MetaMap.ts | 1 + Customizations/Layouts/Natuurpunt.ts | 1 + Customizations/Layouts/Statues.ts | 1 + Customizations/Layouts/StreetWidth.ts | 1 + Customizations/Layouts/Toilets.ts | 1 + Customizations/Layouts/WalkByBrussels.ts | 1 + Customizations/TagRendering.ts | 139 +++++++++++------------ Logic/OsmConnection.ts | 1 + Logic/StrayClickHandler.ts | 8 +- UI/CenterMessageBox.ts | 2 +- UI/FeatureInfoBox.ts | 4 +- UI/Image/ImageCarouselWithUpload.ts | 4 +- UI/ImageUploadFlow.ts | 3 +- UI/Input/DropDown.ts | 9 +- UI/Input/TextField.ts | 1 - UI/MessageBoxHandler.ts | 21 ++-- UI/PendingChanges.ts | 2 +- UI/UIElement.ts | 3 +- UI/UIEventSource.ts | 11 ++ UI/UserBadge.ts | 8 +- UI/i18n/Locale.ts | 28 +++-- UI/i18n/Translation.ts | 24 +++- UI/i18n/Translations.ts | 48 ++++++-- index.css | 15 ++- index.html | 6 +- index.ts | 79 ++++++++----- test.ts | 10 +- 34 files changed, 287 insertions(+), 173 deletions(-) diff --git a/Customizations/Layers/BikeShop.ts b/Customizations/Layers/BikeShop.ts index 3e4413a..d1b1bea 100644 --- a/Customizations/Layers/BikeShop.ts +++ b/Customizations/Layers/BikeShop.ts @@ -1,6 +1,6 @@ import {TagRenderingOptions} from "../TagRendering"; import {LayerDefinition} from "../LayerDefinition"; -import {Tag} from "../../Logic/TagsFilter"; +import {And, Tag} from "../../Logic/TagsFilter"; import L from "leaflet"; import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; import {NameQuestion} from "../Questions/NameQuestion"; @@ -24,6 +24,16 @@ export class BikeShop extends LayerDefinition { 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"}, diff --git a/Customizations/Layout.ts b/Customizations/Layout.ts index a1d888e..a883bd8 100644 --- a/Customizations/Layout.ts +++ b/Customizations/Layout.ts @@ -14,6 +14,7 @@ export class Layout { public welcomeBackMessage: string; public startzoom: number; + public supportedLanguages: string[]; public startLon: number; public startLat: number; public welcomeTail: string; @@ -35,6 +36,7 @@ export class Layout { */ constructor( name: string, + supportedLanguages: string[], title: UIElement | string, layers: LayerDefinition[], startzoom: number, @@ -45,6 +47,7 @@ export class Layout { welcomeBackMessage: string = "You are logged in. Welcome back!", welcomeTail: string = "" ) { + this.supportedLanguages = supportedLanguages; this.title = typeof(title) === 'string' ? new FixedUiElement(title) : title; this.startLon = startLon; this.startLat = startLat; 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 860149d..c4fc3e7 100644 --- a/Customizations/Layouts/Cyclofix.ts +++ b/Customizations/Layouts/Cyclofix.ts @@ -4,21 +4,22 @@ import BikeServices from "../Layers/BikeStations"; import {GhostBike} from "../Layers/GhostBike"; import Translations from "../../UI/i18n/Translations"; import {DrinkingWater} from "../Layers/DrinkingWater"; -import {BikeShop} from "../Layers/BikeShop"; +import {BikeShop} from "../Layers/BikeShop" export default class Cyclofix extends Layout { constructor() { super( "pomp", - Translations.t.cylofix.title, + ["en", "nl", "fr"], + Translations.cylofix.title, [new BikeServices(), new BikeShop(), new DrinkingWater(), new BikeParkings()], 16, 50.8465573, 4.3516970, - "

" + Translations.t.cylofix.title.Render() + "

\n" + + "

" + Translations.cylofix.title.Render() + "

\n" + "\n" + - `

${Translations.t.cylofix.description.Render()}

` + `

${Translations.cylofix.description.Render()}

` , "", ""); } 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/TagRendering.ts b/Customizations/TagRendering.ts index ca7a8d3..67b2b28 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -13,6 +13,7 @@ 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"; export class TagRenderingOptions implements TagDependantUIElementConstructor { @@ -21,8 +22,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; + extraTags?: TagsFilter + }; + mappings?: { k: TagsFilter; txt: string | UIElement; priority?: number, substitute?: boolean }[] }; @@ -73,12 +83,6 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { }, - /** - * 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. @@ -86,6 +90,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { */ tagsPreprocessor?: ((tags: any) => void) }) { + this.options = options; } @@ -134,10 +139,9 @@ class TagRendering extends UIElement implements TagDependantUIElement { private _priority: number; - 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: { @@ -163,8 +167,7 @@ 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, @@ -173,7 +176,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { 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); const self = this; @@ -183,9 +186,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; @@ -202,38 +206,28 @@ 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 + }); } @@ -287,7 +281,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { placeholder?: string, extraTags?: TagsFilter, }, - mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[] + mappings?: { k: TagsFilter, txt: string | UIElement, priority?: number, substitute?: boolean }[] }): InputElement { @@ -315,7 +309,8 @@ class TagRendering extends UIElement implements TagDependantUIElement { if (elements.length == 0) { - throw "NO TAGRENDERINGS!" + 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]; @@ -326,7 +321,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { } - private InputElementForMapping(mapping: { k: TagsFilter, txt: string }) { + private InputElementForMapping(mapping: { k: TagsFilter, txt: string | UIElement }) { return new FixedInputElement(mapping.txt, mapping.k); } @@ -421,10 +416,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); @@ -432,30 +427,30 @@ 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); - } - } @@ -464,23 +459,24 @@ class TagRendering extends UIElement implements TagDependantUIElement { 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 + "" + + (question !== "" ? "
" : "") + + "
" + this._questionElement.Render() + "
" + this._skipButton.Render() + this._saveButton.Render() + "
" } if (this.IsKnown()) { - const html = this.RenderAnwser(); + const html = this.RenderAnwser().Render(); if (html == "") { return ""; } let editButton = ""; - if(this._userDetails.data.loggedIn){ + if (this._userDetails.data.loggedIn && this._question !== undefined) { editButton = this._editButton.Render(); } @@ -499,12 +495,15 @@ class TagRendering extends UIElement implements TagDependantUIElement { return this._priority; } - private ApplyTemplate(template: string): string { + private ApplyTemplate(template: string | UIElement): UIElement { + if (template instanceof UIElement) { + return template; + } const tags = this._tagsPreprocessor(this._source.data); - return TagUtils.ApplyTemplate(template, tags); + return new FixedUiElement(TagUtils.ApplyTemplate(template, tags)); } - + InnerUpdate(htmlElement: HTMLElement) { super.InnerUpdate(htmlElement); this._questionElement.Update(); // Another manual update for them 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/StrayClickHandler.ts b/Logic/StrayClickHandler.ts index 89acbcc..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; @@ -38,7 +38,7 @@ export class StrayClickHandler { self._lastMarker.bindPopup(popup).openPopup(); self._lastMarker.on("click", () => { - leftMessage.setData(self._uiToShow); + fullScreenMessage.setData(self._uiToShow()); }); uiElement.Update(); uiElement.Activate(); diff --git a/UI/CenterMessageBox.ts b/UI/CenterMessageBox.ts index a5cd8b6..ffb864e 100644 --- a/UI/CenterMessageBox.ts +++ b/UI/CenterMessageBox.ts @@ -34,7 +34,7 @@ export class CenterMessageBox extends UIElement { } - protected InnerRender(): string { + InnerRender(): string { if (this._centermessage.data != "") { return this._centermessage.data; diff --git a/UI/FeatureInfoBox.ts b/UI/FeatureInfoBox.ts index 47b5011..7c47878 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 { @@ -31,7 +31,7 @@ export class FeatureInfoBox extends UIElement { constructor( tagsES: UIEventSource, title: TagRenderingOptions, - elementsToShow: TagRenderingOptions[], + elementsToShow: TagDependantUIElementConstructor[], changes: Changes, userDetails: UIEventSource ) { 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 3d0f633..1a628ad 100644 --- a/UI/ImageUploadFlow.ts +++ b/UI/ImageUploadFlow.ts @@ -5,6 +5,7 @@ import {Imgur} from "../Logic/Imgur"; import {UserDetails} from "../Logic/OsmConnection"; import {DropDown} from "./Input/DropDown"; import {VariableUiElement} from "./Base/VariableUIElement"; +import Translations from "./i18n/Translations"; export class ImageUploadFlow extends UIElement { private _licensePicker: UIElement; @@ -66,7 +67,7 @@ export class ImageUploadFlow extends UIElement { "
" + "upload image " + - "Add a picture" + + ""+Translations.general.uploadAPicture.R()+"" + "
" + "
" + diff --git a/UI/Input/DropDown.ts b/UI/Input/DropDown.ts index fb15f02..dfbcafd 100644 --- a/UI/Input/DropDown.ts +++ b/UI/Input/DropDown.ts @@ -50,7 +50,10 @@ export class DropDown extends InputElement { InnerRender(): string { - + if(this._values.length <=1){ + return ""; + } + let options = ""; for (let i = 0; i < this._values.length; i++) { options += "" @@ -65,8 +68,12 @@ export class DropDown extends InputElement { } protected InnerUpdate(element) { + var e = document.getElementById("dropdown-" + this.id); + if(e === null){ + return; + } const self = this; e.onchange = (() => { // @ts-ignore diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts index 0d7eee2..44a816b 100644 --- a/UI/Input/TextField.ts +++ b/UI/Input/TextField.ts @@ -105,7 +105,6 @@ export class TextField extends InputElement { } IsValid(t: T): boolean { - console.log("TXT IS valid?",t,this._toString(t)) if(t === undefined || t === null){ return false; } diff --git a/UI/MessageBoxHandler.ts b/UI/MessageBoxHandler.ts index 42bf403..a06a25a 100644 --- a/UI/MessageBoxHandler.ts +++ b/UI/MessageBoxHandler.ts @@ -6,9 +6,9 @@ import {UIElement} from "./UIElement"; import {VariableUiElement} from "./Base/VariableUIElement"; export class MessageBoxHandler { - private _uielement: UIEventSource<() => UIElement>; + private _uielement: UIEventSource; - constructor(uielement: UIEventSource<() => UIElement>, + constructor(uielement: UIEventSource, onClear: (() => void)) { this._uielement = uielement; this.listenTo(uielement); @@ -22,14 +22,13 @@ export class MessageBoxHandler { } } - new VariableUiElement(new UIEventSource("

Return to the map

"), - () => { - document.getElementById("to-the-map").onclick = function () { - uielement.setData(undefined); - onClear(); - } - } - ).AttachTo("to-the-map"); + new VariableUiElement(new UIEventSource("

Return to the map

")) + .onClick(() => { + console.log("Clicked 'return to the map'") + uielement.setData(undefined); + onClear(); + }) + .AttachTo("to-the-map"); } @@ -55,7 +54,7 @@ export class MessageBoxHandler { location.hash = "#element" wrapper.classList.remove("hidden"); - gen() + gen ?.HideOnEmpty(true) ?.AttachTo("messagesboxmobile") ?.Activate(); 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/UIElement.ts b/UI/UIElement.ts index a0b9d49..8e1ff27 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -37,11 +37,10 @@ export abstract class UIElement { Update(): void { let element = document.getElementById(this.id); - if (element === null || element === undefined) { + if (element === undefined || element === null) { // The element is not painted return; } - element.innerHTML = this.InnerRender(); if (this._hideIfEmpty) { if (element.innerHTML === "") { diff --git a/UI/UIEventSource.ts b/UI/UIEventSource.ts index 54902a0..9529468 100644 --- a/UI/UIEventSource.ts +++ b/UI/UIEventSource.ts @@ -66,5 +66,16 @@ export class UIEventSource{ } + + public syncWith(otherSource: UIEventSource){ + this.addCallback((latest) => otherSource.setData(latest)); + const self = this; + otherSource.addCallback((latest) => self.setData(latest)); + if(this.data === undefined){ + this.setData(otherSource.data); + }else{ + otherSource.setData(this.data); + } + } } \ No newline at end of file diff --git a/UI/UserBadge.ts b/UI/UserBadge.ts index bb704eb..9681586 100644 --- a/UI/UserBadge.ts +++ b/UI/UserBadge.ts @@ -5,6 +5,7 @@ import {Basemap} from "../Logic/Basemap"; import L from "leaflet"; import {FixedUiElement} from "./Base/FixedUiElement"; import {VariableUiElement} from "./Base/VariableUIElement"; +import Translations from "./i18n/Translations"; /** * Handles and updates the user badge @@ -15,13 +16,15 @@ export class UserBadge extends UIElement { private _logout: UIElement; private _basemap: Basemap; private _homeButton: UIElement; + private _languagePicker: UIElement; constructor(userDetails: UIEventSource, pendingChanges: UIElement, + languagePicker: UIElement, basemap: Basemap) { super(userDetails); - + this._languagePicker = languagePicker; this._userDetails = userDetails; this._pendingChanges = pendingChanges; this._basemap = basemap; @@ -61,7 +64,7 @@ export class UserBadge extends UIElement { InnerRender(): string { const user = this._userDetails.data; if (!user.loggedIn) { - return "
Login with OpenStreetMap
"; + return "
" + Translations.general.loginWithOpenStreetMap.R()+ "
"; } @@ -114,6 +117,7 @@ export class UserBadge extends UIElement { " star " + user.csCount + " " + this._logout.Render() + + this._languagePicker.Render() + this._pendingChanges.Render() + "

" + diff --git a/UI/i18n/Locale.ts b/UI/i18n/Locale.ts index 52c45bb..52e0d78 100644 --- a/UI/i18n/Locale.ts +++ b/UI/i18n/Locale.ts @@ -1,18 +1,24 @@ -import { UIEventSource } from "../UIEventSource"; +import {UIEventSource} from "../UIEventSource"; +import {OsmConnection} from "../../Logic/OsmConnection"; -const LANGUAGE_KEY = 'language' - export default class Locale { - public static language: UIEventSource = new UIEventSource(Locale.getInitialLanguage()) - - public static init() { - Locale.language.addCallback(data => { - localStorage.setItem(LANGUAGE_KEY, data) - }) - } + public static language: UIEventSource = Locale.getInitialLanguage() private static getInitialLanguage() { - return localStorage.getItem(LANGUAGE_KEY) + // The key to save in local storage + const LANGUAGE_KEY = 'language' + + const lng = new UIEventSource("en"); + const saved = localStorage.getItem(LANGUAGE_KEY); + lng.setData(saved); + + + lng.addCallback(data => { + console.log("Selected language", data); + localStorage.setItem(LANGUAGE_KEY, data) + }); + + return lng; } } diff --git a/UI/i18n/Translation.ts b/UI/i18n/Translation.ts index 4b53b17..b79746c 100644 --- a/UI/i18n/Translation.ts +++ b/UI/i18n/Translation.ts @@ -1,16 +1,30 @@ import { UIElement } from "../UIElement" import Locale from "./Locale" +import {FixedUiElement} from "../Base/FixedUiElement"; -export default class Translation extends UIElement{ - protected InnerRender(): string { - return this.translations[Locale.language.data] - } +export default class Translation extends UIElement { public readonly translations: object - + constructor(translations: object) { super(Locale.language) this.translations = translations } + + public R(): string { + return new Translation(this.translations).Render(); + } + + InnerRender(): string { + const txt = this.translations[Locale.language.data]; + if (txt !== undefined) { + return txt; + } + const en = this.translations["en"]; + console.warn("No translation for language ", Locale.language.data, "for",en); + return en; + } + + } diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index edf0561..8536c01 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -1,18 +1,42 @@ import Translation from "./Translation"; +import {UIElement} from "../UIElement"; +import {FixedUiElement} from "../Base/FixedUiElement"; export default class Translations { - static t = { - cylofix: { - title: new Translation({en: 'Cyclofix bicycle infrastructure', nl: 'Cyclofix fietsinfrastructuur', fr: 'TODO: FRENCH TRANSLATION'}), - description: new Translation({ - 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." - }) - } + static cylofix = { + title: new Translation({ + en: 'Cyclofix bicycle infrastructure', + nl: 'Cyclofix fietsinfrastructuur', + fr: 'TODO: FRENCH TRANSLATION' + }), + description: new Translation({ + 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." + }) + }; + static general = { + loginWithOpenStreetMap: new Translation({ + en: "Click here to login with OpenStreetMap", + nl: "Klik hier op je aan te melden met OpenStreetMap" + }), + uploadAPicture: new Translation({ + en: "Add a picture", + nl: "Voeg een foto toe" + + }) } + + public static W(s: string | UIElement): + UIElement { + if (s instanceof UIElement) { + return s; + } + return new FixedUiElement(s); + } + } diff --git a/index.css b/index.css index d4448b8..b79d1c8 100644 --- a/index.css +++ b/index.css @@ -307,23 +307,26 @@ form { } } - #to-the-map { + position: relative; +} + +#to-the-map h2{ + position: absolute; height: 4em; + padding: 0.5em; margin: 0; - position: absolute; - bottom: 0; - right: 0; - padding-right: 2em; + padding-top: 1em; + text-align: center; width: 100%; - text-align: right; color: white; background-color: #7ebc6f; cursor: pointer; + } diff --git a/index.html b/index.html index bcdeca3..b48ae01 100644 --- a/index.html +++ b/index.html @@ -28,17 +28,13 @@ Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is blocking it. +


-
diff --git a/index.ts b/index.ts index c1fd116..acf78fe 100644 --- a/index.ts +++ b/index.ts @@ -25,7 +25,10 @@ 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 } from "./Customizations/Layout"; +import {Layout} from "./Customizations/Layout"; +import {DropDown} from "./UI/Input/DropDown"; +import {FixedInputElement} from "./UI/Input/FixedInputElement"; +import {FixedUiElement} from "./UI/Base/FixedUiElement"; // --------------------- Read the URL parameters ----------------- @@ -90,22 +93,29 @@ if (paramDict.test) { const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout]; console.log("Using layout: ", layoutToUse.name); -document.title = layoutToUse.title.Render(); +document.title = layoutToUse.title.InnerRender(); Locale.language.addCallback(e => { - document.title = layoutToUse.title.Render(); + 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); @@ -119,9 +129,18 @@ 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")); + +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); @@ -191,8 +210,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, @@ -204,7 +228,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? @@ -213,14 +237,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; } } @@ -231,7 +257,10 @@ 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"); @@ -239,7 +268,7 @@ new SearchAndGo(bm).AttachTo("searchbox"); new CollapseButton("messagesbox") .AttachTo("collapseButton"); -var welcomeMessage = () => { +var generateWelcomeMessage = () => { return new VariableUiElement( osmConnection.userDetails.map((userdetails) => { var login = layoutToUse.gettingStartedPlzLogin; @@ -254,11 +283,11 @@ var welcomeMessage = () => { osmConnection.registerActivateOsmAUthenticationClass() }); } -leftMessage.setData(welcomeMessage); -welcomeMessage().AttachTo("messagesbox"); +generateWelcomeMessage().AttachTo("messagesbox"); +fullScreenMessage.setData(generateWelcomeMessage()); -var messageBox = new MessageBoxHandler(leftMessage, () => { +var messageBox = new MessageBoxHandler(fullScreenMessage, () => { selectedElement.setData(undefined) }); @@ -286,13 +315,3 @@ locationControl.ping(); messageBox.update(); -// --- Locale --- - -Locale.init() - -window.setLanguage = function(language:string) { - Locale.language.setData(language) -} - -// const eLanguageSelect = document.getElementById('language-select') as HTMLOptionElement -// eLanguageSelect.addEventListener('selectionchange') diff --git a/test.ts b/test.ts index e4f2d19..b2a2271 100644 --- a/test.ts +++ b/test.ts @@ -1 +1,9 @@ -console.log("Hello world") \ No newline at end of file +import {DropDown} from "./UI/Input/DropDown"; +import Locale from "./UI/i18n/Locale"; + +console.log("Hello world") + +let languagePicker = new DropDown("", ["en", "nl"].map(lang => { + return {value: lang, shown: lang} + } +), Locale.language).AttachTo("maindiv"); \ No newline at end of file