forked from MapComplete/MapComplete
		
	First iteration of the timepicker
This commit is contained in:
		
							parent
							
								
									d1f286f466
								
							
						
					
					
						commit
						2a704a2b1d
					
				
					 7 changed files with 110 additions and 13 deletions
				
			
		|  | @ -1,3 +1,5 @@ | |||
| import {Utils} from "../Utils"; | ||||
| 
 | ||||
| export interface OpeningHour { | ||||
|     weekday: number, // 0 is monday, 1 is tuesday, ...
 | ||||
|     startHour: number, | ||||
|  | @ -7,6 +9,35 @@ export interface OpeningHour { | |||
| } | ||||
| 
 | ||||
| export class OpeningHourUtils { | ||||
| 
 | ||||
| 
 | ||||
|     private static readonly days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"] | ||||
|     private static readonly daysIndexed = { | ||||
|         mo: 0, | ||||
|         tu: 1, | ||||
|         we: 2, | ||||
|         th: 3, | ||||
|         fr: 4, | ||||
|         sa: 5, | ||||
|         su: 6 | ||||
|     } | ||||
| 
 | ||||
|     public static ToString(ohs: OpeningHour[]) { | ||||
|         const parts = []; | ||||
| 
 | ||||
|         function hhmm(h, m) { | ||||
|             return Utils.TwoDigits(h) + ":" + Utils.TwoDigits(m); | ||||
|         } | ||||
| 
 | ||||
|         for (const oh of ohs) { | ||||
|             parts.push( | ||||
|                 OpeningHourUtils.days[oh.weekday] + " " + hhmm(oh.startHour, oh.startMinutes) + "-" + hhmm(oh.endHour, oh.endMinutes) | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         return parts.join("; ")+";" | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Merge duplicate opening-hour element in place. | ||||
|      * Returns true if something changed | ||||
|  | @ -18,12 +49,12 @@ export class OpeningHourUtils { | |||
|         const newList = []; | ||||
|         while (queue.length > 0) { | ||||
|             let maybeAdd = queue.pop(); | ||||
|              | ||||
| 
 | ||||
|             let doAddEntry = true; | ||||
|             if(maybeAdd.weekday == undefined){ | ||||
|                 doAddEntry = false; | ||||
|             } | ||||
|              | ||||
| 
 | ||||
|             for (let i = newList.length - 1; i >= 0 && doAddEntry; i--) { | ||||
|                 let guard = newList[i]; | ||||
|                 if (maybeAdd.weekday != guard.weekday) { | ||||
|  | @ -60,7 +91,7 @@ export class OpeningHourUtils { | |||
|                         endHour = maybeAdd.endHour; | ||||
|                         endMinutes = maybeAdd.endMinutes; | ||||
|                     } | ||||
|                      | ||||
| 
 | ||||
|                     queue.push({ | ||||
|                         startHour: startHour, | ||||
|                         startMinutes: startMinutes, | ||||
|  | @ -107,5 +138,47 @@ export class OpeningHourUtils { | |||
|         return OpeningHourUtils.startTime(mightLieIn) <= OpeningHourUtils.endTime(checked) && | ||||
|             OpeningHourUtils.endTime(checked) <= OpeningHourUtils.endTime(mightLieIn) | ||||
|     } | ||||
| 
 | ||||
|     static Parse(str: string) { | ||||
|         if (str === undefined || str === "") { | ||||
|             return [] | ||||
|         } | ||||
| 
 | ||||
|         const parts = str.toLowerCase().split(";"); | ||||
|         const ohs = [] | ||||
| 
 | ||||
|         function parseTime(hhmm) { | ||||
|             const spl = hhmm.trim().split(":"); | ||||
|             return [Number(spl[0].trim()), Number(spl[1].trim())] | ||||
|         } | ||||
| 
 | ||||
|         for (const part of parts) { | ||||
|             if(part === ""){ | ||||
|                 continue; | ||||
|             } | ||||
|             try { | ||||
| 
 | ||||
|                 const partSplit = part.trim().split(" "); | ||||
|                 const weekday = OpeningHourUtils.daysIndexed[partSplit[0]] | ||||
|                 const timings = partSplit[1].split("-"); | ||||
|                 const start = parseTime(timings[0]) | ||||
|                 const end = parseTime(timings[1]); | ||||
| 
 | ||||
|                 const oh: OpeningHour = { | ||||
|                     weekday: weekday, | ||||
|                     startHour: start[0], | ||||
|                     startMinutes: start[1], | ||||
|                     endHour: end[0], | ||||
|                     endMinutes: end[1], | ||||
|                 } | ||||
|                 ohs.push(oh); | ||||
| 
 | ||||
|             } catch (e) { | ||||
|                 console.error("Could not parse opening hours part", part, ", skipping it due to ", e) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return ohs; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -23,7 +23,7 @@ export default class State { | |||
|     // The singleton of the global state
 | ||||
|     public static state: State; | ||||
|      | ||||
|     public static vNumber = "0.0.9b"; | ||||
|     public static vNumber = "0.1.0"; | ||||
|      | ||||
|     // The user journey states thresholds when a new feature gets unlocked
 | ||||
|     public static userJourney = { | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ export default class OpeningHoursPicker extends InputElement<OpeningHour[]> { | |||
| 
 | ||||
|     private readonly _weekdays: UIEventSource<UIElement[]> = new UIEventSource<UIElement[]>([]); | ||||
| 
 | ||||
|     constructor(ohs: UIEventSource<OpeningHour[]>) { | ||||
|     constructor(ohs: UIEventSource<OpeningHour[]> = new UIEventSource<OpeningHour[]>([])) { | ||||
|         super(); | ||||
|         this._ohs = ohs; | ||||
|         this._backgroundTable = new OpeningHoursPickerTable(this._weekdays); | ||||
|  |  | |||
|  | @ -48,8 +48,8 @@ export default class OpeningHoursPickerTable extends InputElement<OpeningHour> { | |||
|                 Utils.Times(id => `<td id="${this.id}-timecell-${id}-${h}-30" class="oh-timecell oh-timecell-half"><div class="oh-timecell-inner"></div></td>`, 7) + | ||||
|                 '</tr>'; | ||||
|         } | ||||
|         let days = OpeningHoursPickerTable.days.join("</th><th>"); | ||||
|         return `<table id="oh-table-${this.id}" class="oh-table"><tr><th></th><th>${days}</tr>${rows}</table>`; | ||||
|         let days = OpeningHoursPickerTable.days.join("</th><th width='14%'>"); | ||||
|         return `<table id="oh-table-${this.id}" class="oh-table"><tr><th></th><th width='14%'>${days}</th></tr>${rows}</table>`; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerUpdate() { | ||||
|  |  | |||
|  | @ -8,13 +8,15 @@ import {UIElement} from "../UIElement"; | |||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import CombinedInputElement from "./CombinedInputElement"; | ||||
| import SimpleDatePicker from "./SimpleDatePicker"; | ||||
| import OpeningHoursPicker from "./OpeningHours/OpeningHoursPicker"; | ||||
| import {OpeningHour, OpeningHourUtils} from "../../Logic/OpeningHours"; | ||||
| 
 | ||||
| interface TextFieldDef { | ||||
|     name: string, | ||||
|     explanation: string, | ||||
|     isValid: ((s: string, country?: string) => boolean), | ||||
|     reformat?: ((s: string, country?: string) => string), | ||||
|     inputHelper?: (value:UIEventSource<string>) => InputElement<string>, | ||||
|     inputHelper?: (value: UIEventSource<string>) => InputElement<string>, | ||||
| } | ||||
| 
 | ||||
| export default class ValidatedTextField { | ||||
|  | @ -141,6 +143,24 @@ export default class ValidatedTextField { | |||
|                 return parsePhoneNumberFromString(str, country?.toUpperCase())?.isValid() ?? false | ||||
|             }, | ||||
|             (str, country: any) => parsePhoneNumberFromString(str, country?.toUpperCase()).formatInternational() | ||||
|         ), | ||||
|         ValidatedTextField.tp( | ||||
|             "opening_hours", | ||||
|             "Has extra elements to easily input when a POI is opened", | ||||
|             (s, country) => true, // TODO
 | ||||
|             str => str, // TODO reformat with opening_hours.js
 | ||||
|             (value) => { | ||||
|                 const input = new InputElementMap<OpeningHour[], string>(new OpeningHoursPicker(), | ||||
|                     (a, b) => a === b, | ||||
|                     ohs => OpeningHourUtils.ToString(ohs), | ||||
|                     str => OpeningHourUtils.Parse(str) | ||||
|                 ) | ||||
|                 input.GetValue().addCallback(latest => { | ||||
|                     console.log(latest); | ||||
|                     value.setData(latest); | ||||
|                 }) | ||||
|                 return input; | ||||
|             } | ||||
|         ) | ||||
|     ] | ||||
|      | ||||
|  |  | |||
|  | @ -214,6 +214,14 @@ | |||
|         "type": "email" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "render": "Shop is open {opening_hours}", | ||||
|       "question": "When is this shop opened?", | ||||
|       "freeform": { | ||||
|         "key": "opening_hours", | ||||
|         "type": "opening_hours" | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "question": { | ||||
|         "en": "Does this shop sell bikes?", | ||||
|  |  | |||
|  | @ -92,10 +92,8 @@ | |||
| } | ||||
| 
 | ||||
| .oh-timerange-inner input { | ||||
|      width: calc(100% - 2em); | ||||
|      width: 100%; | ||||
|      box-sizing: border-box; | ||||
|      margin-left: 1em; | ||||
|      margin-right:1em; | ||||
|  } | ||||
| 
 | ||||
| .oh-timerange-inner-small { | ||||
|  | @ -109,8 +107,6 @@ | |||
| .oh-timerange-inner-small input { | ||||
|     width: min-content; | ||||
|     box-sizing: border-box; | ||||
|     margin-left: 1em; | ||||
|     margin-right:1em; | ||||
| } | ||||
| 
 | ||||
| .oh-delete-range{ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue