forked from MapComplete/MapComplete
		
	Add email question, some tweaks
This commit is contained in:
		
						commit
						f67508336a
					
				
					 12 changed files with 123 additions and 69 deletions
				
			
		|  | @ -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] | ||||
|                 } | ||||
|             }; | ||||
|         }; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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(), | ||||
|  |  | |||
							
								
								
									
										18
									
								
								Customizations/Questions/EmailQuestion.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Customizations/Questions/EmailQuestion.ts
									
										
									
									
									
										Normal 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" | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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("<regexOrStr> 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)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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([ | ||||
|                 `<a class="subtle-button" href="${this.linkTo}" target="_blank">`, | ||||
|                 this.imageUrl !== undefined ? `<img src='${this.imageUrl}'>` : "", | ||||
|                 this.message, | ||||
|                 '</a>' | ||||
|             ]).Render(); | ||||
|         } | ||||
|          | ||||
|         return new Combine([ | ||||
|             '<span class="subtle-button">', | ||||
|             this.imageUrl !== undefined ? `<img src='${this.imageUrl}'>` : "", | ||||
|  |  | |||
|  | @ -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)); | ||||
|  |  | |||
|  | @ -34,16 +34,12 @@ export class MoreScreen extends UIElement { | |||
|             const link = | ||||
|                 new SubtleButton(layout.icon, | ||||
|                     new Combine([ | ||||
|                         `<a href="${linkText}" target="_blank">`, | ||||
|                         "<div>", | ||||
|                         "<b>", | ||||
|                         Translations.W(layout.title), | ||||
|                         "</b>", | ||||
|                         "<br/>", | ||||
|                         Translations.W(layout.description), | ||||
|                         "</div>", | ||||
|                         "</a>" | ||||
|                     ])); | ||||
|                     ]), linkText); | ||||
| 
 | ||||
|             els.push(link) | ||||
|         } | ||||
|  |  | |||
|  | @ -35,6 +35,8 @@ export class SimpleAddUI extends UIElement { | |||
|         = new UIEventSource<Preset>(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, "<span class='alert'>", | ||||
|                 Translations.t.general.readYourMessages, | ||||
|                 "</span>"]).Render(); | ||||
|                 "</span>", | ||||
|                 this.goToInboxButton | ||||
|             ]).Render(); | ||||
|         } | ||||
| 
 | ||||
|         if (this._userDetails.data.dryRun) { | ||||
|  |  | |||
|  | @ -748,7 +748,16 @@ export default class Translations { | |||
|                 websiteIs: new T({ | ||||
|                     en: "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({ | ||||
|  | @ -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" | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										11
									
								
								index.css
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								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; | ||||
|  |  | |||
							
								
								
									
										6
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								index.ts
									
										
									
									
									
								
							|  | @ -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
 | ||||
| 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); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue