Add email question, some tweaks

This commit is contained in:
Pieter Vander Vennet 2020-07-29 19:02:36 +02:00
commit f67508336a
12 changed files with 123 additions and 69 deletions

View file

@ -9,29 +9,20 @@ import Website from "../Questions/Website";
import CafeRepair from "../Questions/bike/CafeRepair"; import CafeRepair from "../Questions/bike/CafeRepair";
import CafeDiy from "../Questions/bike/CafeDiy"; import CafeDiy from "../Questions/bike/CafeDiy";
import CafePump from "../Questions/bike/CafePump"; import CafePump from "../Questions/bike/CafePump";
import {EmailQuestion} from "../Questions/EmailQuestion";
export default class BikeCafes extends LayerDefinition { 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 private readonly to = Translations.t.cyclofix.cafe
constructor() { constructor() {
super(); super()
this.name = this.to.name; this.name = this.to.name
this.icon = "./assets/bike/cafe.svg"; this.icon = "./assets/bike/cafe.svg"
this.overpassFilter = new And([ this.overpassFilter = new And([
new Tag("amenity", /^pub|bar|cafe$/),
new Or([ new Or([
new Regex("amenity", "^pub|bar|cafe") new Tag(/^service:bicycle:/, "*"),
]),
new Or([
...this.bikeServices,
new Tag("pub", "cycling") new Tag("pub", "cycling")
]) ])
]) ])
@ -49,24 +40,24 @@ export default class BikeCafes extends LayerDefinition {
this.maxAllowedOverlapPercentage = 10; this.maxAllowedOverlapPercentage = 10;
this.minzoom = 13; this.minzoom = 13
this.style = this.generateStyleFunction(); this.style = this.generateStyleFunction()
this.title = new FixedText(this.to.title) this.title = new FixedText(this.to.title)
this.elementsToShow = [ this.elementsToShow = [
new ImageCarouselWithUploadConstructor(), new ImageCarouselWithUploadConstructor(),
new CafeName(), new CafeName(),
new PhoneNumberQuestion("{name}"),
new Website("{name}"), new Website("{name}"),
new PhoneNumberQuestion("{name}"),
new EmailQuestion("{name}"),
new CafeRepair(), new CafeRepair(),
new CafeDiy(), new CafeDiy(),
new CafePump() new CafePump()
]; ]
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY; this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
} }
private generateStyleFunction() { private generateStyleFunction() {
const self = this; const self = this
return function (properties: any) { return function (properties: any) {
return { return {
color: "#00bb00", color: "#00bb00",
@ -75,7 +66,7 @@ export default class BikeCafes extends LayerDefinition {
iconSize: [50, 50], iconSize: [50, 50],
iconAnchor: [25,50] iconAnchor: [25,50]
} }
}; }
}; }
} }
} }

View file

@ -16,19 +16,6 @@ import Website from "../Questions/Website";
export default class BikeOtherShops extends LayerDefinition { export default class BikeOtherShops extends LayerDefinition {
private readonly sellsBikes = new Tag("service:bicycle:retail", "yes") 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 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.icon = "./assets/bike/non_bike_repair_shop.svg"
this.overpassFilter = new And([ this.overpassFilter = new And([
anyValueExcept("shop", "bicycle"), anyValueExcept("shop", "bicycle"),
this.hasBikeServices new Tag(/^service:bicycle:/, "*"),
]) ])
this.presets = [] this.presets = []
this.maxAllowedOverlapPercentage = 10 this.maxAllowedOverlapPercentage = 10

View file

@ -13,6 +13,7 @@ import ShopSecondHand from "../Questions/bike/ShopSecondHand";
import { TagRenderingOptions } from "../TagRendering"; import { TagRenderingOptions } from "../TagRendering";
import {PhoneNumberQuestion} from "../Questions/PhoneNumberQuestion"; import {PhoneNumberQuestion} from "../Questions/PhoneNumberQuestion";
import Website from "../Questions/Website"; import Website from "../Questions/Website";
import {EmailQuestion} from "../Questions/EmailQuestion";
export default class BikeShops extends LayerDefinition { export default class BikeShops extends LayerDefinition {
@ -56,8 +57,9 @@ export default class BikeShops extends LayerDefinition {
this.elementsToShow = [ this.elementsToShow = [
new ImageCarouselWithUploadConstructor(), new ImageCarouselWithUploadConstructor(),
new ShopName(), new ShopName(),
new PhoneNumberQuestion("{name}"),
new Website("{name}"), new Website("{name}"),
new PhoneNumberQuestion("{name}"),
new EmailQuestion("{name}"),
new ShopRetail(), new ShopRetail(),
new ShopRental(), new ShopRental(),
new ShopRepair(), new ShopRepair(),

View file

@ -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"
}
});
}
}

View file

@ -55,24 +55,37 @@ export class Regex extends TagsFilter {
export class Tag extends TagsFilter { export class Tag extends TagsFilter {
public key: string public key: string | RegExp
public value: string public value: string | RegExp
public invertValue: boolean 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() super()
this.key = key this.key = key
this.value = value this.value = value
this.invertValue = invertValue this.invertValue = invertValue
}
if (value === "*" && invertValue) { private static regexOrStrMatches(regexOrStr: string | RegExp, testStr: string) {
throw new Error("Invalid combination: invertValue && value == *") if (typeof regexOrStr === 'string') {
return regexOrStr === testStr
} else if (regexOrStr instanceof RegExp) {
return (regexOrStr as RegExp).test(testStr)
} }
throw new Error("<regexOrStr> must be of type RegExp or string")
} }
matches(tags: { k: string; v: string }[]): boolean { matches(tags: { k: string; v: string }[]): boolean {
for (const tag of tags) { for (const tag of tags) {
if (tag.k === this.key) { if (Tag.regexOrStrMatches(this.key, tag.k)) {
if (tag.v === "") { if (tag.v === "") {
// This tag has been removed // This tag has been removed
return this.value === "" return this.value === ""
@ -82,7 +95,7 @@ export class Tag extends TagsFilter {
return true; 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[] { asOverpass(): string[] {
if (this.value === "*") { const keyIsRegex = this.key instanceof RegExp
return ['["' + this.key + '"]']; 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 === "") { if (this.value === "") {
// NOT having this key // 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) { 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));
} }
} }

View file

@ -1,20 +1,33 @@
import {UIElement} from "../UIElement"; import {UIElement} from "../UIElement";
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations";
import Combine from "./Combine"; import Combine from "./Combine";
import {link} from "fs";
export class SubtleButton extends UIElement{ export class SubtleButton extends UIElement{
private imageUrl: string; private imageUrl: string;
private message: UIElement; private message: UIElement;
private linkTo: string = undefined;
constructor(imageUrl: string, message: string | UIElement) { constructor(imageUrl: string, message: string | UIElement, linkTo : string = undefined) {
super(undefined); super(undefined);
this.linkTo = linkTo;
this.message = Translations.W(message); this.message = Translations.W(message);
this.imageUrl = imageUrl; this.imageUrl = imageUrl;
} }
InnerRender(): string { InnerRender(): string {
if(this.linkTo != undefined){
return new Combine([
`<a class="subtle-button" href="${this.linkTo}" target="_blank">`,
this.imageUrl !== undefined ? `<img src='${this.imageUrl}'>` : "",
this.message,
'</a>'
]).Render();
}
return new Combine([ return new Combine([
'<span class="subtle-button">', '<span class="subtle-button">',
this.imageUrl !== undefined ? `<img src='${this.imageUrl}'>` : "", this.imageUrl !== undefined ? `<img src='${this.imageUrl}'>` : "",

View file

@ -26,7 +26,7 @@ export class LayerSelection extends UIElement{
this._checkboxes.push(new CheckBox( this._checkboxes.push(new CheckBox(
new Combine([checkbox, icon, name]), new Combine([checkbox, icon, name]),
new Combine([ new Combine([
Img.checkmark, Img.no_checkmark,
icon, icon,
layer.layerDef.name]), layer.layerDef.name]),
layer.isDisplayed)); layer.isDisplayed));

View file

@ -34,16 +34,12 @@ export class MoreScreen extends UIElement {
const link = const link =
new SubtleButton(layout.icon, new SubtleButton(layout.icon,
new Combine([ new Combine([
`<a href="${linkText}" target="_blank">`,
"<div>",
"<b>", "<b>",
Translations.W(layout.title), Translations.W(layout.title),
"</b>", "</b>",
"<br/>", "<br/>",
Translations.W(layout.description), Translations.W(layout.description),
"</div>", ]), linkText);
"</a>"
]));
els.push(link) els.push(link)
} }

View file

@ -35,6 +35,8 @@ export class SimpleAddUI extends UIElement {
= new UIEventSource<Preset>(undefined); = new UIEventSource<Preset>(undefined);
private confirmButton: UIElement = undefined; private confirmButton: UIElement = undefined;
private cancelButton: UIElement; 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 }>, constructor(zoomlevel: UIEventSource<{ zoom: number }>,
lastClickLocation: UIEventSource<{ lat: number, lon: number }>, lastClickLocation: UIEventSource<{ lat: number, lon: number }>,
@ -132,7 +134,9 @@ export class SimpleAddUI extends UIElement {
if (this._userDetails.data.unreadMessages > 0) { if (this._userDetails.data.unreadMessages > 0) {
return new Combine([header, "<span class='alert'>", return new Combine([header, "<span class='alert'>",
Translations.t.general.readYourMessages, Translations.t.general.readYourMessages,
"</span>"]).Render(); "</span>",
this.goToInboxButton
]).Render();
} }
if (this._userDetails.data.dryRun) { if (this._userDetails.data.dryRun) {

View file

@ -748,7 +748,16 @@ export default class Translations {
websiteIs: new T({ websiteIs: new T({
en: "Website: <a href='{website}' target='_blank'>{website}</a>", en: "Website: <a href='{website}' target='_blank'>{website}</a>",
nl: "Website: <a href='{website}' target='_blank'>{website}</a>" nl: "Website: <a href='{website}' target='_blank'>{website}</a>"
}) }),
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 <a href='mailto:{email}' target='_blank'>{email}</a>",
nl: "Het email-adres van {category} is <a href='mailto:{email}' target='_blank'>{email}</a>"
}),
}, },
openStreetMapIntro: new T({ openStreetMapIntro: new T({
@ -794,6 +803,10 @@ export default class Translations {
fewChangesBefore: new T({ fewChangesBefore: new T({
en: "Please, answer a few questions of existing points before adding a new point.", 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." 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"
}) })
} }
} }

View file

@ -4,10 +4,6 @@ html, body {
padding: 0; padding: 0;
} }
a {
text-decoration: unset;
color:unset;
}
body { body {
font-family: 'Helvetica Neue', Arial, sans-serif; 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{ .subtle-button img{
width: 3em; width: 3em;
max-height: 3em; max-height: 3em;

View file

@ -105,11 +105,11 @@ const fullScreenMessage = new UIEventSource<UIElement>(undefined);
// The latest element that was selected - used to generate the right UI at the right place // The latest element that was selected - used to generate the right UI at the right place
const selectedElement = new UIEventSource<{ feature: any }>(undefined); const selectedElement = new UIEventSource<{ feature: any }>(undefined);
const zoom = QueryParameters.GetQueryParameter("z", "" + layoutToUse.startzoom) const zoom = QueryParameters.GetQueryParameter("z", undefined)
.syncWith(LocalStorageSource.Get("zoom")); .syncWith(LocalStorageSource.Get("zoom"));
const lat = QueryParameters.GetQueryParameter("lat", "" + layoutToUse.startLat) const lat = QueryParameters.GetQueryParameter("lat", undefined)
.syncWith(LocalStorageSource.Get("lat")); .syncWith(LocalStorageSource.Get("lat"));
const lon = QueryParameters.GetQueryParameter("lon", "" + layoutToUse.startLon) const lon = QueryParameters.GetQueryParameter("lon", undefined)
.syncWith(LocalStorageSource.Get("lon")); .syncWith(LocalStorageSource.Get("lon"));
const featureSwitchUserbadge = QueryParameters.GetQueryParameter("fs-userbadge", ""+layoutToUse.enableUserBadge); const featureSwitchUserbadge = QueryParameters.GetQueryParameter("fs-userbadge", ""+layoutToUse.enableUserBadge);