forked from MapComplete/MapComplete
		
	Translations
This commit is contained in:
		
							parent
							
								
									615bbec05d
								
							
						
					
					
						commit
						369c19a58a
					
				
					 34 changed files with 287 additions and 173 deletions
				
			
		|  | @ -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"}, | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ export class All extends Layout{ | |||
|     constructor() { | ||||
|         super( | ||||
|             "all", | ||||
|             ["en"], | ||||
|             "All quest layers", | ||||
|             [], | ||||
|             15, | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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, | ||||
|             "<h3>" + Translations.t.cylofix.title.Render() + "</h3>\n" + | ||||
|             "<h3>" + Translations.cylofix.title.Render() + "</h3>\n" + | ||||
|             "\n" + | ||||
|             `<p>${Translations.t.cylofix.description.Render()}</p>` | ||||
|             `<p>${Translations.cylofix.description.Render()}</p>` | ||||
|             , | ||||
|             "", ""); | ||||
|     } | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ export class Groen extends Layout { | |||
|      | ||||
|     constructor() { | ||||
|         super("buurtnatuur", | ||||
|             ["nl"], | ||||
|             "Buurtnatuur", | ||||
|             [new NatureReserves(), new Park(), new Bos()], | ||||
|             10, | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import {Map} from "../Layers/Map"; | |||
| export class MetaMap extends Layout{ | ||||
|     constructor() { | ||||
|         super(    "metamap", | ||||
|             ["en"], | ||||
|             "Open Map Map", | ||||
|             [new Map()], | ||||
|             1, | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ export class Natuurpunt extends Layout{ | |||
|     constructor() { | ||||
|         super( | ||||
|             "natuurpunt", | ||||
|             ["nl"], | ||||
|             "De natuur in", | ||||
|             [new Birdhide(), new InformationBoard(), new NatureReserves(true)], | ||||
|             12, | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ export class Statues extends Layout{ | |||
|     constructor() { | ||||
|         super(    "statues", | ||||
|             "Open Artwork Map", | ||||
|             ["en"], | ||||
|             [new Artwork()], | ||||
|             10, | ||||
|             50.8435, | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ export class StreetWidth extends Layout{ | |||
|      | ||||
|     constructor() { | ||||
|         super(    "width", | ||||
|             ["nl"], | ||||
|             "Straatbreedtes in Brugge", | ||||
|             [new Widths( | ||||
|                 2, | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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<any>, 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<TagsFilter> { | ||||
| 
 | ||||
|  | @ -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); | ||||
|  | @ -453,7 +448,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { | |||
| 
 | ||||
|         if (highestTemplate !== undefined) { | ||||
|             // we render the found template
 | ||||
|                 return this._primer + this.ApplyTemplate(highestTemplate); | ||||
|             return 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 "<div class='question'>" + | ||||
|                 "<span class='question-text'>" + this._question + "</span>" + | ||||
|                 (this._question !== "" ? "<br/>" : "") + | ||||
|                "<div>"+ this._questionElement.Render() +"</div>"+ | ||||
|                 "<span class='question-text'>" + question + "</span>" + | ||||
|                 (question !== "" ? "<br/>" : "") + | ||||
|                 "<div>" + this._questionElement.Render() + "</div>" + | ||||
|                 this._skipButton.Render() + | ||||
|                 this._saveButton.Render() + | ||||
|                 "</div>" | ||||
|         } | ||||
| 
 | ||||
|         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,9 +495,12 @@ 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)); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -123,6 +123,7 @@ export class OsmConnection { | |||
|     public preferenceSources : any = {} | ||||
|      | ||||
|     public GetPreference(key: string) : UIEventSource<string>{ | ||||
|         key = "mapcomplete-"+key; | ||||
|         if (this.preferenceSources[key] !== undefined) { | ||||
|             return this.preferenceSources[key]; | ||||
|         } | ||||
|  |  | |||
|  | @ -10,16 +10,16 @@ import {UIElement} from "../UI/UIElement"; | |||
| export class StrayClickHandler { | ||||
|     private _basemap: Basemap; | ||||
|     private _lastMarker; | ||||
|     private _leftMessage: UIEventSource<() => UIElement>; | ||||
|     private _fullScreenMessage: UIEventSource<UIElement>; | ||||
|     private _uiToShow: (() => UIElement); | ||||
| 
 | ||||
|     constructor( | ||||
|         basemap: Basemap, | ||||
|         selectElement: UIEventSource<any>, | ||||
|         leftMessage: UIEventSource<() => UIElement>,  | ||||
|         fullScreenMessage: UIEventSource<UIElement>,  | ||||
|         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(); | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ export class CenterMessageBox extends UIElement { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     protected InnerRender(): string { | ||||
|     InnerRender(): string { | ||||
| 
 | ||||
|         if (this._centermessage.data != "") { | ||||
|             return this._centermessage.data; | ||||
|  |  | |||
|  | @ -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<any>, | ||||
|         title: TagRenderingOptions, | ||||
|         elementsToShow: TagRenderingOptions[], | ||||
|         elementsToShow: TagDependantUIElementConstructor[], | ||||
|         changes: Changes, | ||||
|         userDetails: UIEventSource<UserDetails> | ||||
|     ) { | ||||
|  |  | |||
|  | @ -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(); | ||||
|     } | ||||
|  |  | |||
|  | @ -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 { | |||
| 
 | ||||
|             "<div class='imageflow-file-input-wrapper'>" + | ||||
|             "<img src='./assets/camera-plus.svg' alt='upload image'/> " + | ||||
|             "<span class='imageflow-add-picture'>Add a picture</span>" + | ||||
|             "<span class='imageflow-add-picture'>"+Translations.general.uploadAPicture.R()+"</span>" + | ||||
|             "<div class='break'></div>" + | ||||
|             "</div>" + | ||||
| 
 | ||||
|  |  | |||
|  | @ -50,6 +50,9 @@ export class DropDown<T> extends InputElement<T> { | |||
| 
 | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         if(this._values.length <=1){ | ||||
|             return ""; | ||||
|         } | ||||
|          | ||||
|         let options = ""; | ||||
|         for (let i = 0; i < this._values.length; i++) { | ||||
|  | @ -66,7 +69,11 @@ export class DropDown<T> extends InputElement<T> { | |||
| 
 | ||||
|     protected InnerUpdate(element) { | ||||
|         | ||||
| 
 | ||||
|         var e = document.getElementById("dropdown-" + this.id); | ||||
|         if(e === null){ | ||||
|             return; | ||||
|         } | ||||
|         const self = this; | ||||
|         e.onchange = (() => { | ||||
|             // @ts-ignore
 | ||||
|  |  | |||
|  | @ -105,7 +105,6 @@ export class TextField<T> extends InputElement<T> { | |||
|     } | ||||
| 
 | ||||
|     IsValid(t: T): boolean { | ||||
|         console.log("TXT IS valid?",t,this._toString(t)) | ||||
|         if(t === undefined || t === null){ | ||||
|             return false; | ||||
|         } | ||||
|  |  | |||
|  | @ -6,9 +6,9 @@ import {UIElement} from "./UIElement"; | |||
| import {VariableUiElement} from "./Base/VariableUIElement"; | ||||
| 
 | ||||
| export class MessageBoxHandler { | ||||
|     private _uielement: UIEventSource<() => UIElement>; | ||||
|     private _uielement: UIEventSource<UIElement>; | ||||
| 
 | ||||
|     constructor(uielement: UIEventSource<() => UIElement>, | ||||
|     constructor(uielement: UIEventSource<UIElement>, | ||||
|                 onClear: (() => void)) { | ||||
|         this._uielement = uielement; | ||||
|         this.listenTo(uielement); | ||||
|  | @ -22,14 +22,13 @@ export class MessageBoxHandler { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         new VariableUiElement(new UIEventSource<string>("<h2>Return to the map</h2>"), | ||||
|             () => { | ||||
|                 document.getElementById("to-the-map").onclick = function () { | ||||
|         new VariableUiElement(new UIEventSource<string>("<h2>Return to the map</h2>")) | ||||
|             .onClick(() => { | ||||
|                 console.log("Clicked 'return to the map'") | ||||
|                 uielement.setData(undefined); | ||||
|                 onClear(); | ||||
|                 } | ||||
|             } | ||||
|         ).AttachTo("to-the-map"); | ||||
|             }) | ||||
|             .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(); | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ export class PendingChanges extends UIElement { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     protected InnerRender(): string { | ||||
|     InnerRender(): string { | ||||
|         if (this._isSaving.data) { | ||||
|             return "<span class='alert'>Saving</span>"; | ||||
|         } | ||||
|  |  | |||
|  | @ -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 === "") { | ||||
|  |  | |||
|  | @ -67,4 +67,15 @@ export class UIEventSource<T>{ | |||
|     } | ||||
| 
 | ||||
|      | ||||
|     public syncWith(otherSource: UIEventSource<T>){ | ||||
|         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); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<UserDetails>, | ||||
|                 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 "<div class='activate-osm-authentication'>Login with OpenStreetMap</div>"; | ||||
|             return "<div class='activate-osm-authentication'>" + Translations.general.loginWithOpenStreetMap.R()+ "</div>"; | ||||
|         } | ||||
|          | ||||
|          | ||||
|  | @ -114,6 +117,7 @@ export class UserBadge extends UIElement { | |||
|             "   <a href='https://www.openstreetmap.org/user/" + user.name + "/history' target='_blank'><img class='small-userbadge-icon' src='./assets/star.svg' alt='star'/> " + user.csCount + | ||||
|             "</a></span> " + | ||||
|             this._logout.Render() + | ||||
|             this._languagePicker.Render() + | ||||
|             this._pendingChanges.Render() + | ||||
|             "</p>" + | ||||
|         | ||||
|  |  | |||
|  | @ -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<string> = new UIEventSource(Locale.getInitialLanguage()) | ||||
| 
 | ||||
|     public static init() { | ||||
|         Locale.language.addCallback(data => { | ||||
|             localStorage.setItem(LANGUAGE_KEY, data) | ||||
|         }) | ||||
|     } | ||||
|     public static language: UIEventSource<string> = 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; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,11 +1,9 @@ | |||
| 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 | ||||
| 
 | ||||
|  | @ -13,4 +11,20 @@ export default class Translation extends UIElement{ | |||
|         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; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,10 +1,15 @@ | |||
| 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'}), | ||||
|     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.", | ||||
|  | @ -13,6 +18,25 @@ export default class Translations { | |||
|             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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
							
								
								
									
										15
									
								
								index.css
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								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; | ||||
|      | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -28,17 +28,13 @@ | |||
|             Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is | ||||
|             blocking it. | ||||
|         </div> | ||||
|         <div id="language-select"></div> | ||||
|         <br/> | ||||
|         <div id="searchbox"></div> | ||||
|     </div> | ||||
|     <br/> | ||||
|     <div id="messagesbox-wrapper"> | ||||
|         <div id="collapseButton"></div> | ||||
|         <select id="language-select"> | ||||
|             <option>EN</option> | ||||
|             <option>NL</option> | ||||
|             <option>FR</option> | ||||
|         </select> | ||||
|         <div id="messagesbox"></div> | ||||
|     </div> | ||||
| </div> | ||||
|  |  | |||
							
								
								
									
										69
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										69
									
								
								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<string>(""); | ||||
| 
 | ||||
| // 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<number>(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<UIElement>(undefined); | ||||
| 
 | ||||
| const selectedElement = new UIEventSource<any>(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( | ||||
| 
 | ||||
|             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')
 | ||||
|  |  | |||
							
								
								
									
										8
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										8
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -1 +1,9 @@ | |||
| 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"); | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue