| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  | import {DropDown} from "./DropDown"; | 
					
						
							|  |  |  | import * as EmailValidator from "email-validator"; | 
					
						
							|  |  |  | import {parsePhoneNumberFromString} from "libphonenumber-js"; | 
					
						
							|  |  |  | import InputElementMap from "./InputElementMap"; | 
					
						
							|  |  |  | import {InputElement} from "./InputElement"; | 
					
						
							|  |  |  | import {TextField} from "./TextField"; | 
					
						
							|  |  |  | import {UIElement} from "../UIElement"; | 
					
						
							|  |  |  | import {UIEventSource} from "../../Logic/UIEventSource"; | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  | import CombinedInputElement from "./CombinedInputElement"; | 
					
						
							|  |  |  | import SimpleDatePicker from "./SimpleDatePicker"; | 
					
						
							| 
									
										
										
										
											2020-10-08 19:03:00 +02:00
										 |  |  | import OpeningHoursInput from "./OpeningHours/OpeningHoursInput"; | 
					
						
							| 
									
										
										
										
											2020-11-15 03:10:44 +01:00
										 |  |  | import DirectionInput from "./DirectionInput"; | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | interface TextFieldDef { | 
					
						
							|  |  |  |     name: string, | 
					
						
							|  |  |  |     explanation: string, | 
					
						
							|  |  |  |     isValid: ((s: string, country?: string) => boolean), | 
					
						
							|  |  |  |     reformat?: ((s: string, country?: string) => string), | 
					
						
							| 
									
										
										
										
											2020-10-04 12:55:44 +02:00
										 |  |  |     inputHelper?: (value: UIEventSource<string>) => InputElement<string>, | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | export default class ValidatedTextField { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static tp(name: string, | 
					
						
							|  |  |  |                       explanation: string, | 
					
						
							|  |  |  |                       isValid?: ((s: string, country?: string) => boolean), | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |                       reformat?: ((s: string, country?: string) => string), | 
					
						
							|  |  |  |                       inputHelper?: (value: UIEventSource<string>) => InputElement<string>): TextFieldDef { | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (isValid === undefined) { | 
					
						
							|  |  |  |             isValid = () => true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (reformat === undefined) { | 
					
						
							|  |  |  |             reformat = (str, _) => str; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             name: name, | 
					
						
							|  |  |  |             explanation: explanation, | 
					
						
							|  |  |  |             isValid: isValid, | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |             reformat: reformat, | 
					
						
							|  |  |  |             inputHelper: inputHelper | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |     public static tpList: TextFieldDef[] = [ | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "string", | 
					
						
							|  |  |  |             "A basic string"), | 
					
						
							| 
									
										
										
										
											2020-10-27 01:01:34 +01:00
										 |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "text", | 
					
						
							|  |  |  |             "A string, but allows input of longer strings more comfortably (a text area)"), | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "date", | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |             "A date", | 
					
						
							|  |  |  |             (str) => { | 
					
						
							|  |  |  |                 const time = Date.parse(str); | 
					
						
							|  |  |  |                 return !isNaN(time); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             (str) => { | 
					
						
							|  |  |  |                 const d = new Date(str); | 
					
						
							|  |  |  |                 let month = '' + (d.getMonth() + 1); | 
					
						
							|  |  |  |                 let day = '' + d.getDate(); | 
					
						
							|  |  |  |                 const year = d.getFullYear(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (month.length < 2) | 
					
						
							|  |  |  |                     month = '0' + month; | 
					
						
							|  |  |  |                 if (day.length < 2) | 
					
						
							|  |  |  |                     day = '0' + day; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return [year, month, day].join('-'); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             (value) => new SimpleDatePicker(value)), | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "wikidata", | 
					
						
							|  |  |  |             "A wikidata identifier, e.g. Q42"), | 
					
						
							|  |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "int", | 
					
						
							|  |  |  |             "A number", | 
					
						
							|  |  |  |             (str) => { | 
					
						
							|  |  |  |                 str = "" + str; | 
					
						
							|  |  |  |                 return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) | 
					
						
							|  |  |  |             }), | 
					
						
							|  |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "nat", | 
					
						
							|  |  |  |             "A positive number or zero", | 
					
						
							|  |  |  |             (str) => { | 
					
						
							|  |  |  |                 str = "" + str; | 
					
						
							|  |  |  |                 return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 | 
					
						
							|  |  |  |             }), | 
					
						
							|  |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "pnat", | 
					
						
							|  |  |  |             "A strict positive number", | 
					
						
							|  |  |  |             (str) => { | 
					
						
							|  |  |  |                 str = "" + str; | 
					
						
							|  |  |  |                 return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) > 0 | 
					
						
							|  |  |  |             }), | 
					
						
							| 
									
										
										
										
											2020-11-15 03:10:44 +01:00
										 |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "direction", | 
					
						
							|  |  |  |             "A geographical direction, in degrees. 0° is north, 90° is east", | 
					
						
							|  |  |  |             (str) => { | 
					
						
							|  |  |  |                 str = "" + str; | 
					
						
							|  |  |  |                 return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) > 0 && Number(str) <= 360 | 
					
						
							|  |  |  |             },str => str, | 
					
						
							|  |  |  |             (value) => { | 
					
						
							|  |  |  |               return new DirectionInput(value); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         ), | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "float", | 
					
						
							|  |  |  |             "A decimal", | 
					
						
							|  |  |  |             (str) => !isNaN(Number(str))), | 
					
						
							|  |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "pfloat", | 
					
						
							|  |  |  |             "A positive decimal (incl zero)", | 
					
						
							|  |  |  |             (str) => !isNaN(Number(str)) && Number(str) >= 0), | 
					
						
							|  |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "email", | 
					
						
							|  |  |  |             "An email adress", | 
					
						
							|  |  |  |             (str) => EmailValidator.validate(str)), | 
					
						
							|  |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "url", | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |             "A url", | 
					
						
							|  |  |  |             (str) => { | 
					
						
							|  |  |  |                 try { | 
					
						
							|  |  |  |                     new URL(str); | 
					
						
							|  |  |  |                     return true; | 
					
						
							|  |  |  |                 } catch (e) { | 
					
						
							|  |  |  |                     return false; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }, (str) => { | 
					
						
							|  |  |  |                 try { | 
					
						
							|  |  |  |                     const url = new URL(str); | 
					
						
							|  |  |  |                     const blacklistedTrackingParams = [ | 
					
						
							|  |  |  |                         "fbclid",// Oh god, how I hate the fbclid. Let it burn, burn in hell!
 | 
					
						
							|  |  |  |                         "gclid", | 
					
						
							| 
									
										
										
										
											2020-09-27 18:45:37 +02:00
										 |  |  |                         "cmpid", "agid", "utm", "utm_source","utm_medium"] | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |                     for (const dontLike of blacklistedTrackingParams) { | 
					
						
							|  |  |  |                         url.searchParams.delete(dontLike) | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     return url.toString(); | 
					
						
							|  |  |  |                 } catch (e) { | 
					
						
							|  |  |  |                     console.error(e) | 
					
						
							|  |  |  |                     return undefined; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }), | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "phone", | 
					
						
							|  |  |  |             "A phone number", | 
					
						
							|  |  |  |             (str, country: any) => { | 
					
						
							| 
									
										
										
										
											2020-09-26 01:43:20 +02:00
										 |  |  |                 if (str === undefined) { | 
					
						
							|  |  |  |                     return false; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |                 return parsePhoneNumberFromString(str, country?.toUpperCase())?.isValid() ?? false | 
					
						
							|  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2020-09-26 21:00:03 +02:00
										 |  |  |             (str, country: any) => parsePhoneNumberFromString(str, country?.toUpperCase()).formatInternational() | 
					
						
							| 
									
										
										
										
											2020-10-04 12:55:44 +02:00
										 |  |  |         ), | 
					
						
							|  |  |  |         ValidatedTextField.tp( | 
					
						
							|  |  |  |             "opening_hours", | 
					
						
							|  |  |  |             "Has extra elements to easily input when a POI is opened", | 
					
						
							|  |  |  |             (s, country) => true, // TODO
 | 
					
						
							| 
									
										
										
										
											2020-10-06 01:37:02 +02:00
										 |  |  |             str => str,  | 
					
						
							| 
									
										
										
										
											2020-10-04 12:55:44 +02:00
										 |  |  |             (value) => { | 
					
						
							| 
									
										
										
										
											2020-10-08 19:03:00 +02:00
										 |  |  |                 return new OpeningHoursInput(value); | 
					
						
							| 
									
										
										
										
											2020-10-04 12:55:44 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     private static allTypesDict(){ | 
					
						
							|  |  |  |         const types = {}; | 
					
						
							|  |  |  |         for (const tp of ValidatedTextField.tpList) { | 
					
						
							|  |  |  |             types[tp.name] = tp; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return types; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static TypeDropdown(): DropDown<string> { | 
					
						
							|  |  |  |         const values: { value: string, shown: string }[] = []; | 
					
						
							|  |  |  |         const expl = ValidatedTextField.tpList; | 
					
						
							|  |  |  |         for (const key in expl) { | 
					
						
							| 
									
										
										
										
											2020-10-10 13:44:10 +02:00
										 |  |  |             values.push({value: expl[key].name, shown: `${expl[key].name} - ${expl[key].explanation}`}) | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         return new DropDown<string>("", values) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-27 01:01:34 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * {string (typename) --> TextFieldDef} | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |     public static AllTypes = ValidatedTextField.allTypesDict(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |     public static InputForType(type: string, options?: { | 
					
						
							|  |  |  |         placeholder?: string | UIElement, | 
					
						
							|  |  |  |         value?: UIEventSource<string>, | 
					
						
							|  |  |  |         textArea?: boolean, | 
					
						
							|  |  |  |         textAreaRows?: number, | 
					
						
							| 
									
										
										
										
											2020-09-26 21:00:03 +02:00
										 |  |  |         isValid?: ((s: string, country: string) => boolean), | 
					
						
							|  |  |  |         country?: string | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |     }): InputElement<string> { | 
					
						
							|  |  |  |         options = options ?? {}; | 
					
						
							|  |  |  |         options.placeholder = options.placeholder ?? type; | 
					
						
							|  |  |  |         const tp: TextFieldDef = ValidatedTextField.AllTypes[type] | 
					
						
							| 
									
										
										
										
											2020-09-26 21:00:03 +02:00
										 |  |  |         const isValidTp = tp.isValid; | 
					
						
							|  |  |  |         let isValid; | 
					
						
							| 
									
										
										
										
											2020-10-27 01:01:34 +01:00
										 |  |  |         options.textArea = options.textArea ?? type === "text"; | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |         if (options.isValid) { | 
					
						
							|  |  |  |             const optValid = options.isValid; | 
					
						
							|  |  |  |             isValid = (str, country) => { | 
					
						
							| 
									
										
										
										
											2020-09-26 21:00:03 +02:00
										 |  |  |                 if(str === undefined){ | 
					
						
							|  |  |  |                     return false; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return isValidTp(str, country ?? options.country) && optValid(str, country ?? options.country); | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-09-26 21:00:03 +02:00
										 |  |  |         }else{ | 
					
						
							|  |  |  |             isValid = isValidTp; | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         options.isValid = isValid; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let input: InputElement<string> = new TextField(options); | 
					
						
							| 
									
										
										
										
											2020-09-26 21:00:03 +02:00
										 |  |  |         if (tp.reformat) { | 
					
						
							|  |  |  |             input.GetValue().addCallbackAndRun(str => { | 
					
						
							|  |  |  |                 if (!options.isValid(str, options.country)) { | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const formatted = tp.reformat(str, options.country); | 
					
						
							|  |  |  |                 input.GetValue().setData(formatted); | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |         if (tp.inputHelper) { | 
					
						
							|  |  |  |             input = new CombinedInputElement(input, tp.inputHelper(input.GetValue())); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return input; | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static NumberInput(type: string = "int", extraValidation: (number: Number) => boolean = undefined): InputElement<number> { | 
					
						
							|  |  |  |         const isValid = ValidatedTextField.AllTypes[type].isValid; | 
					
						
							|  |  |  |         extraValidation = extraValidation ?? (() => true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const fromString = str => { | 
					
						
							|  |  |  |             if (!isValid(str)) { | 
					
						
							|  |  |  |                 return undefined; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const n = Number(str); | 
					
						
							|  |  |  |             if (!extraValidation(n)) { | 
					
						
							|  |  |  |                 return undefined; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return n; | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         const toString = num => { | 
					
						
							|  |  |  |             if (num === undefined) { | 
					
						
							|  |  |  |                 return undefined; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return "" + num; | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         const textField = ValidatedTextField.InputForType(type); | 
					
						
							|  |  |  |         return new InputElementMap(textField, (n0, n1) => n0 === n1, fromString, toString) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     public static KeyInput(allowEmpty: boolean = false): InputElement<string> { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function fromString(str) { | 
					
						
							|  |  |  |             if (str?.match(/^[a-zA-Z][a-zA-Z0-9:_-]*$/)) { | 
					
						
							|  |  |  |                 return str; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (str === "" && allowEmpty) { | 
					
						
							|  |  |  |                 return ""; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return undefined | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const toString = str => str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function isSame(str0, str1) { | 
					
						
							|  |  |  |             return str0 === str1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const textfield = new TextField({ | 
					
						
							|  |  |  |             placeholder: "key", | 
					
						
							|  |  |  |             isValid: str => fromString(str) !== undefined, | 
					
						
							|  |  |  |             value: new UIEventSource<string>("") | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return new InputElementMap(textfield, isSame, fromString, toString); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static Mapped<T>(fromString: (str) => T, toString: (T) => string, options?: { | 
					
						
							|  |  |  |         placeholder?: string | UIElement, | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |         type?: string, | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |         value?: UIEventSource<string>, | 
					
						
							|  |  |  |         startValidated?: boolean, | 
					
						
							|  |  |  |         textArea?: boolean, | 
					
						
							|  |  |  |         textAreaRows?: number, | 
					
						
							| 
									
										
										
										
											2020-09-26 21:00:03 +02:00
										 |  |  |         isValid?: ((string: string) => boolean), | 
					
						
							|  |  |  |         country?: string | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |     }): InputElement<T> { | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |         let textField: InputElement<string>; | 
					
						
							| 
									
										
										
										
											2020-09-27 20:51:37 +02:00
										 |  |  |         if (options?.type) { | 
					
						
							| 
									
										
										
										
											2020-09-26 21:00:03 +02:00
										 |  |  |             textField = ValidatedTextField.InputForType(options.type, options); | 
					
						
							| 
									
										
										
										
											2020-09-26 03:02:19 +02:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             textField = new TextField(options); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-09-25 12:44:04 +02:00
										 |  |  |         return new InputElementMap( | 
					
						
							|  |  |  |             textField, (a, b) => a === b, | 
					
						
							|  |  |  |             fromString, toString | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |