| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  | import {UIEventSource} from "../../Logic/UIEventSource"; | 
					
						
							|  |  |  | import Combine from "../Base/Combine"; | 
					
						
							|  |  |  | import {FixedUiElement} from "../Base/FixedUiElement"; | 
					
						
							|  |  |  | import {OH} from "./OpeningHours"; | 
					
						
							|  |  |  | import Translations from "../i18n/Translations"; | 
					
						
							|  |  |  | import Constants from "../../Models/Constants"; | 
					
						
							|  |  |  | import BaseUIElement from "../BaseUIElement"; | 
					
						
							|  |  |  | import Toggle from "../Input/Toggle"; | 
					
						
							|  |  |  | import {VariableUiElement} from "../Base/VariableUIElement"; | 
					
						
							|  |  |  | import Table from "../Base/Table"; | 
					
						
							|  |  |  | import {Translation} from "../i18n/Translation"; | 
					
						
							| 
									
										
										
										
											2021-12-21 18:35:31 +01:00
										 |  |  | import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-28 00:45:49 +02:00
										 |  |  | export default class OpeningHoursVisualization extends Toggle { | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |     private static readonly weekdays: Translation[] = [ | 
					
						
							|  |  |  |         Translations.t.general.weekdays.abbreviations.monday, | 
					
						
							|  |  |  |         Translations.t.general.weekdays.abbreviations.tuesday, | 
					
						
							|  |  |  |         Translations.t.general.weekdays.abbreviations.wednesday, | 
					
						
							|  |  |  |         Translations.t.general.weekdays.abbreviations.thursday, | 
					
						
							|  |  |  |         Translations.t.general.weekdays.abbreviations.friday, | 
					
						
							|  |  |  |         Translations.t.general.weekdays.abbreviations.saturday, | 
					
						
							|  |  |  |         Translations.t.general.weekdays.abbreviations.sunday, | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  |     constructor(tags: UIEventSource<any>, state: { osmConnection?: OsmConnection }, key: string, prefix = "", postfix = "") { | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |         const ohTable = new VariableUiElement(tags | 
					
						
							| 
									
										
										
										
											2021-10-29 03:42:33 +02:00
										 |  |  |             .map(tags => { | 
					
						
							| 
									
										
										
										
											2021-11-02 17:47:21 +01:00
										 |  |  |                 const value: string = tags[key]; | 
					
						
							|  |  |  |                 if (value === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (value.startsWith(prefix) && value.endsWith(postfix)) { | 
					
						
							|  |  |  |                     return value.substring(prefix.length, value.length - postfix.length).trim() | 
					
						
							| 
									
										
										
										
											2021-10-29 03:42:33 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 return value; | 
					
						
							|  |  |  |             }) // This mapping will absorb all other changes to tags in order to prevent regeneration
 | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |             .map(ohtext => { | 
					
						
							| 
									
										
										
										
											2021-11-02 17:47:21 +01:00
										 |  |  |                     if (ohtext === undefined) { | 
					
						
							|  |  |  |                         return new FixedUiElement("No opening hours defined with key " + key).SetClass("alert") | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |                     try { | 
					
						
							| 
									
										
										
										
											2022-04-28 00:32:15 +02:00
										 |  |  |                         return OpeningHoursVisualization.CreateFullVisualisation( | 
					
						
							|  |  |  |                             OH.CreateOhObject(tags.data, ohtext)) | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |                     } catch (e) { | 
					
						
							| 
									
										
										
										
											2021-11-02 17:47:21 +01:00
										 |  |  |                         console.warn(e, e.stack); | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |                         return new Combine([Translations.t.general.opening_hours.error_loading, | 
					
						
							|  |  |  |                             new Toggle( | 
					
						
							|  |  |  |                                 new FixedUiElement(e).SetClass("subtle"), | 
					
						
							|  |  |  |                                 undefined, | 
					
						
							| 
									
										
										
										
											2021-12-21 18:35:31 +01:00
										 |  |  |                                 state?.osmConnection?.userDetails.map(userdetails => userdetails.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked) | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |                             ) | 
					
						
							|  |  |  |                         ]); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             )) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |         super( | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |             ohTable, | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:43 +02:00
										 |  |  |             Translations.t.general.opening_hours.loadingCountry.Clone(), | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |             tags.map(tgs => tgs._country !== undefined) | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static CreateFullVisualisation(oh: any): BaseUIElement { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /** First, we determine which range of dates we want to visualize: this week or next week?**/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const today = new Date(); | 
					
						
							|  |  |  |         today.setHours(0, 0, 0, 0); | 
					
						
							| 
									
										
										
										
											2022-04-28 00:32:15 +02:00
										 |  |  |         const lastMonday = OH.getMondayBefore(today); | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |         const nextSunday = new Date(lastMonday); | 
					
						
							|  |  |  |         nextSunday.setDate(nextSunday.getDate() + 7); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!oh.getState() && !oh.getUnknown()) { | 
					
						
							|  |  |  |             // POI is currently closed
 | 
					
						
							|  |  |  |             const nextChange: Date = oh.getNextChange(); | 
					
						
							|  |  |  |             if ( | 
					
						
							|  |  |  |                 // Shop isn't gonna open anymore in this timerange
 | 
					
						
							|  |  |  |                 nextSunday < nextChange | 
					
						
							|  |  |  |                 // And we are already in the weekend to show next week
 | 
					
						
							|  |  |  |                 && (today.getDay() == 0 || today.getDay() == 6) | 
					
						
							|  |  |  |             ) { | 
					
						
							|  |  |  |                 // We move the range to next week!
 | 
					
						
							|  |  |  |                 lastMonday.setDate(lastMonday.getDate() + 7); | 
					
						
							|  |  |  |                 nextSunday.setDate(nextSunday.getDate() + 7); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* We calculate the ranges when it is opened! */ | 
					
						
							|  |  |  |         const ranges = OH.GetRanges(oh, lastMonday, nextSunday); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* First, a small sanity check. The business might be permanently closed, 24/7 opened or something other special | 
					
						
							|  |  |  |         * So, we have to handle the case that ranges is completely empty*/ | 
					
						
							|  |  |  |         if (ranges.filter(range => range.length > 0).length === 0) { | 
					
						
							|  |  |  |             return OpeningHoursVisualization.ShowSpecialCase(oh).SetClass("p-4 rounded-full block bg-gray-200") | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /** With all the edge cases handled, we can actually construct the table! **/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return OpeningHoursVisualization.ConstructVizTable(oh, ranges, lastMonday) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static ConstructVizTable(oh: any, ranges: { isOpen: boolean; isSpecial: boolean; comment: string; startDate: Date; endDate: Date }[][], | 
					
						
							|  |  |  |                                      rangeStart: Date): BaseUIElement { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const isWeekstable: boolean = oh.isWeekStable(); | 
					
						
							|  |  |  |         let [changeHours, changeHourText] = OH.allChangeMoments(ranges); | 
					
						
							|  |  |  |         const today = new Date(); | 
					
						
							|  |  |  |         today.setHours(0, 0, 0, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // @ts-ignore
 | 
					
						
							|  |  |  |         const todayIndex = Math.ceil((today - rangeStart) / (1000 * 60 * 60 * 24)) | 
					
						
							|  |  |  |         // By default, we always show the range between 8 - 19h, in order to give a stable impression
 | 
					
						
							|  |  |  |         // Ofc, a bigger range is used if needed
 | 
					
						
							|  |  |  |         const earliestOpen = Math.min(8 * 60 * 60, ...changeHours); | 
					
						
							|  |  |  |         let latestclose = Math.max(...changeHours); | 
					
						
							|  |  |  |         // We always make sure there is 30m of leeway in order to give enough room for the closing entry
 | 
					
						
							|  |  |  |         latestclose = Math.max(19 * 60 * 60, latestclose + 30 * 60) | 
					
						
							|  |  |  |         const availableArea = latestclose - earliestOpen; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* | 
					
						
							|  |  |  |         * The OH-visualisation is a table, consisting of 8 rows and 2 columns: | 
					
						
							|  |  |  |         * The first row is a header row (which is NOT passed as header but just as a normal row!) containing empty for the first column and one object giving all the end times | 
					
						
							|  |  |  |         * The other rows are one for each weekday: the first element showing 'mo', 'tu', ..., the second element containing the bars. | 
					
						
							|  |  |  |         * Note that the bars are actually an embedded <div> spanning the full width, containing multiple sub-elements | 
					
						
							|  |  |  |         * */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const [header, headerHeight] = OpeningHoursVisualization.ConstructHeaderElement(availableArea, changeHours, changeHourText, earliestOpen) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const weekdays = [] | 
					
						
							|  |  |  |         const weekdayStyles = [] | 
					
						
							|  |  |  |         for (let i = 0; i < 7; i++) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const day = OpeningHoursVisualization.weekdays[i].Clone(); | 
					
						
							|  |  |  |             day.SetClass("w-full h-full block") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const rangesForDay = ranges[i].map(range => | 
					
						
							|  |  |  |                 OpeningHoursVisualization.CreateRangeElem(availableArea, earliestOpen, latestclose, range, isWeekstable) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             const allRanges = new Combine([ | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |                 ...OpeningHoursVisualization.CreateLinesAtChangeHours(changeHours, availableArea, earliestOpen), | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |                 ...rangesForDay]).SetClass("w-full block"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let extraStyle = "" | 
					
						
							|  |  |  |             if (todayIndex == i) { | 
					
						
							|  |  |  |                 extraStyle = "background-color: var(--subtle-detail-color);" | 
					
						
							|  |  |  |                 allRanges.SetClass("ohviz-today") | 
					
						
							|  |  |  |             } else if (i >= 5) { | 
					
						
							|  |  |  |                 extraStyle = "background-color: rgba(230, 231, 235, 1);" | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             weekdays.push([day, allRanges]) | 
					
						
							|  |  |  |             weekdayStyles.push(["padding-left: 0.5em;" + extraStyle, `position: relative;` + extraStyle]) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return new Table(undefined, | 
					
						
							|  |  |  |             [[" ", header], ...weekdays], | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  |             {contentStyle: [["width: 5%", `position: relative; height: ${headerHeight}`], ...weekdayStyles]} | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |         ).SetClass("w-full") | 
					
						
							|  |  |  |             .SetStyle("border-collapse: collapse; word-break; word-break: normal; word-wrap: normal") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static CreateRangeElem(availableArea: number, earliestOpen: number, latestclose: number, | 
					
						
							|  |  |  |                                    range: { isOpen: boolean; isSpecial: boolean; comment: string; startDate: Date; endDate: Date }, | 
					
						
							|  |  |  |                                    isWeekstable: boolean): BaseUIElement { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const textToShow = range.comment ?? (isWeekstable ? "" : range.startDate.toLocaleDateString()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!range.isOpen && !range.isSpecial) { | 
					
						
							|  |  |  |             return new FixedUiElement(textToShow).SetClass("ohviz-day-off") | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const startOfDay: Date = new Date(range.startDate); | 
					
						
							|  |  |  |         startOfDay.setHours(0, 0, 0, 0); | 
					
						
							|  |  |  |         // @ts-ignore
 | 
					
						
							|  |  |  |         const startpoint = (range.startDate - startOfDay) / 1000 - earliestOpen; | 
					
						
							|  |  |  |         // @ts-ignore
 | 
					
						
							|  |  |  |         const width = (100 * (range.endDate - range.startDate) / 1000) / (latestclose - earliestOpen); | 
					
						
							|  |  |  |         const startPercentage = (100 * startpoint / availableArea); | 
					
						
							|  |  |  |         return new FixedUiElement(textToShow).SetStyle(`left:${startPercentage}%; width:${width}%`) | 
					
						
							|  |  |  |             .SetClass("ohviz-range"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static CreateLinesAtChangeHours(changeHours: number[], availableArea: number, earliestOpen: number): | 
					
						
							|  |  |  |         BaseUIElement[] { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const allLines: BaseUIElement[] = [] | 
					
						
							|  |  |  |         for (const changeMoment of changeHours) { | 
					
						
							|  |  |  |             const offset = 100 * (changeMoment - earliestOpen) / availableArea; | 
					
						
							|  |  |  |             if (offset < 0 || offset > 100) { | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const el = new FixedUiElement("").SetStyle(`left:${offset}%;`).SetClass("ohviz-line"); | 
					
						
							|  |  |  |             allLines.push(el); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return allLines; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The OH-Visualization header element, a single bar with hours | 
					
						
							|  |  |  |      * @param availableArea | 
					
						
							|  |  |  |      * @param changeHours | 
					
						
							|  |  |  |      * @param changeHourText | 
					
						
							|  |  |  |      * @param earliestOpen | 
					
						
							|  |  |  |      * @constructor | 
					
						
							|  |  |  |      * @private | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private static ConstructHeaderElement(availableArea: number, changeHours: number[], changeHourText: string[], earliestOpen: number) | 
					
						
							|  |  |  |         : [BaseUIElement, string] { | 
					
						
							|  |  |  |         let header: BaseUIElement[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         header.push(...OpeningHoursVisualization.CreateLinesAtChangeHours(changeHours, availableArea, earliestOpen)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let showHigher = false; | 
					
						
							|  |  |  |         let showHigherUsed = false; | 
					
						
							|  |  |  |         for (let i = 0; i < changeHours.length; i++) { | 
					
						
							|  |  |  |             let changeMoment = changeHours[i]; | 
					
						
							|  |  |  |             const offset = 100 * (changeMoment - earliestOpen) / availableArea; | 
					
						
							|  |  |  |             if (offset < 0 || offset > 100) { | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |             if (i > 0 && ((changeMoment - changeHours[i - 1]) / (60 * 60)) < 2) { | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  |                 // Quite close to the previous value
 | 
					
						
							|  |  |  |                 // We alternate the heights
 | 
					
						
							|  |  |  |                 showHigherUsed = true; | 
					
						
							|  |  |  |                 showHigher = !showHigher; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 showHigher = false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const el = new Combine([ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 new FixedUiElement(changeHourText[i]) | 
					
						
							|  |  |  |                     .SetClass("relative bg-white pl-1 pr-1 h-3 font-sm rounded-xl border-2 border-black  border-opacity-50") | 
					
						
							|  |  |  |                     .SetStyle("left: -50%; word-break:initial") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             ]) | 
					
						
							|  |  |  |                 .SetStyle(`left:${offset}%;margin-top: ${showHigher ? '1.4rem;' : "0.1rem"}`) | 
					
						
							|  |  |  |                 .SetClass("block absolute top-0 m-0 h-full box-border ohviz-time-indication"); | 
					
						
							|  |  |  |             header.push(el); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const headerElem = new Combine(header).SetClass(`w-full absolute block ${showHigherUsed ? "h-16" : "h-8"}`) | 
					
						
							|  |  |  |             .SetStyle("margin-top: -1rem") | 
					
						
							|  |  |  |         const headerHeight = showHigherUsed ? "4rem" : "2rem"; | 
					
						
							|  |  |  |         return [headerElem, headerHeight] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /* | 
					
						
							|  |  |  |     * Visualizes any special case: e.g. not open for a long time, 24/7 open, ... | 
					
						
							|  |  |  |     * */ | 
					
						
							|  |  |  |     private static ShowSpecialCase(oh: any) { | 
					
						
							|  |  |  |         const opensAtDate = oh.getNextChange(); | 
					
						
							|  |  |  |         if (opensAtDate === undefined) { | 
					
						
							|  |  |  |             const comm = oh.getComment() ?? oh.getUnknown(); | 
					
						
							|  |  |  |             if (!!comm) { | 
					
						
							|  |  |  |                 return new FixedUiElement(comm) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (oh.getState()) { | 
					
						
							|  |  |  |                 return Translations.t.general.opening_hours.open_24_7.Clone() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return Translations.t.general.opening_hours.closed_permanently.Clone() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const willOpenAt = `${opensAtDate.getDate()}/${opensAtDate.getMonth() + 1} ${OH.hhmm(opensAtDate.getHours(), opensAtDate.getMinutes())}` | 
					
						
							|  |  |  |         return Translations.t.general.opening_hours.closed_until.Subs({date: willOpenAt}) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 00:32:15 +02:00
										 |  |  |     | 
					
						
							| 
									
										
										
										
											2021-06-16 14:23:53 +02:00
										 |  |  | } |