Translations

This commit is contained in:
Pieter Vander Vennet 2020-07-21 00:07:04 +02:00
parent 615bbec05d
commit 369c19a58a
34 changed files with 287 additions and 173 deletions

View file

@ -1,6 +1,6 @@
import {TagRenderingOptions} from "../TagRendering"; import {TagRenderingOptions} from "../TagRendering";
import {LayerDefinition} from "../LayerDefinition"; import {LayerDefinition} from "../LayerDefinition";
import {Tag} from "../../Logic/TagsFilter"; import {And, Tag} from "../../Logic/TagsFilter";
import L from "leaflet"; import L from "leaflet";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
import {NameQuestion} from "../Questions/NameQuestion"; import {NameQuestion} from "../Questions/NameQuestion";
@ -24,6 +24,16 @@ export class BikeShop extends LayerDefinition {
this.title = new TagRenderingOptions({ this.title = new TagRenderingOptions({
mappings: [ 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: this.sellsBikes, txt: "Bicycle shop"},
{k: new Tag("service:bicycle:retail", "no"), txt: "Bicycle repair"}, {k: new Tag("service:bicycle:retail", "no"), txt: "Bicycle repair"},
{k: new Tag("service:bicycle:retail", ""), txt: "Bicycle repair/shop"}, {k: new Tag("service:bicycle:retail", ""), txt: "Bicycle repair/shop"},

View file

@ -14,6 +14,7 @@ export class Layout {
public welcomeBackMessage: string; public welcomeBackMessage: string;
public startzoom: number; public startzoom: number;
public supportedLanguages: string[];
public startLon: number; public startLon: number;
public startLat: number; public startLat: number;
public welcomeTail: string; public welcomeTail: string;
@ -35,6 +36,7 @@ export class Layout {
*/ */
constructor( constructor(
name: string, name: string,
supportedLanguages: string[],
title: UIElement | string, title: UIElement | string,
layers: LayerDefinition[], layers: LayerDefinition[],
startzoom: number, startzoom: number,
@ -45,6 +47,7 @@ export class Layout {
welcomeBackMessage: string = "You are logged in. Welcome back!", welcomeBackMessage: string = "You are logged in. Welcome back!",
welcomeTail: string = "" welcomeTail: string = ""
) { ) {
this.supportedLanguages = supportedLanguages;
this.title = typeof(title) === 'string' ? new FixedUiElement(title) : title; this.title = typeof(title) === 'string' ? new FixedUiElement(title) : title;
this.startLon = startLon; this.startLon = startLon;
this.startLat = startLat; this.startLat = startLat;

View file

@ -4,6 +4,7 @@ export class All extends Layout{
constructor() { constructor() {
super( super(
"all", "all",
["en"],
"All quest layers", "All quest layers",
[], [],
15, 15,

View file

@ -4,6 +4,7 @@ import * as Layer from "../Layers/Bookcases";
export class Bookcases extends Layout{ export class Bookcases extends Layout{
constructor() { constructor() {
super( "bookcases", super( "bookcases",
["nl"],
"Open Bookcase Map", "Open Bookcase Map",
[new Layer.Bookcases()], [new Layer.Bookcases()],
14, 14,

View file

@ -4,21 +4,22 @@ import BikeServices from "../Layers/BikeStations";
import {GhostBike} from "../Layers/GhostBike"; import {GhostBike} from "../Layers/GhostBike";
import Translations from "../../UI/i18n/Translations"; import Translations from "../../UI/i18n/Translations";
import {DrinkingWater} from "../Layers/DrinkingWater"; import {DrinkingWater} from "../Layers/DrinkingWater";
import {BikeShop} from "../Layers/BikeShop"; import {BikeShop} from "../Layers/BikeShop"
export default class Cyclofix extends Layout { export default class Cyclofix extends Layout {
constructor() { constructor() {
super( super(
"pomp", "pomp",
Translations.t.cylofix.title, ["en", "nl", "fr"],
Translations.cylofix.title,
[new BikeServices(), new BikeShop(), new DrinkingWater(), new BikeParkings()], [new BikeServices(), new BikeShop(), new DrinkingWater(), new BikeParkings()],
16, 16,
50.8465573, 50.8465573,
4.3516970, 4.3516970,
"<h3>" + Translations.t.cylofix.title.Render() + "</h3>\n" + "<h3>" + Translations.cylofix.title.Render() + "</h3>\n" +
"\n" + "\n" +
`<p>${Translations.t.cylofix.description.Render()}</p>` `<p>${Translations.cylofix.description.Render()}</p>`
, ,
"", ""); "", "");
} }

View file

@ -4,6 +4,7 @@ import {GrbToFix} from "../Layers/GrbToFix";
export class GRB extends Layout { export class GRB extends Layout {
constructor() { constructor() {
super("grb", super("grb",
["en"],
"Grb import fix tool", "Grb import fix tool",
[new GrbToFix()], [new GrbToFix()],
15, 15,

View file

@ -7,6 +7,7 @@ export class Groen extends Layout {
constructor() { constructor() {
super("buurtnatuur", super("buurtnatuur",
["nl"],
"Buurtnatuur", "Buurtnatuur",
[new NatureReserves(), new Park(), new Bos()], [new NatureReserves(), new Park(), new Bos()],
10, 10,

View file

@ -5,6 +5,7 @@ import {Map} from "../Layers/Map";
export class MetaMap extends Layout{ export class MetaMap extends Layout{
constructor() { constructor() {
super( "metamap", super( "metamap",
["en"],
"Open Map Map", "Open Map Map",
[new Map()], [new Map()],
1, 1,

View file

@ -7,6 +7,7 @@ export class Natuurpunt extends Layout{
constructor() { constructor() {
super( super(
"natuurpunt", "natuurpunt",
["nl"],
"De natuur in", "De natuur in",
[new Birdhide(), new InformationBoard(), new NatureReserves(true)], [new Birdhide(), new InformationBoard(), new NatureReserves(true)],
12, 12,

View file

@ -5,6 +5,7 @@ export class Statues extends Layout{
constructor() { constructor() {
super( "statues", super( "statues",
"Open Artwork Map", "Open Artwork Map",
["en"],
[new Artwork()], [new Artwork()],
10, 10,
50.8435, 50.8435,

View file

@ -7,6 +7,7 @@ export class StreetWidth extends Layout{
constructor() { constructor() {
super( "width", super( "width",
["nl"],
"Straatbreedtes in Brugge", "Straatbreedtes in Brugge",
[new Widths( [new Widths(
2, 2,

View file

@ -4,6 +4,7 @@ import * as Layer from "../Layers/Toilets";
export class Toilets extends Layout{ export class Toilets extends Layout{
constructor() { constructor() {
super( "toilets", super( "toilets",
["en"],
"Open Toilet Map", "Open Toilet Map",
[new Layer.Toilets()], [new Layer.Toilets()],
12, 12,

View file

@ -6,6 +6,7 @@ import { Park } from "../Layers/Park";
export class WalkByBrussels extends Layout { export class WalkByBrussels extends Layout {
constructor() { constructor() {
super("walkbybrussels", super("walkbybrussels",
["en","fr","nl"],
"Drinking Water Spots", "Drinking Water Spots",
[new DrinkingWater(), new Park(), new NatureReserves()], [new DrinkingWater(), new Park(), new NatureReserves()],
10, 10,

View file

@ -13,6 +13,7 @@ import {InputElement} from "../UI/Input/InputElement";
import {InputElementWrapper} from "../UI/Input/InputElementWrapper"; import {InputElementWrapper} from "../UI/Input/InputElementWrapper";
import {FixedInputElement} from "../UI/Input/FixedInputElement"; import {FixedInputElement} from "../UI/Input/FixedInputElement";
import {RadioButton} from "../UI/Input/RadioButton"; import {RadioButton} from "../UI/Input/RadioButton";
import Translations from "../UI/i18n/Translations";
export class TagRenderingOptions implements TagDependantUIElementConstructor { export class TagRenderingOptions implements TagDependantUIElementConstructor {
@ -21,8 +22,17 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
*/ */
public options: { public options: {
priority?: number; question?: string; primer?: string; priority?: number;
freeform?: { key: string; tagsPreprocessor?: (tags: any) => any; template: string; renderTemplate: string; placeholder?: string; extraTags?: TagsFilter }; mappings?: { k: TagsFilter; txt: string; priority?: number, substitute?: boolean }[] 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 * In some very rare cases, tags have to be rewritten before displaying
* This function can be used for that. * This function can be used for that.
@ -86,6 +90,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
*/ */
tagsPreprocessor?: ((tags: any) => void) tagsPreprocessor?: ((tags: any) => void)
}) { }) {
this.options = options; this.options = options;
} }
@ -134,10 +139,9 @@ class TagRendering extends UIElement implements TagDependantUIElement {
private _priority: number; private _priority: number;
private _question: string; private _question: UIElement;
private _primer: string; private _mapping: { k: TagsFilter, txt: UIElement, priority?: number }[];
private _mapping: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[]; private _renderMapping: { k: TagsFilter, txt: UIElement, priority?: number }[];
private _renderMapping: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[];
private _tagsPreprocessor?: ((tags: any) => any); private _tagsPreprocessor?: ((tags: any) => any);
private _freeform: { private _freeform: {
@ -163,8 +167,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
constructor(tags: UIEventSource<any>, changes: Changes, options: { constructor(tags: UIEventSource<any>, changes: Changes, options: {
priority?: number priority?: number
question?: string, question?: string | UIElement,
primer?: string,
freeform?: { freeform?: {
key: string, template: string, key: string, template: string,
@ -173,7 +176,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
extraTags?: TagsFilter, extraTags?: TagsFilter,
}, },
tagsPreprocessor?: ((tags: any) => any), 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); super(tags);
const self = this; const self = this;
@ -183,9 +186,10 @@ class TagRendering extends UIElement implements TagDependantUIElement {
this._userDetails = changes.login.userDetails; this._userDetails = changes.login.userDetails;
this.ListenTo(this._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._priority = options.priority ?? 0;
this._primer = options.primer ?? "";
this._tagsPreprocessor = function (properties) { this._tagsPreprocessor = function (properties) {
if (options.tagsPreprocessor === undefined) { if (options.tagsPreprocessor === undefined) {
return properties; return properties;
@ -202,38 +206,28 @@ class TagRendering extends UIElement implements TagDependantUIElement {
this._renderMapping = []; this._renderMapping = [];
this._freeform = options.freeform; this._freeform = options.freeform;
// Prepare the choices for the Radio buttons
const choices: UIElement[] = [];
const usedChoices: string [] = [];
for (const choice of options.mappings ?? []) { for (const choice of options.mappings ?? []) {
if (choice.k === null) { let choiceSubbed = {
this._mapping.push(choice); k: choice.k,
continue; txt: this.ApplyTemplate(choice.txt),
} priority: choice.priority
let choiceSubbed = choice; };
if (choice.substitute) { if (choice.substitute) {
choiceSubbed = { choiceSubbed = {
k: choice.k.substituteValues( k: choice.k.substituteValues(
options.tagsPreprocessor(this._source.data)), options.tagsPreprocessor(this._source.data)),
txt: this.ApplyTemplate(choice.txt), txt: this.ApplyTemplate(choice.txt),
substitute: false,
priority: choice.priority priority: choice.priority
} }
} }
const txt = choiceSubbed.txt this._mapping.push({
// Choices is what is shown in the radio buttons k: choiceSubbed.k,
if (usedChoices.indexOf(txt) < 0) { txt: choiceSubbed.txt
});
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
}
} }
@ -287,7 +281,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
placeholder?: string, placeholder?: string,
extraTags?: TagsFilter, extraTags?: TagsFilter,
}, },
mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[] mappings?: { k: TagsFilter, txt: string | UIElement, priority?: number, substitute?: boolean }[]
}): }):
InputElement<TagsFilter> { InputElement<TagsFilter> {
@ -315,7 +309,8 @@ class TagRendering extends UIElement implements TagDependantUIElement {
if (elements.length == 0) { 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) { if (elements.length == 1) {
return elements[0]; 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); return new FixedInputElement(mapping.txt, mapping.k);
} }
@ -421,10 +416,10 @@ class TagRendering extends UIElement implements TagDependantUIElement {
return true; return true;
} }
private RenderAnwser(): string { private RenderAnwser(): UIElement {
const tags = TagUtils.proprtiesToKV(this._source.data); const tags = TagUtils.proprtiesToKV(this._source.data);
let freeform = ""; let freeform: UIElement = new FixedUiElement("");
let freeformScore = -10; let freeformScore = -10;
if (this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined) { if (this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined) {
freeform = this.ApplyTemplate(this._freeform.renderTemplate); freeform = this.ApplyTemplate(this._freeform.renderTemplate);
@ -432,30 +427,30 @@ class TagRendering extends UIElement implements TagDependantUIElement {
} }
let highestScore = -100; let highestScore = -100;
let highestTemplate = undefined; let highestTemplate = undefined;
for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) { for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) {
if (oneOnOneElement.k == null || if (oneOnOneElement.k == null ||
oneOnOneElement.k.matches(tags)) { oneOnOneElement.k.matches(tags)) {
// We have found a matching key -> we use the template, but only if it scores better // We have found a matching key -> we use the template, but only if it scores better
let score = oneOnOneElement.priority ?? let score = oneOnOneElement.priority ??
(oneOnOneElement.k === null ? -1 : 0); (oneOnOneElement.k === null ? -1 : 0);
if (score > highestScore) { if (score > highestScore) {
highestScore = score; highestScore = score;
highestTemplate = oneOnOneElement.txt highestTemplate = oneOnOneElement.txt
}
} }
} }
}
if (freeformScore > highestScore) { if (freeformScore > highestScore) {
return freeform; 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) { if (this.IsQuestioning() || this._editMode.data) {
// Not yet known or questioning, we have to ask a question // Not yet known or questioning, we have to ask a question
const question = this._question.Render();
return "<div class='question'>" + return "<div class='question'>" +
"<span class='question-text'>" + this._question + "</span>" + "<span class='question-text'>" + question + "</span>" +
(this._question !== "" ? "<br/>" : "") + (question !== "" ? "<br/>" : "") +
"<div>"+ this._questionElement.Render() +"</div>"+ "<div>" + this._questionElement.Render() + "</div>" +
this._skipButton.Render() + this._skipButton.Render() +
this._saveButton.Render() + this._saveButton.Render() +
"</div>" "</div>"
} }
if (this.IsKnown()) { if (this.IsKnown()) {
const html = this.RenderAnwser(); const html = this.RenderAnwser().Render();
if (html == "") { if (html == "") {
return ""; return "";
} }
let editButton = ""; let editButton = "";
if(this._userDetails.data.loggedIn){ if (this._userDetails.data.loggedIn && this._question !== undefined) {
editButton = this._editButton.Render(); editButton = this._editButton.Render();
} }
@ -499,12 +495,15 @@ class TagRendering extends UIElement implements TagDependantUIElement {
return this._priority; 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); const tags = this._tagsPreprocessor(this._source.data);
return TagUtils.ApplyTemplate(template, tags); return new FixedUiElement(TagUtils.ApplyTemplate(template, tags));
} }
InnerUpdate(htmlElement: HTMLElement) { InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement); super.InnerUpdate(htmlElement);
this._questionElement.Update(); // Another manual update for them this._questionElement.Update(); // Another manual update for them

View file

@ -123,6 +123,7 @@ export class OsmConnection {
public preferenceSources : any = {} public preferenceSources : any = {}
public GetPreference(key: string) : UIEventSource<string>{ public GetPreference(key: string) : UIEventSource<string>{
key = "mapcomplete-"+key;
if (this.preferenceSources[key] !== undefined) { if (this.preferenceSources[key] !== undefined) {
return this.preferenceSources[key]; return this.preferenceSources[key];
} }

View file

@ -10,16 +10,16 @@ import {UIElement} from "../UI/UIElement";
export class StrayClickHandler { export class StrayClickHandler {
private _basemap: Basemap; private _basemap: Basemap;
private _lastMarker; private _lastMarker;
private _leftMessage: UIEventSource<() => UIElement>; private _fullScreenMessage: UIEventSource<UIElement>;
private _uiToShow: (() => UIElement); private _uiToShow: (() => UIElement);
constructor( constructor(
basemap: Basemap, basemap: Basemap,
selectElement: UIEventSource<any>, selectElement: UIEventSource<any>,
leftMessage: UIEventSource<() => UIElement>, fullScreenMessage: UIEventSource<UIElement>,
uiToShow: (() => UIElement)) { uiToShow: (() => UIElement)) {
this._basemap = basemap; this._basemap = basemap;
this._leftMessage = leftMessage; this._fullScreenMessage = fullScreenMessage;
this._uiToShow = uiToShow; this._uiToShow = uiToShow;
const self = this; const self = this;
const map = basemap.map; const map = basemap.map;
@ -38,7 +38,7 @@ export class StrayClickHandler {
self._lastMarker.bindPopup(popup).openPopup(); self._lastMarker.bindPopup(popup).openPopup();
self._lastMarker.on("click", () => { self._lastMarker.on("click", () => {
leftMessage.setData(self._uiToShow); fullScreenMessage.setData(self._uiToShow());
}); });
uiElement.Update(); uiElement.Update();
uiElement.Activate(); uiElement.Activate();

View file

@ -34,7 +34,7 @@ export class CenterMessageBox extends UIElement {
} }
protected InnerRender(): string { InnerRender(): string {
if (this._centermessage.data != "") { if (this._centermessage.data != "") {
return this._centermessage.data; return this._centermessage.data;

View file

@ -10,7 +10,7 @@ import {TagRenderingOptions} from "../Customizations/TagRendering";
import {OsmLink} from "../Customizations/Questions/OsmLink"; import {OsmLink} from "../Customizations/Questions/OsmLink";
import {WikipediaLink} from "../Customizations/Questions/WikipediaLink"; import {WikipediaLink} from "../Customizations/Questions/WikipediaLink";
import {And} from "../Logic/TagsFilter"; import {And} from "../Logic/TagsFilter";
import {TagDependantUIElement} from "../Customizations/UIElementConstructor"; import {TagDependantUIElement, TagDependantUIElementConstructor} from "../Customizations/UIElementConstructor";
export class FeatureInfoBox extends UIElement { export class FeatureInfoBox extends UIElement {
@ -31,7 +31,7 @@ export class FeatureInfoBox extends UIElement {
constructor( constructor(
tagsES: UIEventSource<any>, tagsES: UIEventSource<any>,
title: TagRenderingOptions, title: TagRenderingOptions,
elementsToShow: TagRenderingOptions[], elementsToShow: TagDependantUIElementConstructor[],
changes: Changes, changes: Changes,
userDetails: UIEventSource<UserDetails> userDetails: UIEventSource<UserDetails>
) { ) {

View file

@ -34,14 +34,14 @@ class ImageCarouselWithUpload extends TagDependantUIElement {
const changes = dependencies.changes; const changes = dependencies.changes;
this._imageElement = new ImageCarousel(tags, changes); this._imageElement = new ImageCarousel(tags, changes);
const userDetails = changes.login.userDetails; 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, this._pictureUploader = new OsmImageUploadHandler(tags,
userDetails, license, userDetails, license,
changes, this._imageElement.slideshow).getUI(); changes, this._imageElement.slideshow).getUI();
} }
protected InnerRender(): string { InnerRender(): string {
return this._imageElement.Render() + return this._imageElement.Render() +
this._pictureUploader.Render(); this._pictureUploader.Render();
} }

View file

@ -5,6 +5,7 @@ import {Imgur} from "../Logic/Imgur";
import {UserDetails} from "../Logic/OsmConnection"; import {UserDetails} from "../Logic/OsmConnection";
import {DropDown} from "./Input/DropDown"; import {DropDown} from "./Input/DropDown";
import {VariableUiElement} from "./Base/VariableUIElement"; import {VariableUiElement} from "./Base/VariableUIElement";
import Translations from "./i18n/Translations";
export class ImageUploadFlow extends UIElement { export class ImageUploadFlow extends UIElement {
private _licensePicker: UIElement; private _licensePicker: UIElement;
@ -66,7 +67,7 @@ export class ImageUploadFlow extends UIElement {
"<div class='imageflow-file-input-wrapper'>" + "<div class='imageflow-file-input-wrapper'>" +
"<img src='./assets/camera-plus.svg' alt='upload image'/> " + "<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 class='break'></div>" +
"</div>" + "</div>" +

View file

@ -50,7 +50,10 @@ export class DropDown<T> extends InputElement<T> {
InnerRender(): string { InnerRender(): string {
if(this._values.length <=1){
return "";
}
let options = ""; let options = "";
for (let i = 0; i < this._values.length; i++) { for (let i = 0; i < this._values.length; i++) {
options += "<option value='" + i + "'>" + this._values[i].shown.InnerRender() + "</option>" options += "<option value='" + i + "'>" + this._values[i].shown.InnerRender() + "</option>"
@ -65,8 +68,12 @@ export class DropDown<T> extends InputElement<T> {
} }
protected InnerUpdate(element) { protected InnerUpdate(element) {
var e = document.getElementById("dropdown-" + this.id); var e = document.getElementById("dropdown-" + this.id);
if(e === null){
return;
}
const self = this; const self = this;
e.onchange = (() => { e.onchange = (() => {
// @ts-ignore // @ts-ignore

View file

@ -105,7 +105,6 @@ export class TextField<T> extends InputElement<T> {
} }
IsValid(t: T): boolean { IsValid(t: T): boolean {
console.log("TXT IS valid?",t,this._toString(t))
if(t === undefined || t === null){ if(t === undefined || t === null){
return false; return false;
} }

View file

@ -6,9 +6,9 @@ import {UIElement} from "./UIElement";
import {VariableUiElement} from "./Base/VariableUIElement"; import {VariableUiElement} from "./Base/VariableUIElement";
export class MessageBoxHandler { export class MessageBoxHandler {
private _uielement: UIEventSource<() => UIElement>; private _uielement: UIEventSource<UIElement>;
constructor(uielement: UIEventSource<() => UIElement>, constructor(uielement: UIEventSource<UIElement>,
onClear: (() => void)) { onClear: (() => void)) {
this._uielement = uielement; this._uielement = uielement;
this.listenTo(uielement); this.listenTo(uielement);
@ -22,14 +22,13 @@ export class MessageBoxHandler {
} }
} }
new VariableUiElement(new UIEventSource<string>("<h2>Return to the map</h2>"), new VariableUiElement(new UIEventSource<string>("<h2>Return to the map</h2>"))
() => { .onClick(() => {
document.getElementById("to-the-map").onclick = function () { console.log("Clicked 'return to the map'")
uielement.setData(undefined); uielement.setData(undefined);
onClear(); onClear();
} })
} .AttachTo("to-the-map");
).AttachTo("to-the-map");
} }
@ -55,7 +54,7 @@ export class MessageBoxHandler {
location.hash = "#element" location.hash = "#element"
wrapper.classList.remove("hidden"); wrapper.classList.remove("hidden");
gen() gen
?.HideOnEmpty(true) ?.HideOnEmpty(true)
?.AttachTo("messagesboxmobile") ?.AttachTo("messagesboxmobile")
?.Activate(); ?.Activate();

View file

@ -21,7 +21,7 @@ export class PendingChanges extends UIElement {
}) })
} }
protected InnerRender(): string { InnerRender(): string {
if (this._isSaving.data) { if (this._isSaving.data) {
return "<span class='alert'>Saving</span>"; return "<span class='alert'>Saving</span>";
} }

View file

@ -37,11 +37,10 @@ export abstract class UIElement {
Update(): void { Update(): void {
let element = document.getElementById(this.id); let element = document.getElementById(this.id);
if (element === null || element === undefined) { if (element === undefined || element === null) {
// The element is not painted // The element is not painted
return; return;
} }
element.innerHTML = this.InnerRender(); element.innerHTML = this.InnerRender();
if (this._hideIfEmpty) { if (this._hideIfEmpty) {
if (element.innerHTML === "") { if (element.innerHTML === "") {

View file

@ -66,5 +66,16 @@ 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);
}
}
} }

View file

@ -5,6 +5,7 @@ import {Basemap} from "../Logic/Basemap";
import L from "leaflet"; import L from "leaflet";
import {FixedUiElement} from "./Base/FixedUiElement"; import {FixedUiElement} from "./Base/FixedUiElement";
import {VariableUiElement} from "./Base/VariableUIElement"; import {VariableUiElement} from "./Base/VariableUIElement";
import Translations from "./i18n/Translations";
/** /**
* Handles and updates the user badge * Handles and updates the user badge
@ -15,13 +16,15 @@ export class UserBadge extends UIElement {
private _logout: UIElement; private _logout: UIElement;
private _basemap: Basemap; private _basemap: Basemap;
private _homeButton: UIElement; private _homeButton: UIElement;
private _languagePicker: UIElement;
constructor(userDetails: UIEventSource<UserDetails>, constructor(userDetails: UIEventSource<UserDetails>,
pendingChanges: UIElement, pendingChanges: UIElement,
languagePicker: UIElement,
basemap: Basemap) { basemap: Basemap) {
super(userDetails); super(userDetails);
this._languagePicker = languagePicker;
this._userDetails = userDetails; this._userDetails = userDetails;
this._pendingChanges = pendingChanges; this._pendingChanges = pendingChanges;
this._basemap = basemap; this._basemap = basemap;
@ -61,7 +64,7 @@ export class UserBadge extends UIElement {
InnerRender(): string { InnerRender(): string {
const user = this._userDetails.data; const user = this._userDetails.data;
if (!user.loggedIn) { 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 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> " + "</a></span> " +
this._logout.Render() + this._logout.Render() +
this._languagePicker.Render() +
this._pendingChanges.Render() + this._pendingChanges.Render() +
"</p>" + "</p>" +

View file

@ -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 { export default class Locale {
public static language: UIEventSource<string> = new UIEventSource(Locale.getInitialLanguage()) public static language: UIEventSource<string> = Locale.getInitialLanguage()
public static init() {
Locale.language.addCallback(data => {
localStorage.setItem(LANGUAGE_KEY, data)
})
}
private static 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;
} }
} }

View file

@ -1,16 +1,30 @@
import { UIElement } from "../UIElement" import { UIElement } from "../UIElement"
import Locale from "./Locale" import Locale from "./Locale"
import {FixedUiElement} from "../Base/FixedUiElement";
export default class Translation extends UIElement{ export default class Translation extends UIElement {
protected InnerRender(): string {
return this.translations[Locale.language.data]
}
public readonly translations: object public readonly translations: object
constructor(translations: object) { constructor(translations: object) {
super(Locale.language) super(Locale.language)
this.translations = translations 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;
}
} }

View file

@ -1,18 +1,42 @@
import Translation from "./Translation"; import Translation from "./Translation";
import {UIElement} from "../UIElement";
import {FixedUiElement} from "../Base/FixedUiElement";
export default class Translations { export default class Translations {
static t = { static cylofix = {
cylofix: { title: new Translation({
title: new Translation({en: 'Cyclofix bicycle infrastructure', nl: 'Cyclofix fietsinfrastructuur', fr: 'TODO: FRENCH TRANSLATION'}), en: 'Cyclofix bicycle infrastructure',
description: new Translation({ nl: 'Cyclofix fietsinfrastructuur',
en: "On this map we want to collect data about the whereabouts of bicycle pumps and public racks in Brussels." + fr: 'TODO: FRENCH TRANSLATION'
"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." + description: new Translation({
"Hierdoor kunnen fietsers snel de dichtstbijzijnde infrastructuur vinden die voldoet aan hun behoeften.", en: "On this map we want to collect data about the whereabouts of bicycle pumps and public racks in Brussels." +
fr: "Sur cette carte, nous voulons collecter des données sur la localisation des pompes à vélo et des supports publics à Bruxelles." + "As a result, cyclists will be able to quickly find the nearest infrastructure for their needs.",
"Les cyclistes pourront ainsi trouver rapidement l'infrastructure la plus proche de leurs besoins." 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);
}
} }

View file

@ -307,23 +307,26 @@ form {
} }
} }
#to-the-map { #to-the-map {
position: relative;
}
#to-the-map h2{
position: absolute;
height: 4em; height: 4em;
padding: 0.5em; padding: 0.5em;
margin: 0; margin: 0;
position: absolute;
bottom: 0;
right: 0;
padding-right: 2em; padding-right: 2em;
padding-top: 1em;
text-align: center;
width: 100%; width: 100%;
text-align: right;
color: white; color: white;
background-color: #7ebc6f; background-color: #7ebc6f;
cursor: pointer; cursor: pointer;
} }

View file

@ -28,17 +28,13 @@
Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is
blocking it. blocking it.
</div> </div>
<div id="language-select"></div>
<br/> <br/>
<div id="searchbox"></div> <div id="searchbox"></div>
</div> </div>
<br/> <br/>
<div id="messagesbox-wrapper"> <div id="messagesbox-wrapper">
<div id="collapseButton"></div> <div id="collapseButton"></div>
<select id="language-select">
<option>EN</option>
<option>NL</option>
<option>FR</option>
</select>
<div id="messagesbox"></div> <div id="messagesbox"></div>
</div> </div>
</div> </div>

View file

@ -25,7 +25,10 @@ import {All} from "./Customizations/Layouts/All";
import Translations from "./UI/i18n/Translations"; import Translations from "./UI/i18n/Translations";
import Translation from "./UI/i18n/Translation"; import Translation from "./UI/i18n/Translation";
import Locale from "./UI/i18n/Locale"; 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 ----------------- // --------------------- Read the URL parameters -----------------
@ -90,22 +93,29 @@ if (paramDict.test) {
const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout]; const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout];
console.log("Using layout: ", layoutToUse.name); console.log("Using layout: ", layoutToUse.name);
document.title = layoutToUse.title.Render(); document.title = layoutToUse.title.InnerRender();
Locale.language.addCallback(e => { Locale.language.addCallback(e => {
document.title = layoutToUse.title.Render(); document.title = layoutToUse.title.InnerRender();
}) })
// ----------------- Setup a few event sources ------------- // ----------------- 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 // The message that should be shown at the center of the screen
const centerMessage = new UIEventSource<string>(""); 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 // 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 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); const selectedElement = new UIEventSource<any>(undefined);
@ -119,9 +129,18 @@ const locationControl = new UIEventSource<{ lat: number, lon: number, zoom: numb
// ----------------- Prepare the important objects ----------------- // ----------------- 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 saveTimeout = 30000; // After this many milliseconds without changes, saves are sent of to OSM
const allElements = new ElementStorage(); const allElements = new ElementStorage();
const osmConnection = new OsmConnection(dryRun);
const changes = new Changes( const changes = new Changes(
"Beantwoorden van vragen met #MapComplete voor vragenset #" + layoutToUse.name, "Beantwoorden van vragen met #MapComplete voor vragenset #" + layoutToUse.name,
osmConnection, allElements); osmConnection, allElements);
@ -191,8 +210,13 @@ const layerUpdater = new LayerUpdater(bm, minZoom, flayers);
// ------------------ Setup various UI elements ------------ // ------------------ 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, return new SimpleAddUI(bm.Location,
bm.LastClickLocation, bm.LastClickLocation,
changes, 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) => { selectedElement.addCallback((data) => {
// Which is the applicable set? // Which is the applicable set?
@ -213,14 +237,16 @@ selectedElement.addCallback((data) => {
const applicable = layer.overpassFilter.matches(TagUtils.proprtiesToKV(data)); const applicable = layer.overpassFilter.matches(TagUtils.proprtiesToKV(data));
if (applicable) { if (applicable) {
// This layer is the layer that gives the questions // This layer is the layer that gives the questions
leftMessage.setData(() =>
new FeatureInfoBox( const featureBox = new FeatureInfoBox(
allElements.getElement(data.id), allElements.getElement(data.id),
layer.title, layer.title,
layer.elementsToShow, layer.elementsToShow,
changes, changes,
osmConnection.userDetails osmConnection.userDetails
)); );
fullScreenMessage.setData(featureBox);
break; break;
} }
} }
@ -231,7 +257,10 @@ selectedElement.addCallback((data) => {
const pendingChanges = new PendingChanges( const pendingChanges = new PendingChanges(
changes, secondsTillChangesAreSaved,); changes, secondsTillChangesAreSaved,);
new UserBadge(osmConnection.userDetails, pendingChanges, bm) new UserBadge(osmConnection.userDetails,
pendingChanges,
new FixedUiElement(""),
bm)
.AttachTo('userbadge'); .AttachTo('userbadge');
new SearchAndGo(bm).AttachTo("searchbox"); new SearchAndGo(bm).AttachTo("searchbox");
@ -239,7 +268,7 @@ new SearchAndGo(bm).AttachTo("searchbox");
new CollapseButton("messagesbox") new CollapseButton("messagesbox")
.AttachTo("collapseButton"); .AttachTo("collapseButton");
var welcomeMessage = () => { var generateWelcomeMessage = () => {
return new VariableUiElement( return new VariableUiElement(
osmConnection.userDetails.map((userdetails) => { osmConnection.userDetails.map((userdetails) => {
var login = layoutToUse.gettingStartedPlzLogin; var login = layoutToUse.gettingStartedPlzLogin;
@ -254,11 +283,11 @@ var welcomeMessage = () => {
osmConnection.registerActivateOsmAUthenticationClass() osmConnection.registerActivateOsmAUthenticationClass()
}); });
} }
leftMessage.setData(welcomeMessage); generateWelcomeMessage().AttachTo("messagesbox");
welcomeMessage().AttachTo("messagesbox"); fullScreenMessage.setData(generateWelcomeMessage());
var messageBox = new MessageBoxHandler(leftMessage, () => { var messageBox = new MessageBoxHandler(fullScreenMessage, () => {
selectedElement.setData(undefined) selectedElement.setData(undefined)
}); });
@ -286,13 +315,3 @@ locationControl.ping();
messageBox.update(); 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')

10
test.ts
View file

@ -1 +1,9 @@
console.log("Hello world") 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");