diff --git a/Customizations/Layers/BikeCafes.ts b/Customizations/Layers/BikeCafes.ts index 66bb27464..5dc75c433 100644 --- a/Customizations/Layers/BikeCafes.ts +++ b/Customizations/Layers/BikeCafes.ts @@ -9,29 +9,20 @@ import Website from "../Questions/Website"; import CafeRepair from "../Questions/bike/CafeRepair"; import CafeDiy from "../Questions/bike/CafeDiy"; import CafePump from "../Questions/bike/CafePump"; +import {EmailQuestion} from "../Questions/EmailQuestion"; export default class BikeCafes extends LayerDefinition { - private readonly repairsBikes = anyValueExcept("service:bicycle:repair", "no") - private readonly hasPump = new Tag("service:bicycle:pump", "yes") - private readonly diy = new Tag("service:bicycle:diy", "yes") - private readonly bikeServices = [ - this.diy, - this.repairsBikes, - this.hasPump - ] private readonly to = Translations.t.cyclofix.cafe constructor() { - super(); - this.name = this.to.name; - this.icon = "./assets/bike/cafe.svg"; + super() + this.name = this.to.name + this.icon = "./assets/bike/cafe.svg" this.overpassFilter = new And([ + new Tag("amenity", /^pub|bar|cafe$/), new Or([ - new Regex("amenity", "^pub|bar|cafe") - ]), - new Or([ - ...this.bikeServices, + new Tag(/^service:bicycle:/, "*"), new Tag("pub", "cycling") ]) ]) @@ -49,24 +40,24 @@ export default class BikeCafes extends LayerDefinition { this.maxAllowedOverlapPercentage = 10; - this.minzoom = 13; - this.style = this.generateStyleFunction(); + this.minzoom = 13 + this.style = this.generateStyleFunction() this.title = new FixedText(this.to.title) this.elementsToShow = [ new ImageCarouselWithUploadConstructor(), new CafeName(), - new PhoneNumberQuestion("{name}"), new Website("{name}"), + new PhoneNumberQuestion("{name}"), + new EmailQuestion("{name}"), new CafeRepair(), new CafeDiy(), new CafePump() - ]; - this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY; - + ] + this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY } private generateStyleFunction() { - const self = this; + const self = this return function (properties: any) { return { color: "#00bb00", @@ -75,7 +66,7 @@ export default class BikeCafes extends LayerDefinition { iconSize: [50, 50], iconAnchor: [25,50] } - }; - }; + } + } } -} +} \ No newline at end of file diff --git a/Customizations/Layers/BikeOtherShops.ts b/Customizations/Layers/BikeOtherShops.ts index bfb4fee85..60e2bc7c3 100644 --- a/Customizations/Layers/BikeOtherShops.ts +++ b/Customizations/Layers/BikeOtherShops.ts @@ -16,19 +16,6 @@ import Website from "../Questions/Website"; export default class BikeOtherShops extends LayerDefinition { private readonly sellsBikes = new Tag("service:bicycle:retail", "yes") - private readonly repairsBikes = anyValueExcept("service:bicycle:repair", "no") - private readonly rentsBikes = new Tag("service:bicycle:rental", "yes") - private readonly hasPump = new Tag("service:bicycle:pump", "yes") - private readonly hasDiy = new Tag("service:bicycle:diy", "yes") - private readonly sellsSecondHand = anyValueExcept("service:bicycle:repair", "no") - private readonly hasBikeServices = new Or([ - this.sellsBikes, - this.repairsBikes, - // this.rentsBikes, - // this.hasPump, - // this.hasDiy, - // this.sellsSecondHand - ]) private readonly to = Translations.t.cyclofix.nonBikeShop @@ -38,7 +25,7 @@ export default class BikeOtherShops extends LayerDefinition { this.icon = "./assets/bike/non_bike_repair_shop.svg" this.overpassFilter = new And([ anyValueExcept("shop", "bicycle"), - this.hasBikeServices + new Tag(/^service:bicycle:/, "*"), ]) this.presets = [] this.maxAllowedOverlapPercentage = 10 diff --git a/Customizations/Layers/BikeShops.ts b/Customizations/Layers/BikeShops.ts index 5a2b78757..d532e6123 100644 --- a/Customizations/Layers/BikeShops.ts +++ b/Customizations/Layers/BikeShops.ts @@ -13,6 +13,7 @@ import ShopSecondHand from "../Questions/bike/ShopSecondHand"; import { TagRenderingOptions } from "../TagRendering"; import {PhoneNumberQuestion} from "../Questions/PhoneNumberQuestion"; import Website from "../Questions/Website"; +import {EmailQuestion} from "../Questions/EmailQuestion"; export default class BikeShops extends LayerDefinition { @@ -56,8 +57,9 @@ export default class BikeShops extends LayerDefinition { this.elementsToShow = [ new ImageCarouselWithUploadConstructor(), new ShopName(), - new PhoneNumberQuestion("{name}"), new Website("{name}"), + new PhoneNumberQuestion("{name}"), + new EmailQuestion("{name}"), new ShopRetail(), new ShopRental(), new ShopRepair(), diff --git a/Customizations/Questions/EmailQuestion.ts b/Customizations/Questions/EmailQuestion.ts new file mode 100644 index 000000000..ee99bd4e2 --- /dev/null +++ b/Customizations/Questions/EmailQuestion.ts @@ -0,0 +1,18 @@ +import {TagRenderingOptions} from "../TagRendering"; +import {UIElement} from "../../UI/UIElement"; +import Translations from "../../UI/i18n/Translations"; + +export class EmailQuestion extends TagRenderingOptions { + + constructor(category: string | UIElement) { + super({ + question: Translations.t.general.questions.emailOf.Subs({category: category}), + freeform: { + renderTemplate: Translations.t.general.questions.emailIs.Subs({category: category}), + template: "$email$", + key: "email" + } + }); + } + +} \ No newline at end of file diff --git a/Logic/TagsFilter.ts b/Logic/TagsFilter.ts index 0a2d3a556..827548ad3 100644 --- a/Logic/TagsFilter.ts +++ b/Logic/TagsFilter.ts @@ -55,24 +55,37 @@ export class Regex extends TagsFilter { export class Tag extends TagsFilter { - public key: string - public value: string + public key: string | RegExp + public value: string | RegExp public invertValue: boolean - constructor(key: string, value: string, invertValue = false) { + constructor(key: string | RegExp, value: string | RegExp, invertValue = false) { + if (value === "*" && invertValue) { + throw new Error("Invalid combination: invertValue && value == *") + } + + if (value instanceof RegExp && invertValue) { + throw new Error("Unsupported combination: RegExp value and inverted value (use regex to invert the match)") + } + super() this.key = key this.value = value this.invertValue = invertValue + } - if (value === "*" && invertValue) { - throw new Error("Invalid combination: invertValue && value == *") + private static regexOrStrMatches(regexOrStr: string | RegExp, testStr: string) { + if (typeof regexOrStr === 'string') { + return regexOrStr === testStr + } else if (regexOrStr instanceof RegExp) { + return (regexOrStr as RegExp).test(testStr) } + throw new Error(" must be of type RegExp or string") } matches(tags: { k: string; v: string }[]): boolean { for (const tag of tags) { - if (tag.k === this.key) { + if (Tag.regexOrStrMatches(this.key, tag.k)) { if (tag.v === "") { // This tag has been removed return this.value === "" @@ -82,7 +95,7 @@ export class Tag extends TagsFilter { return true; } - return this.value === tag.v !== this.invertValue + return Tag.regexOrStrMatches(this.value, tag.v) !== this.invertValue } } @@ -94,19 +107,33 @@ export class Tag extends TagsFilter { } asOverpass(): string[] { - if (this.value === "*") { - return ['["' + this.key + '"]']; + const keyIsRegex = this.key instanceof RegExp + const key = keyIsRegex ? (this.key as RegExp).source : this.key + + const valIsRegex = this.value instanceof RegExp + const val = valIsRegex ? (this.value as RegExp).source : this.value + + const regexKeyPrefix = keyIsRegex ? '~' : '' + const anyVal = this.value === "*" + + if (anyVal && !keyIsRegex) { + return [`[${regexKeyPrefix}"${key}"]`]; } if (this.value === "") { // NOT having this key - return ['[!"' + this.key + '"]']; + return ['[!"' + key + '"]']; } - const compareOperator = this.invertValue ? '!=' : '=' - return ['["' + this.key + '"' + compareOperator + '"' + this.value + '"]']; + + const compareOperator = (valIsRegex || keyIsRegex) ? '~' : (this.invertValue ? '!=' : '=') + return [`[${regexKeyPrefix}"${key}"${compareOperator}"${keyIsRegex && anyVal ? '.' : val}"]`]; } substituteValues(tags: any) { - return new Tag(this.key, TagUtils.ApplyTemplate(this.value, tags)); + if (typeof this.value !== 'string') { + throw new Error("substituteValues() only possible with tag value of type string") + } + + return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags)); } } diff --git a/UI/Base/SubtleButton.ts b/UI/Base/SubtleButton.ts index 68523f643..757fd3a95 100644 --- a/UI/Base/SubtleButton.ts +++ b/UI/Base/SubtleButton.ts @@ -1,20 +1,33 @@ import {UIElement} from "../UIElement"; import Translations from "../i18n/Translations"; import Combine from "./Combine"; +import {link} from "fs"; export class SubtleButton extends UIElement{ private imageUrl: string; private message: UIElement; + private linkTo: string = undefined; - constructor(imageUrl: string, message: string | UIElement) { + constructor(imageUrl: string, message: string | UIElement, linkTo : string = undefined) { super(undefined); + this.linkTo = linkTo; this.message = Translations.W(message); this.imageUrl = imageUrl; } InnerRender(): string { + + if(this.linkTo != undefined){ + return new Combine([ + ``, + this.imageUrl !== undefined ? `` : "", + this.message, + '' + ]).Render(); + } + return new Combine([ '', this.imageUrl !== undefined ? `` : "", diff --git a/UI/LayerSelection.ts b/UI/LayerSelection.ts index 28432b91c..1e0bcb6f4 100644 --- a/UI/LayerSelection.ts +++ b/UI/LayerSelection.ts @@ -26,7 +26,7 @@ export class LayerSelection extends UIElement{ this._checkboxes.push(new CheckBox( new Combine([checkbox, icon, name]), new Combine([ - Img.checkmark, + Img.no_checkmark, icon, layer.layerDef.name]), layer.isDisplayed)); diff --git a/UI/MoreScreen.ts b/UI/MoreScreen.ts index b7018b7ad..f4f8dbded 100644 --- a/UI/MoreScreen.ts +++ b/UI/MoreScreen.ts @@ -34,16 +34,12 @@ export class MoreScreen extends UIElement { const link = new SubtleButton(layout.icon, new Combine([ - ``, - "
", "", Translations.W(layout.title), "", "
", Translations.W(layout.description), - "
", - "
" - ])); + ]), linkText); els.push(link) } diff --git a/UI/SimpleAddUI.ts b/UI/SimpleAddUI.ts index a76392c28..a8cbff0c5 100644 --- a/UI/SimpleAddUI.ts +++ b/UI/SimpleAddUI.ts @@ -35,6 +35,8 @@ export class SimpleAddUI extends UIElement { = new UIEventSource(undefined); private confirmButton: UIElement = undefined; private cancelButton: UIElement; + private goToInboxButton: UIElement = new SubtleButton("/assets/envelope.svg", + Translations.t.general.goToInbox, "https://www.openstreetmap.org/messages/inbox"); constructor(zoomlevel: UIEventSource<{ zoom: number }>, lastClickLocation: UIEventSource<{ lat: number, lon: number }>, @@ -132,7 +134,9 @@ export class SimpleAddUI extends UIElement { if (this._userDetails.data.unreadMessages > 0) { return new Combine([header, "", Translations.t.general.readYourMessages, - ""]).Render(); + "
", + this.goToInboxButton + ]).Render(); } if (this._userDetails.data.dryRun) { diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index 45b8d9eec..e7bfdb1ba 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -748,7 +748,16 @@ export default class Translations { websiteIs: new T({ en: "Website: {website}", nl: "Website: {website}" - }) + }), + emailOf: new T({ + en: "What is the email address of {category}?", + nl: "Wat is het email-adres van {category}?" + } + ), + emailIs: new T({ + en: "The email address of this {category} is {email}", + nl: "Het email-adres van {category} is {email}" + }), }, openStreetMapIntro: new T({ @@ -794,6 +803,10 @@ export default class Translations { fewChangesBefore: new T({ en: "Please, answer a few questions of existing points before adding a new point.", nl: "Gelieve eerst enkele vragen van bestaande punten te beantwoorden vooraleer zelf punten toe te voegen." + }), + goToInbox: new T({ + en: "Open inbox", + nl: "Ga naar de berichten" }) } } diff --git a/index.css b/index.css index 1d844eb3f..990c3088d 100644 --- a/index.css +++ b/index.css @@ -4,10 +4,6 @@ html, body { padding: 0; } -a { - text-decoration: unset; - color:unset; -} body { font-family: 'Helvetica Neue', Arial, sans-serif; @@ -1206,6 +1202,13 @@ form { } +.subtle-button a { + text-decoration: unset !important; + color:unset !important; + display: block ruby; +} + + .subtle-button img{ width: 3em; max-height: 3em; diff --git a/index.ts b/index.ts index bd28fd361..bd8840eb3 100644 --- a/index.ts +++ b/index.ts @@ -105,11 +105,11 @@ const fullScreenMessage = new UIEventSource(undefined); // The latest element that was selected - used to generate the right UI at the right place const selectedElement = new UIEventSource<{ feature: any }>(undefined); -const zoom = QueryParameters.GetQueryParameter("z", "" + layoutToUse.startzoom) +const zoom = QueryParameters.GetQueryParameter("z", undefined) .syncWith(LocalStorageSource.Get("zoom")); -const lat = QueryParameters.GetQueryParameter("lat", "" + layoutToUse.startLat) +const lat = QueryParameters.GetQueryParameter("lat", undefined) .syncWith(LocalStorageSource.Get("lat")); -const lon = QueryParameters.GetQueryParameter("lon", "" + layoutToUse.startLon) +const lon = QueryParameters.GetQueryParameter("lon", undefined) .syncWith(LocalStorageSource.Get("lon")); const featureSwitchUserbadge = QueryParameters.GetQueryParameter("fs-userbadge", ""+layoutToUse.enableUserBadge);