forked from MapComplete/MapComplete
		
	Fix opening hours input element
This commit is contained in:
		
							parent
							
								
									94f9a0de56
								
							
						
					
					
						commit
						64ec06bfc8
					
				
					 19 changed files with 643 additions and 599 deletions
				
			
		|  | @ -102,9 +102,13 @@ export default class SimpleMetaTagger { | |||
| 
 | ||||
|             SimpleMetaTagger.GetCountryCodeFor(lon, lat, (countries) => { | ||||
|                 try { | ||||
|                     const oldCountry = feature.properties["_country"]; | ||||
|                     feature.properties["_country"] = countries[0].trim().toLowerCase(); | ||||
|                     if (oldCountry !== feature.properties["_country"]) { | ||||
|                         const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id); | ||||
|                         tagsSource.ping(); | ||||
|                     } | ||||
| 
 | ||||
|                 } catch (e) { | ||||
|                     console.warn(e) | ||||
|                 } | ||||
|  |  | |||
|  | @ -19,6 +19,9 @@ export default class Combine extends BaseUIElement { | |||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         const el = document.createElement("span") | ||||
| 
 | ||||
|         try{ | ||||
|              | ||||
|       | ||||
|         for (const subEl of this.uiElements) { | ||||
|             if(subEl === undefined || subEl === null){ | ||||
|                 continue; | ||||
|  | @ -28,6 +31,11 @@ export default class Combine extends BaseUIElement { | |||
|                 el.appendChild(subHtml) | ||||
|             } | ||||
|         } | ||||
|         }catch(e){ | ||||
|             const domExc = e as DOMException | ||||
|             console.error("DOMException: ", domExc.name) | ||||
|             el.appendChild(new FixedUiElement("Could not generate this combine!").SetClass("alert").ConstructElement()) | ||||
|         } | ||||
|          | ||||
|         return el; | ||||
|     } | ||||
|  |  | |||
|  | @ -6,10 +6,14 @@ export default class Table extends BaseUIElement { | |||
| 
 | ||||
|     private readonly _header: BaseUIElement[]; | ||||
|     private readonly _contents: BaseUIElement[][]; | ||||
|     private readonly _contentStyle: string[][]; | ||||
| 
 | ||||
|     constructor(header: (BaseUIElement | string)[], contents: (BaseUIElement | string)[][]) { | ||||
|     constructor(header: (BaseUIElement | string)[],  | ||||
|                 contents: (BaseUIElement | string)[][], | ||||
|                 contentStyle?: string[][]) { | ||||
|         super(); | ||||
|         this._header = header.map(Translations.W); | ||||
|         this._contentStyle = contentStyle ?? []; | ||||
|         this._header = header?.map(Translations.W); | ||||
|         this._contents = contents.map(row => row.map(Translations.W)); | ||||
|     } | ||||
| 
 | ||||
|  | @ -28,15 +32,23 @@ export default class Table extends BaseUIElement { | |||
|             table.appendChild(tr) | ||||
|         } | ||||
| 
 | ||||
|         for (const row of this._contents) { | ||||
|         for (let i = 0; i < this._contents.length; i++){ | ||||
|             let row = this._contents[i]; | ||||
|             const tr = document.createElement("tr") | ||||
|             for (const elem of row) { | ||||
|                 const htmlElem = elem.ConstructElement() | ||||
|             for (let j = 0; j < row.length; j++){ | ||||
|                 let elem = row[j]; | ||||
|                 const htmlElem = elem?.ConstructElement() | ||||
|                 if (htmlElem === undefined) { | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 let style = undefined; | ||||
|                 if(this._contentStyle !== undefined && this._contentStyle[i] !== undefined && this._contentStyle[j]!== undefined){ | ||||
|                     style = this._contentStyle[i][j] | ||||
|                 } | ||||
|          | ||||
|                 const td = document.createElement("td") | ||||
|                 td.style.cssText = style; | ||||
|                 td.appendChild(htmlElem) | ||||
|                 tr.appendChild(td) | ||||
|             } | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ export class VariableUiElement extends BaseUIElement { | |||
|             } | ||||
| 
 | ||||
|             if (contents === undefined) { | ||||
|                 return | ||||
|                 return el; | ||||
|             } | ||||
|             if (typeof contents === "string") { | ||||
|                 el.innerHTML = contents | ||||
|  |  | |||
|  | @ -102,6 +102,8 @@ export default abstract class BaseUIElement { | |||
|         if(this.InnerConstructElement === undefined){ | ||||
|             throw "ERROR! This is not a correct baseUIElement: "+this.constructor.name | ||||
|         } | ||||
| try{ | ||||
|              | ||||
| 
 | ||||
|         const el = this.InnerConstructElement(); | ||||
| 
 | ||||
|  | @ -149,7 +151,13 @@ export default abstract class BaseUIElement { | |||
|             el.addEventListener('mouseout', () => self._onHover.setData(false)); | ||||
|         } | ||||
| 
 | ||||
|         return el | ||||
|         return el}catch(e){ | ||||
|             const domExc = e as DOMException; | ||||
|             if(domExc){ | ||||
|                 console.log("An exception occured", domExc.code, domExc.message, domExc.name ) | ||||
|             } | ||||
|             console.error(e) | ||||
| } | ||||
|     } | ||||
|      | ||||
|     public AsMarkdown(): string{ | ||||
|  |  | |||
|  | @ -1,324 +0,0 @@ | |||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import State from "../../State"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import {OH} from "./OpeningHours"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import Constants from "../../Models/Constants"; | ||||
| import opening_hours from "opening_hours"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| export default class OpeningHoursVisualization extends UIElement { | ||||
|     private static readonly weekdays = [ | ||||
|         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, | ||||
|     ] | ||||
|     private readonly _key: string; | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, key: string) { | ||||
|         super(tags); | ||||
|         this._key = key; | ||||
|         this.ListenTo(UIEventSource.Chronic(60 * 1000)); // Automatically reload every minute
 | ||||
|         this.ListenTo(UIEventSource.Chronic(500, () => { | ||||
|             return tags.data._country === undefined; | ||||
|         })); | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private static GetRanges(oh: any, from: Date, to: Date): ({ | ||||
|         isOpen: boolean, | ||||
|         isSpecial: boolean, | ||||
|         comment: string, | ||||
|         startDate: Date, | ||||
|         endDate: Date | ||||
|     }[])[] { | ||||
| 
 | ||||
| 
 | ||||
|         const values = [[], [], [], [], [], [], []]; | ||||
| 
 | ||||
|         const start = new Date(from); | ||||
|         // We go one day more into the past, in order to force rendering of holidays in the start of the period
 | ||||
|         start.setDate(from.getDate() - 1); | ||||
| 
 | ||||
|         const iterator = oh.getIterator(start); | ||||
| 
 | ||||
|         let prevValue = undefined; | ||||
|         while (iterator.advance(to)) { | ||||
| 
 | ||||
|             if (prevValue) { | ||||
|                 prevValue.endDate = iterator.getDate() as Date | ||||
|             } | ||||
|             const endDate = new Date(iterator.getDate()) as Date; | ||||
|             endDate.setHours(0, 0, 0, 0) | ||||
|             endDate.setDate(endDate.getDate() + 1); | ||||
|             const value = { | ||||
|                 isSpecial: iterator.getUnknown(), | ||||
|                 isOpen: iterator.getState(), | ||||
|                 comment: iterator.getComment(), | ||||
|                 startDate: iterator.getDate() as Date, | ||||
|                 endDate: endDate // Should be overwritten by the next iteration
 | ||||
|             } | ||||
|             prevValue = value; | ||||
| 
 | ||||
|             if (value.comment === undefined && !value.isOpen && !value.isSpecial) { | ||||
|                 // simply closed, nothing special here
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (value.startDate < from) { | ||||
|                 continue; | ||||
|             } | ||||
|             // Get day: sunday is 0, monday is 1. We move everything so that monday == 0
 | ||||
|             values[(value.startDate.getDay() + 6) % 7].push(value); | ||||
|         } | ||||
|         return values; | ||||
|     } | ||||
| 
 | ||||
|     private static getMonday(d) { | ||||
|         d = new Date(d); | ||||
|         const day = d.getDay(); | ||||
|         const diff = d.getDate() - day + (day == 0 ? -6 : 1); // adjust when day is sunday
 | ||||
|         return new Date(d.setDate(diff)); | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string | BaseUIElement { | ||||
| 
 | ||||
| 
 | ||||
|         const today = new Date(); | ||||
|         today.setHours(0, 0, 0, 0); | ||||
| 
 | ||||
|         const lastMonday = OpeningHoursVisualization.getMonday(today); | ||||
|         const nextSunday = new Date(lastMonday); | ||||
|         nextSunday.setDate(nextSunday.getDate() + 7); | ||||
| 
 | ||||
|         const tags = this._source.data; | ||||
|         if (tags._country === undefined) { | ||||
|             return "Loading country information..."; | ||||
|         } | ||||
|         let oh = null; | ||||
| 
 | ||||
|         try { | ||||
|             // noinspection JSPotentiallyInvalidConstructorUsage
 | ||||
|             oh = new opening_hours(tags[this._key], { | ||||
|                 lat: tags._lat, | ||||
|                 lon: tags._lon, | ||||
|                 address: { | ||||
|                     country_code: tags._country | ||||
|                 } | ||||
|             }, {tag_key: this._key}); | ||||
|         } catch (e) { | ||||
|             console.log(e); | ||||
|             return new Combine([Translations.t.general.opening_hours.error_loading, | ||||
|                 State.state?.osmConnection?.userDetails?.data?.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked ? | ||||
|                     `<span class='subtle'>${e}</span>` | ||||
|                     : "" | ||||
|             ]); | ||||
|         } | ||||
| 
 | ||||
|         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 mover further along
 | ||||
|                 lastMonday.setDate(lastMonday.getDate() + 7); | ||||
|                 nextSunday.setDate(nextSunday.getDate() + 7); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // ranges[0] are all ranges for monday
 | ||||
|         const ranges = OpeningHoursVisualization.GetRanges(oh, lastMonday, nextSunday); | ||||
|         if (ranges.map(r => r.length).reduce((a, b) => a + b, 0) == 0) { | ||||
|             // Closed!
 | ||||
|             const opensAtDate = oh.getNextChange(); | ||||
|             if (opensAtDate === undefined) { | ||||
|                 const comm = oh.getComment() ?? oh.getUnknown(); | ||||
|                 if (!!comm) { | ||||
|                     return new FixedUiElement(comm).SetClass("ohviz-closed"); | ||||
|                 } | ||||
| 
 | ||||
|                 if (oh.getState()) { | ||||
|                     return Translations.t.general.opening_hours.open_24_7.SetClass("ohviz-closed") | ||||
|                 } | ||||
|                 return Translations.t.general.opening_hours.closed_permanently.SetClass("ohviz-closed") | ||||
|             } | ||||
|             const moment = `${opensAtDate.getDate()}/${opensAtDate.getMonth() + 1} ${OH.hhmm(opensAtDate.getHours(), opensAtDate.getMinutes())}` | ||||
|             return Translations.t.general.opening_hours.closed_until.Subs({date: moment}).SetClass("ohviz-closed") | ||||
|         } | ||||
| 
 | ||||
|         const isWeekstable = oh.isWeekStable(); | ||||
| 
 | ||||
|         let [changeHours, changeHourText] = OpeningHoursVisualization.allChangeMoments(ranges); | ||||
| 
 | ||||
|         // 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 rows: BaseUIElement[] = []; | ||||
|         const availableArea = latestclose - earliestOpen; | ||||
|         // @ts-ignore
 | ||||
|         const now = (100 * (((new Date() - today) / 1000) - earliestOpen)) / availableArea; | ||||
| 
 | ||||
| 
 | ||||
|         let header: BaseUIElement[] = []; | ||||
| 
 | ||||
|         if (now >= 0 && now <= 100) { | ||||
|             header.push(new FixedUiElement("").SetStyle(`left:${now}%;`).SetClass("ohviz-now")) | ||||
|         } | ||||
|         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"); | ||||
|             header.push(el); | ||||
|         } | ||||
| 
 | ||||
|         for (let i = 0; i < changeHours.length; i++) { | ||||
|             let changeMoment = changeHours[i]; | ||||
|             const offset = 100 * (changeMoment - earliestOpen) / availableArea; | ||||
|             if (offset < 0 || offset > 100) { | ||||
|                 continue; | ||||
|             } | ||||
|             const el = new FixedUiElement( | ||||
|                 `<div style='margin-top: ${i % 2 == 0 ? '1.5em;' : "1%"}'>${changeHourText[i]}</div>` | ||||
|             ) | ||||
|                 .SetStyle(`left:${offset}%`) | ||||
|                 .SetClass("ohviz-time-indication"); | ||||
|             header.push(el); | ||||
|         } | ||||
| 
 | ||||
|         rows.push(new Combine([`<td width="5%"> </td>`, | ||||
|             `<td style="position:relative;height:2.5em;">`, | ||||
|             new Combine(header), `</td>`])); | ||||
| 
 | ||||
|         for (let i = 0; i < 7; i++) { | ||||
|             const dayRanges = ranges[i]; | ||||
|             const isToday = (new Date().getDay() + 6) % 7 === i; | ||||
|             let weekday = OpeningHoursVisualization.weekdays[i]; | ||||
| 
 | ||||
|             let dateToShow = "" | ||||
|             if (!isWeekstable) { | ||||
|                 const day = new Date(lastMonday) | ||||
|                 day.setDate(day.getDate() + i); | ||||
|                 dateToShow = "" + day.getDate() + "/" + (day.getMonth() + 1); | ||||
|             } | ||||
| 
 | ||||
|             let innerContent: (string | BaseUIElement)[] = []; | ||||
| 
 | ||||
|             // Add the lines
 | ||||
|             for (const changeMoment of changeHours) { | ||||
|                 const offset = 100 * (changeMoment - earliestOpen) / availableArea; | ||||
|                 innerContent.push(new FixedUiElement("").SetStyle(`left:${offset}%;`).SetClass("ohviz-line")) | ||||
|             } | ||||
| 
 | ||||
|             // Add the actual ranges
 | ||||
|             for (const range of dayRanges) { | ||||
|                 if (!range.isOpen && !range.isSpecial) { | ||||
|                     innerContent.push( | ||||
|                         new FixedUiElement(range.comment ?? dateToShow).SetClass("ohviz-day-off")) | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
|                 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); | ||||
|                 innerContent.push( | ||||
|                     new FixedUiElement(range.comment ?? dateToShow).SetStyle(`left:${startPercentage}%; width:${width}%`).SetClass("ohviz-range")) | ||||
|             } | ||||
| 
 | ||||
|             // Add line for 'now'
 | ||||
|             if (now >= 0 && now <= 100) { | ||||
|                 innerContent.push(new FixedUiElement("").SetStyle(`left:${now}%;`).SetClass("ohviz-now")) | ||||
|             } | ||||
| 
 | ||||
|             let clss = "" | ||||
|             if (isToday) { | ||||
|                 clss = "ohviz-today" | ||||
|             } | ||||
| 
 | ||||
|             rows.push(new Combine( | ||||
|                 [`<td class="ohviz-weekday ${clss}">${weekday}</td>`, | ||||
|                     `<td style="position:relative;" class="${clss}">`, | ||||
|                     ...innerContent, | ||||
|                     `</td>`])) | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         return new Combine([ | ||||
|             "<table class='ohviz' style='width:100%; word-break: normal; word-wrap: normal'>", | ||||
|             ...rows.map(el => new Combine(["<tr>" ,el , "</tr>"])), | ||||
|             "</table>" | ||||
|         ]).SetClass("ohviz-container"); | ||||
|     } | ||||
| 
 | ||||
|     private static allChangeMoments(ranges: { | ||||
|         isOpen: boolean, | ||||
|         isSpecial: boolean, | ||||
|         comment: string, | ||||
|         startDate: Date, | ||||
|         endDate: Date | ||||
|     }[][]): [number[], string[]] { | ||||
|         const changeHours: number[] = [] | ||||
|         const changeHourText: string[] = []; | ||||
|         const extrachangeHours: number[] = [] | ||||
|         const extrachangeHourText: string[] = []; | ||||
| 
 | ||||
|         for (const weekday of ranges) { | ||||
|             for (const range of weekday) { | ||||
|                 if (!range.isOpen && !range.isSpecial) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 const startOfDay: Date = new Date(range.startDate); | ||||
|                 startOfDay.setHours(0, 0, 0, 0); | ||||
|                 // @ts-ignore
 | ||||
|                 const changeMoment: number = (range.startDate - startOfDay) / 1000; | ||||
|                 if (changeHours.indexOf(changeMoment) < 0) { | ||||
|                     changeHours.push(changeMoment); | ||||
|                     changeHourText.push(OH.hhmm(range.startDate.getHours(), range.startDate.getMinutes())) | ||||
|                 } | ||||
| 
 | ||||
|                 // @ts-ignore
 | ||||
|                 let changeMomentEnd: number = (range.endDate - startOfDay) / 1000; | ||||
|                 if (changeMomentEnd >= 24 * 60 * 60) { | ||||
|                     if (extrachangeHours.indexOf(changeMomentEnd) < 0) { | ||||
|                         extrachangeHours.push(changeMomentEnd); | ||||
|                         extrachangeHourText.push(OH.hhmm(range.endDate.getHours(), range.endDate.getMinutes())) | ||||
|                     } | ||||
|                 } else if (changeHours.indexOf(changeMomentEnd) < 0) { | ||||
|                     changeHours.push(changeMomentEnd); | ||||
|                     changeHourText.push(OH.hhmm(range.endDate.getHours(), range.endDate.getMinutes())) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         changeHourText.sort(); | ||||
|         changeHours.sort(); | ||||
|         extrachangeHourText.sort(); | ||||
|         extrachangeHours.sort(); | ||||
|         changeHourText.push(...extrachangeHourText); | ||||
|         changeHours.push(...extrachangeHours); | ||||
| 
 | ||||
|         return [changeHours, changeHourText] | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -8,6 +8,9 @@ export interface OpeningHour { | |||
|     endMinutes: number | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Various utilities manipulating opening hours | ||||
|  */ | ||||
| export class OH { | ||||
| 
 | ||||
| 
 | ||||
|  | @ -163,6 +166,12 @@ export class OH { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gives the number of hours since the start of day. | ||||
|      * E.g. | ||||
|      * startTime({startHour: 9, startMinuts: 15}) == 9.25 | ||||
|      * @param oh | ||||
|      */ | ||||
|     public static startTime(oh: OpeningHour): number { | ||||
|         return oh.startHour + oh.startMinutes / 60; | ||||
|     } | ||||
|  | @ -348,5 +357,125 @@ export class OH { | |||
| 
 | ||||
|         return ohs; | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|  This function converts a number of ranges (generated by OpeningHours.js) into all the hours of day that a change occurs. | ||||
|  E.g. | ||||
|  Monday, some business is opended from 9:00 till 17:00 | ||||
|  Tuesday from 9:30 till 18:00 | ||||
|  Wednesday from 9:30 till 12:30 | ||||
|  This function will extract all those moments of change and will return 9:00, 9:30, 12:30, 17:00 and 18:00 | ||||
|  This list will be sorted | ||||
|  */ | ||||
|     public static allChangeMoments(ranges: { | ||||
|         isOpen: boolean, | ||||
|         isSpecial: boolean, | ||||
|         comment: string, | ||||
|         startDate: Date, | ||||
|         endDate: Date | ||||
|     }[][]): [number[], string[]] { | ||||
|         const changeHours: number[] = [] | ||||
|         const changeHourText: string[] = []; | ||||
|          | ||||
|         const extrachangeHours: number[] = [] | ||||
|         const extrachangeHourText: string[] = []; | ||||
| 
 | ||||
|         for (const weekday of ranges) { | ||||
|             for (const range of weekday) { | ||||
|                 if (!range.isOpen && !range.isSpecial) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 const startOfDay: Date = new Date(range.startDate); | ||||
|                 startOfDay.setHours(0, 0, 0, 0); | ||||
|                  | ||||
|                 // The number of seconds since the start of the day
 | ||||
|                 // @ts-ignore
 | ||||
|                 const changeMoment: number = (range.startDate - startOfDay) / 1000; | ||||
|                 if (changeHours.indexOf(changeMoment) < 0) { | ||||
|                     changeHours.push(changeMoment); | ||||
|                     changeHourText.push(OH.hhmm(range.startDate.getHours(), range.startDate.getMinutes())) | ||||
|                 } | ||||
| 
 | ||||
|                 // The number of seconds till between the start of the day and closing
 | ||||
|                 // @ts-ignore
 | ||||
|                 let changeMomentEnd: number = (range.endDate - startOfDay) / 1000; | ||||
|                 if (changeMomentEnd >= 24 * 60 * 60) { | ||||
|                     if (extrachangeHours.indexOf(changeMomentEnd) < 0) { | ||||
|                         extrachangeHours.push(changeMomentEnd); | ||||
|                         extrachangeHourText.push(OH.hhmm(range.endDate.getHours(), range.endDate.getMinutes())) | ||||
|                     } | ||||
|                 } else if (changeHours.indexOf(changeMomentEnd) < 0) { | ||||
|                     changeHours.push(changeMomentEnd); | ||||
|                     changeHourText.push(OH.hhmm(range.endDate.getHours(), range.endDate.getMinutes())) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Note that 'changeHours' and 'changeHourText' will be more or less in sync - one is in numbers, the other in 'HH:MM' format.
 | ||||
|         // But both can be sorted without problem; they'll stay in sync
 | ||||
|         changeHourText.sort(); | ||||
|         changeHours.sort(); | ||||
|         extrachangeHourText.sort(); | ||||
|         extrachangeHours.sort(); | ||||
|          | ||||
|         changeHourText.push(...extrachangeHourText); | ||||
|         changeHours.push(...extrachangeHours); | ||||
| 
 | ||||
|         return [changeHours, changeHourText] | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|  Calculates when the business is opened (or on holiday) between two dates. | ||||
|  Returns a matrix of ranges, where [0] is a list of ranges when it is opened on monday, [1] is a list of ranges for tuesday, ... | ||||
|   */ | ||||
|     public static GetRanges(oh: any, from: Date, to: Date): ({ | ||||
|         isOpen: boolean, | ||||
|         isSpecial: boolean, | ||||
|         comment: string, | ||||
|         startDate: Date, | ||||
|         endDate: Date | ||||
|     }[])[] { | ||||
| 
 | ||||
| 
 | ||||
|         const values = [[], [], [], [], [], [], []]; | ||||
| 
 | ||||
|         const start = new Date(from); | ||||
|         // We go one day more into the past, in order to force rendering of holidays in the start of the period
 | ||||
|         start.setDate(from.getDate() - 1); | ||||
| 
 | ||||
|         const iterator = oh.getIterator(start); | ||||
| 
 | ||||
|         let prevValue = undefined; | ||||
|         while (iterator.advance(to)) { | ||||
| 
 | ||||
|             if (prevValue) { | ||||
|                 prevValue.endDate = iterator.getDate() as Date | ||||
|             } | ||||
|             const endDate = new Date(iterator.getDate()) as Date; | ||||
|             endDate.setHours(0, 0, 0, 0) | ||||
|             endDate.setDate(endDate.getDate() + 1); | ||||
|             const value = { | ||||
|                 isSpecial: iterator.getUnknown(), | ||||
|                 isOpen: iterator.getState(), | ||||
|                 comment: iterator.getComment(), | ||||
|                 startDate: iterator.getDate() as Date, | ||||
|                 endDate: endDate // Should be overwritten by the next iteration
 | ||||
|             } | ||||
|             prevValue = value; | ||||
| 
 | ||||
|             if (value.comment === undefined && !value.isOpen && !value.isSpecial) { | ||||
|                 // simply closed, nothing special here
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (value.startDate < from) { | ||||
|                 continue; | ||||
|             } | ||||
|             // Get day: sunday is 0, monday is 1. We move everything so that monday == 0
 | ||||
|             values[(value.startDate.getDay() + 6) % 7].push(value); | ||||
|         } | ||||
|         return values; | ||||
|     } | ||||
|      | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| import OpeningHoursRange from "./OpeningHoursRange"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import OpeningHoursPickerTable from "./OpeningHoursPickerTable"; | ||||
|  | @ -8,63 +7,39 @@ import {InputElement} from "../Input/InputElement"; | |||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| export default class OpeningHoursPicker extends InputElement<OpeningHour[]> { | ||||
|     private readonly _ohs: UIEventSource<OpeningHour[]>;     | ||||
|     public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
| 
 | ||||
|     private readonly _ohs: UIEventSource<OpeningHour[]>; | ||||
|     private readonly _backgroundTable: OpeningHoursPickerTable; | ||||
| 
 | ||||
|     private readonly _weekdays: UIEventSource<BaseUIElement[]> = new UIEventSource<BaseUIElement[]>([]); | ||||
| 
 | ||||
|     constructor(ohs: UIEventSource<OpeningHour[]> = new UIEventSource<OpeningHour[]>([])) { | ||||
|         super(); | ||||
|         this._ohs = ohs; | ||||
|         this._backgroundTable = new OpeningHoursPickerTable(this._weekdays, this._ohs); | ||||
|         const self = this; | ||||
| 
 | ||||
|          | ||||
|         this._ohs.addCallback(ohs => { | ||||
|             self._ohs.setData(OH.MergeTimes(ohs)); | ||||
|         ohs.addCallback(oh => { | ||||
|             ohs.setData(OH.MergeTimes(oh)); | ||||
|         }) | ||||
| 
 | ||||
|         ohs.addCallbackAndRun(ohs => { | ||||
|             const perWeekday: UIElement[][] = []; | ||||
|             for (let i = 0; i < 7; i++) { | ||||
|                 perWeekday[i] = []; | ||||
|             } | ||||
| 
 | ||||
|             for (const oh of ohs) { | ||||
|                 const source = new UIEventSource<OpeningHour>(oh) | ||||
|                 source.addCallback(_ => { | ||||
|                     self._ohs.setData(OH.MergeTimes(self._ohs.data)) | ||||
|                 }) | ||||
|                 const r = new OpeningHoursRange(source, this._backgroundTable); | ||||
|                 perWeekday[oh.weekday].push(r);  | ||||
|             } | ||||
| 
 | ||||
|             for (let i = 0; i < 7; i++) { | ||||
|                 self._weekdays.data[i] = new Combine(perWeekday[i]); | ||||
|             } | ||||
|             self._weekdays.ping(); | ||||
| 
 | ||||
|         }); | ||||
|         this._backgroundTable = new OpeningHoursPickerTable(this._ohs); | ||||
|         this._backgroundTable.ConstructElement() | ||||
| 
 | ||||
|         ohs.ping(); | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): BaseUIElement { | ||||
|         return this._backgroundTable; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         return this._backgroundTable.ConstructElement(); | ||||
|     } | ||||
| 
 | ||||
|     GetValue(): UIEventSource<OpeningHour[]> { | ||||
|         return this._ohs | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     IsValid(t: OpeningHour[]): boolean { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         return this._backgroundTable.ConstructElement(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -3,19 +3,18 @@ | |||
|  * It will genarate the currently selected opening hour. | ||||
|  */ | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import {OpeningHour} from "./OpeningHours"; | ||||
| import {InputElement} from "../Input/InputElement"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import {Translation} from "../i18n/Translation"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import OpeningHoursRange from "./OpeningHoursRange"; | ||||
| 
 | ||||
| export default class OpeningHoursPickerTable extends InputElement<OpeningHour[]> { | ||||
|     public readonly IsSelected: UIEventSource<boolean>; | ||||
|     private readonly weekdays: UIEventSource<BaseUIElement[]>; | ||||
|     private readonly _element: HTMLTableElement | ||||
| 
 | ||||
|     public static readonly days: BaseUIElement[] = | ||||
|     public static readonly days: Translation[] = | ||||
|         [ | ||||
|             Translations.t.general.weekdays.abbreviations.monday, | ||||
|             Translations.t.general.weekdays.abbreviations.tuesday, | ||||
|  | @ -25,59 +24,105 @@ export default class OpeningHoursPickerTable extends InputElement<OpeningHour[]> | |||
|             Translations.t.general.weekdays.abbreviations.saturday, | ||||
|             Translations.t.general.weekdays.abbreviations.sunday | ||||
|         ] | ||||
| 
 | ||||
| 
 | ||||
|     public readonly IsSelected: UIEventSource<boolean>; | ||||
|     private readonly source: UIEventSource<OpeningHour[]>; | ||||
|      | ||||
|     private static _nextId = 0; | ||||
|     /* | ||||
|     These html-elements are an overlay over the table columns and act as a host for the ranges in the weekdays | ||||
|      */ | ||||
|     public readonly weekdayElements : HTMLElement[] = Utils.TimesT(7, () => document.createElement("div")) | ||||
| 
 | ||||
|     constructor(weekdays: UIEventSource<BaseUIElement[]>, source?: UIEventSource<OpeningHour[]>) { | ||||
|     constructor(source?: UIEventSource<OpeningHour[]>) { | ||||
|         super(); | ||||
|         this.weekdays = weekdays; | ||||
|         this.source = source ?? new UIEventSource<OpeningHour[]>([]); | ||||
|         this.IsSelected = new UIEventSource<boolean>(false); | ||||
|         this.SetStyle("width:100%;height:100%;display:block;"); | ||||
| 
 | ||||
| 
 | ||||
|         const id = OpeningHoursPickerTable._nextId; | ||||
| OpeningHoursPickerTable._nextId ++ ; | ||||
| 
 | ||||
| 
 | ||||
|         let rows = ""; | ||||
|         const self = this; | ||||
|         for (let h = 0; h < 24; h++) { | ||||
|             let hs = "" + h; | ||||
|             if (hs.length == 1) { | ||||
|                 hs = "0" + hs; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|             rows += `<tr><td rowspan="2" class="oh-left-col oh-timecell-full">${hs}:00</td>` + | ||||
|                 Utils.Times(weekday => `<td id="${id}-timecell-${weekday}-${h}" class="oh-timecell oh-timecell-full oh-timecell-${weekday}"></td>`, 7) + | ||||
|                 '</tr><tr>' + | ||||
|                 Utils.Times(id => `<td id="${id}-timecell-${id}-${h}-30" class="oh-timecell oh-timecell-half oh-timecell-${id}"></td>`, 7) + | ||||
|                 '</tr>'; | ||||
|     IsValid(t: OpeningHour[]): boolean { | ||||
|         return true; | ||||
|     } | ||||
|         let days = OpeningHoursPickerTable.days.map((day, i) => { | ||||
|             const innerContent  =  self.weekdays.data[i]?.ConstructElement()?.innerHTML ?? ""; | ||||
|             return day.ConstructElement().innerHTML + "<span style='width:100%; display:block; position: relative;'>"+innerContent+"</span>"; | ||||
|         }).join("</th><th width='14%'>"); | ||||
| 
 | ||||
|         this._element = document.createElement("table") | ||||
|         const el = this._element; | ||||
|         this.SetClass("oh-table") | ||||
|         el.innerHTML =`<tr><th></th><th width='14%'>${days}</th></tr>${rows}`; | ||||
|     GetValue(): UIEventSource<OpeningHour[]> { | ||||
|         return this.source; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         return this._element | ||||
| 
 | ||||
|         const table = document.createElement("table") | ||||
|         table.classList.add("oh-table") | ||||
| 
 | ||||
|         const headerRow = document.createElement("tr") | ||||
|         headerRow.appendChild(document.createElement("th")) | ||||
|         for (let i = 0; i < OpeningHoursPickerTable.days.length; i++) { | ||||
|             let weekday = OpeningHoursPickerTable.days[i].Clone(); | ||||
|             const cell = document.createElement("th") | ||||
|             cell.style.width = "14%" | ||||
|             cell.appendChild(weekday.ConstructElement()) | ||||
|             const fullColumnSpan = this.weekdayElements[i] | ||||
|             fullColumnSpan.classList.add("w-full","h-full","relative") | ||||
|             fullColumnSpan.style.height = "42rem"   | ||||
|              | ||||
|              | ||||
|             const ranges = new VariableUiElement( | ||||
|                 this.source.map(ohs => ohs.filter((oh : OpeningHour) => oh.weekday === i)) | ||||
|                     .map(ohsForToday => { | ||||
|                         return new Combine(ohsForToday.map(oh => new OpeningHoursRange(oh, () =>{ | ||||
|                             this.source.data.splice(this.source.data.indexOf(oh), 1) | ||||
|                             this.source.ping() | ||||
|                         }))) | ||||
|                     }) | ||||
|             ) | ||||
|             fullColumnSpan.appendChild(ranges.ConstructElement()) | ||||
|              | ||||
|              | ||||
|              | ||||
|              | ||||
|             const fullColumnSpanWrapper = document.createElement("div") | ||||
|             fullColumnSpanWrapper.classList.add("absolute") | ||||
|             fullColumnSpanWrapper.style.zIndex = "10" | ||||
|             fullColumnSpanWrapper.style.width = "13.5%" | ||||
|             fullColumnSpanWrapper.style.pointerEvents = "none" | ||||
| 
 | ||||
|             fullColumnSpanWrapper.appendChild(fullColumnSpan) | ||||
|              | ||||
|             cell.appendChild(fullColumnSpanWrapper) | ||||
|             headerRow.appendChild(cell) | ||||
|         } | ||||
| 
 | ||||
|     private InnerUpdate(table: HTMLTableElement) { | ||||
|         table.appendChild(headerRow) | ||||
| 
 | ||||
|         const self = this; | ||||
|         if (table === undefined || table === null) { | ||||
|             return; | ||||
|         for (let h = 0; h < 24; h++) { | ||||
| 
 | ||||
|             const hs = Utils.TwoDigits(h); | ||||
|             const firstCell = document.createElement("td") | ||||
|             firstCell.rowSpan = 2 | ||||
|             firstCell.classList.add("oh-left-col", "oh-timecell-full", "border-box","h-2") | ||||
|             firstCell.appendChild(new FixedUiElement(hs + ":00").ConstructElement()) | ||||
| 
 | ||||
|             const evenRow = document.createElement("tr") | ||||
|             evenRow.appendChild(firstCell); | ||||
| 
 | ||||
|             for (let weekday = 0; weekday < 7; weekday++) { | ||||
|                 const cell = document.createElement("td") | ||||
|                 cell.classList.add("oh-timecell", "oh-timecell-full", `oh-timecell-${weekday}`) | ||||
|                 evenRow.appendChild(cell) | ||||
|             } | ||||
|             table.appendChild(evenRow) | ||||
| 
 | ||||
|             const oddRow = document.createElement("tr") | ||||
| 
 | ||||
|             for (let weekday = 0; weekday < 7; weekday++) { | ||||
|                 const cell = document.createElement("td") | ||||
|                 cell.classList.add("oh-timecell", "oh-timecell-half", `oh-timecell-${weekday}`) | ||||
|                 oddRow.appendChild(cell) | ||||
|             } | ||||
|             table.appendChild(oddRow) | ||||
|         } | ||||
| 
 | ||||
|          | ||||
|         /**** Event handling below ***/ | ||||
| 
 | ||||
| 
 | ||||
|         let mouseIsDown = false; | ||||
|  | @ -123,6 +168,7 @@ OpeningHoursPickerTable._nextId ++ ; | |||
|                     oh.endMinutes = 0; | ||||
|                 } | ||||
|                 self.source.data.push(oh); | ||||
|                 console.log("Created ", oh) | ||||
|             } | ||||
|             self.source.ping(); | ||||
| 
 | ||||
|  | @ -149,6 +195,7 @@ OpeningHoursPickerTable._nextId ++ ; | |||
|         }; | ||||
| 
 | ||||
|         let lastSelectionIend, lastSelectionJEnd; | ||||
| 
 | ||||
|         function selectAllBetween(iEnd, jEnd) { | ||||
| 
 | ||||
|             if (lastSelectionIend === iEnd && lastSelectionJEnd === jEnd) { | ||||
|  | @ -287,15 +334,7 @@ OpeningHoursPickerTable._nextId ++ ; | |||
| 
 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     IsValid(t: OpeningHour[]): boolean { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     GetValue(): UIEventSource<OpeningHour[]> { | ||||
|         return this.source; | ||||
|         return table | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,73 +1,57 @@ | |||
| /** | ||||
|  * A single opening hours range, shown on top of the OH-picker table | ||||
|  */ | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import Svg from "../../Svg"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import {OH, OpeningHour} from "./OpeningHours"; | ||||
| import OpeningHoursPickerTable from "./OpeningHoursPickerTable"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| 
 | ||||
| export default class OpeningHoursRange extends UIElement { | ||||
|     private _oh: UIEventSource<OpeningHour>; | ||||
| export default class OpeningHoursRange extends BaseUIElement { | ||||
|     private _oh: OpeningHour; | ||||
| 
 | ||||
|     private readonly _startTime: BaseUIElement; | ||||
|     private readonly _endTime: BaseUIElement; | ||||
|     private readonly _deleteRange: BaseUIElement; | ||||
|     private readonly _tableId: OpeningHoursPickerTable; | ||||
|     private readonly _onDelete: () => void; | ||||
| 
 | ||||
|     constructor(oh: UIEventSource<OpeningHour>, tableId: OpeningHoursPickerTable) { | ||||
|         super(oh); | ||||
|         this._tableId = tableId; | ||||
|         const self = this; | ||||
|     constructor(oh: OpeningHour, onDelete: () => void) { | ||||
|         super(); | ||||
|         this._oh = oh; | ||||
|         this._onDelete = onDelete; | ||||
|         this.SetClass("oh-timerange"); | ||||
|         oh.addCallbackAndRun(() => { | ||||
|             const el = document.getElementById(this.id) as HTMLElement; | ||||
|             self.InnerUpdate(el); | ||||
|         }) | ||||
| 
 | ||||
|         this._deleteRange =  | ||||
|     } | ||||
| 
 | ||||
|     InnerConstructElement(): HTMLElement { | ||||
|         const height = this.getHeight(); | ||||
|         const oh = this._oh; | ||||
|         const startTime = new FixedUiElement(Utils.TwoDigits(oh.startHour) + ":" + Utils.TwoDigits(oh.startMinutes)).SetClass("oh-timerange-label") | ||||
|         const endTime = new FixedUiElement(Utils.TwoDigits(oh.endHour) + ":" + Utils.TwoDigits(oh.endMinutes)).SetClass("oh-timerange-label") | ||||
| 
 | ||||
| 
 | ||||
|         const deleteRange = | ||||
|             Svg.delete_icon_ui() | ||||
|                 .SetClass("oh-delete-range") | ||||
|                 .onClick(() => { | ||||
|                 oh.data.weekday = undefined; | ||||
|                 oh.ping(); | ||||
|                     this._onDelete() | ||||
|                 }); | ||||
| 
 | ||||
| 
 | ||||
|         this._startTime = new VariableUiElement(oh.map(oh => { | ||||
|             return Utils.TwoDigits(oh.startHour) + ":" + Utils.TwoDigits(oh.startMinutes); | ||||
|         })).SetClass("oh-timerange-label") | ||||
| 
 | ||||
|         this._endTime = new VariableUiElement(oh.map(oh => { | ||||
|             return Utils.TwoDigits(oh.endHour) + ":" + Utils.TwoDigits(oh.endMinutes); | ||||
|         })).SetClass("oh-timerange-label") | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): BaseUIElement { | ||||
|         const oh = this._oh.data; | ||||
|         if (oh === undefined) { | ||||
|             return undefined; | ||||
|         } | ||||
|         const height = this.getHeight(); | ||||
| 
 | ||||
|         let content = [this._deleteRange] | ||||
|         let content = [deleteRange] | ||||
|         if (height > 2) { | ||||
|             content = [this._startTime, this._deleteRange, this._endTime]; | ||||
|             content = [startTime, deleteRange, endTime]; | ||||
|         } | ||||
| 
 | ||||
|         return new Combine(content) | ||||
|             .SetClass("oh-timerange-inner") | ||||
|         const el = new Combine(content) | ||||
|             .SetClass("oh-timerange-inner").ConstructElement(); | ||||
| 
 | ||||
|         el.style.top = (100 * OH.startTime(oh) / 24) + "%" | ||||
|         el.style.height = (100 * (OH.endTime(oh) - OH.startTime(oh)) / 24) + "%" | ||||
|         return el; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private getHeight(): number { | ||||
|         const oh = this._oh.data; | ||||
|         const oh = this._oh; | ||||
| 
 | ||||
|         let endhour = oh.endHour; | ||||
|         if (oh.endHour == 0 && oh.endMinutes == 0) { | ||||
|  | @ -76,28 +60,5 @@ export default class OpeningHoursRange extends UIElement { | |||
|         return (endhour - oh.startHour + ((oh.endMinutes - oh.startMinutes) / 60)); | ||||
|     } | ||||
| 
 | ||||
|     protected InnerUpdate(el: HTMLElement) { | ||||
|         if (el == null) { | ||||
|             return; | ||||
|         } | ||||
|         const oh = this._oh.data; | ||||
|         if (oh === undefined) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // The header cell containing monday, tuesday, ...
 | ||||
|         const table = this._tableId.ConstructElement() as HTMLTableElement; | ||||
| 
 | ||||
|         const bodyRect = document.body.getBoundingClientRect(); | ||||
|         const rangeStart = table.rows[1].cells[1].getBoundingClientRect().top - bodyRect.top; | ||||
|         const rangeEnd = table.rows[table.rows.length - 1].cells[1].getBoundingClientRect().bottom - bodyRect.top; | ||||
| 
 | ||||
|         const pixelsPerHour = (rangeEnd - rangeStart) / 24; | ||||
| 
 | ||||
|         el.style.top = (pixelsPerHour * OH.startTime(oh)) + "px"; | ||||
|         el.style.height = (pixelsPerHour * (OH.endTime(oh) - OH.startTime(oh))) + "px"; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										292
									
								
								UI/OpeningHours/OpeningHoursVisualization.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								UI/OpeningHours/OpeningHoursVisualization.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,292 @@ | |||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import State from "../../State"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import {OH} from "./OpeningHours"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import Constants from "../../Models/Constants"; | ||||
| import opening_hours from "opening_hours"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import Table from "../Base/Table"; | ||||
| import {Translation} from "../i18n/Translation"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| 
 | ||||
| export default class OpeningHoursVisualization extends UIElement { | ||||
|     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, | ||||
|     ] | ||||
|     private readonly _tags: UIEventSource<any>; | ||||
|     private readonly _key: string; | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, key: string) { | ||||
|         super() | ||||
|         this._tags = tags; | ||||
|         this._key = key; | ||||
|     } | ||||
|      | ||||
|     InnerRender(): BaseUIElement { | ||||
|         const tags = this._tags; | ||||
|         const key = this._key; | ||||
|         const tagsDirect = tags.data; | ||||
|         const ohTable = new VariableUiElement(tags | ||||
|             .map(tags => tags[key]) // This mapping will absorb all other changes to tags in order to prevent regeneration
 | ||||
|             .map(ohtext => { | ||||
|                     try { | ||||
|                         // noinspection JSPotentiallyInvalidConstructorUsage
 | ||||
|                         const oh = new opening_hours(ohtext, { | ||||
|                             lat: tagsDirect._lat, | ||||
|                             lon: tagsDirect._lon, | ||||
|                             address: { | ||||
|                                 country_code: tagsDirect._country | ||||
|                             } | ||||
|                         }, {tag_key: this._key}); | ||||
| 
 | ||||
|                         return OpeningHoursVisualization.CreateFullVisualisation(oh) | ||||
|                     } catch (e) { | ||||
|                         console.log(e); | ||||
|                         return new Combine([Translations.t.general.opening_hours.error_loading, | ||||
|                             new Toggle( | ||||
|                                 new FixedUiElement(e).SetClass("subtle"), | ||||
|                                 undefined, | ||||
|                                 State.state?.osmConnection?.userDetails.map(userdetails => userdetails.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked) | ||||
|                             ) | ||||
|                         ]); | ||||
|                     } | ||||
| 
 | ||||
|                 } | ||||
|             )) | ||||
| 
 | ||||
|         return new Toggle( | ||||
|             ohTable, | ||||
|             Translations.t.general.loadingCountry.Clone(), | ||||
|             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); | ||||
|         const lastMonday = OpeningHoursVisualization.getMonday(today); | ||||
|         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([ | ||||
|                 ...OpeningHoursVisualization.CreateLinesAtChangeHours(changeHours, availableArea, earliestOpen) , | ||||
|                 ...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], | ||||
|             [["width: 5%", `position: relative; height: ${headerHeight}`], ...weekdayStyles] | ||||
|         ).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; | ||||
|             } | ||||
| 
 | ||||
|             if (i > 0 && ((changeMoment - changeHours[i - 1]) / (60*60)) < 2) { | ||||
|                 // 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}) | ||||
|     } | ||||
| 
 | ||||
|     private static getMonday(d) { | ||||
|         d = new Date(d); | ||||
|         const day = d.getDay(); | ||||
|         const diff = d.getDate() - day + (day == 0 ? -6 : 1); // adjust when day is sunday
 | ||||
|         return new Date(d.setDate(diff)); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -18,6 +18,7 @@ export default class EditableTagRendering extends Toggle { | |||
|         const editMode = new UIEventSource<boolean>(false); | ||||
| 
 | ||||
|         const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration) | ||||
|         answer.SetClass("w-full") | ||||
|         let rendering = answer; | ||||
| 
 | ||||
|         if (configuration.question !== undefined && State.state?.featureSwitchUserbadge?.data) { | ||||
|  |  | |||
|  | @ -109,7 +109,6 @@ export default class ShowDataLayer { | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private postProcessFeature(feature, leafletLayer: L.Layer) { | ||||
|         const layer: LayerConfig = this._layerDict[feature._matching_layer_id]; | ||||
|         if (layer === undefined) { | ||||
|  | @ -156,7 +155,7 @@ export default class ShowDataLayer { | |||
|             if (selected === undefined || self._leafletMap.data === undefined) { | ||||
|                 return; | ||||
|             } | ||||
|             if (popup.isOpen()) { | ||||
|             if (leafletLayer.getPopup().isOpen()) { | ||||
|                 return; | ||||
|             } | ||||
|             if (selected.properties.id === feature.properties.id) { | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ import ReviewElement from "./Reviews/ReviewElement"; | |||
| import MangroveReviews from "../Logic/Web/MangroveReviews"; | ||||
| import Translations from "./i18n/Translations"; | ||||
| import ReviewForm from "./Reviews/ReviewForm"; | ||||
| import OpeningHoursVisualization from "./OpeningHours/OhVisualization"; | ||||
| import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"; | ||||
| 
 | ||||
| import State from "../State"; | ||||
| import {ImageSearcher} from "../Logic/Actors/ImageSearcher"; | ||||
|  | @ -120,11 +120,7 @@ export default class SpecialVisualizations { | |||
|                     doc: "The tagkey from which the table is constructed." | ||||
|                 }], | ||||
|                 constr: (state: State, tagSource: UIEventSource<any>, args) => { | ||||
|                     let keyname = args[0]; | ||||
|                     if (keyname === undefined || keyname === "") { | ||||
|                         keyname = keyname ?? "opening_hours" | ||||
|                     } | ||||
|                     return new OpeningHoursVisualization(tagSource, keyname) | ||||
|                     return new OpeningHoursVisualization(tagSource, args[0]) | ||||
|                 } | ||||
|             }, | ||||
| 
 | ||||
|  |  | |||
|  | @ -80,8 +80,6 @@ export abstract class UIElement extends BaseUIElement{ | |||
|          | ||||
|     } | ||||
| 
 | ||||
|    | ||||
| 
 | ||||
|     /** | ||||
|      * @deprecated The method should not be used | ||||
|      */ | ||||
|  |  | |||
							
								
								
									
										8
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										8
									
								
								Utils.ts
									
										
									
									
									
								
							|  | @ -73,6 +73,14 @@ export class Utils { | |||
|         return res; | ||||
|     } | ||||
| 
 | ||||
|     public static TimesT<T>(count : number, f: ((i: number) => T)): T[] { | ||||
|         let res : T[] = []; | ||||
|         for (let i = 0; i < count; i++) { | ||||
|             res .push(f(i)); | ||||
|         } | ||||
|         return res; | ||||
|     } | ||||
| 
 | ||||
|     static DoEvery(millis: number, f: (() => void)) { | ||||
|         if (Utils.runningFromConsole) { | ||||
|             return; | ||||
|  |  | |||
|  | @ -103,38 +103,29 @@ | |||
|     border-right: 10px solid var(--catch-detail-color) !important; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .oh-draggable-header { | ||||
|     background-color: blue; | ||||
|     height: 0.5em; | ||||
| } | ||||
| 
 | ||||
| .oh-timerange { | ||||
|     color: var(--catch-detail-color-contrast); | ||||
|     border-radius: 0.5em; | ||||
|     margin-left: 2px; | ||||
|     display: block; | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     left: 0; | ||||
|     width: calc(100% - 4px); | ||||
|     margin-left: calc(5% - 1px); | ||||
|     width: 90%; | ||||
|     background: var(--catch-detail-color); | ||||
|     z-index: 1; | ||||
|     box-sizing: border-box; | ||||
|     border: 2px solid var(--catch-detail-color); | ||||
|     overflow: unset; | ||||
| } | ||||
| 
 | ||||
| .oh-timerange-inner { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     overflow-x: hidden; | ||||
|     justify-content: space-between; | ||||
|     justify-content: center; | ||||
|     align-content: center; | ||||
|     height: 100%; | ||||
|     overflow-y: hidden; | ||||
| } | ||||
| 
 | ||||
| .oh-timerange-inner input { | ||||
|      width: 100%; | ||||
|      box-sizing: border-box; | ||||
|     overflow-x: unset; | ||||
| } | ||||
| 
 | ||||
| .oh-timerange-inner-small { | ||||
|  | @ -144,12 +135,6 @@ | |||
|     height: 100%; | ||||
|     width:100%; | ||||
| } | ||||
| 
 | ||||
| .oh-timerange-inner-small input { | ||||
|     width: min-content; | ||||
|     box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| .oh-delete-range{ | ||||
|     width: 1.5em; | ||||
|     height: 1.5em; | ||||
|  | @ -162,10 +147,6 @@ | |||
|     max-width: 2em; | ||||
| } | ||||
| 
 | ||||
| .oh-timerange-label { | ||||
|     color: white; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /**** Opening hours visualization table ****/ | ||||
| 
 | ||||
|  | @ -190,7 +171,6 @@ | |||
| 
 | ||||
| .ohviz-today .ohviz-range { | ||||
|     border: 1.5px solid black; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| .ohviz-day-off { | ||||
|  | @ -235,70 +215,12 @@ | |||
|     border-radius: 1em; | ||||
| } | ||||
| 
 | ||||
| .ohviz-now { | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     margin: 0; | ||||
|     height: 100%; | ||||
|     border: 1px solid black; | ||||
|     box-sizing: border-box | ||||
| } | ||||
| 
 | ||||
| .ohviz-line { | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     margin: 0; | ||||
|     height: 100%; | ||||
|     border-left: 1px solid #ccc; | ||||
|     border-left: 1px solid #999; | ||||
|     box-sizing: border-box | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .ohviz-time-indication > div { | ||||
|     position: relative; | ||||
|     background-color: white; | ||||
|     left: -50%; | ||||
|     padding-left: 0.3em; | ||||
|     padding-right: 0.3em; | ||||
|     font-size: smaller; | ||||
|     border-radius: 0.3em; | ||||
|     border: 1px solid #ccc; | ||||
|     word-break: initial; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| .ohviz-time-indication { | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     margin: 0; | ||||
|     height: 100%; | ||||
|     box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .ohviz-today { | ||||
|     background-color: var(--subtle-detail-color); | ||||
| } | ||||
| 
 | ||||
| .ohviz-weekday { | ||||
|     padding-left: 0.5em; | ||||
|     word-break: normal; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .ohviz { | ||||
|     border-collapse: collapse; | ||||
| } | ||||
| 
 | ||||
| .ohviz-container { | ||||
|     border: 0.5em solid var(--subtle-detail-color); | ||||
|     border-radius: 1em; | ||||
|     display: block; | ||||
| } | ||||
| 
 | ||||
| .ohviz-closed { | ||||
|     padding: 1em; | ||||
|     background-color: #eee; | ||||
|     border-radius: 1em; | ||||
|     display: block; | ||||
| } | ||||
|  | @ -121,6 +121,7 @@ | |||
|             "zoomInToSeeThisLayer": "Zoom in to see this layer", | ||||
|             "title": "Select layers" | ||||
|         }, | ||||
|         "loadingCountry": "Determining country...", | ||||
|         "weekdays": { | ||||
|             "abbreviations": { | ||||
|                 "monday": "Mon", | ||||
|  |  | |||
							
								
								
									
										19
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										19
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -3,6 +3,11 @@ import SpecialVisualizations from "./UI/SpecialVisualizations"; | |||
| import State from "./State"; | ||||
| import Combine from "./UI/Base/Combine"; | ||||
| import {FixedUiElement} from "./UI/Base/FixedUiElement"; | ||||
| import OpeningHoursVisualization from "./UI/OpeningHours/OpeningHoursVisualization"; | ||||
| import OpeningHoursPickerTable from "./UI/OpeningHours/OpeningHoursPickerTable"; | ||||
| import OpeningHoursPicker from "./UI/OpeningHours/OpeningHoursPicker"; | ||||
| import {OH, OpeningHour} from "./UI/OpeningHours/OpeningHours"; | ||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement"; | ||||
| 
 | ||||
| 
 | ||||
| const tagsSource = new UIEventSource({ | ||||
|  | @ -11,13 +16,23 @@ const tagsSource = new UIEventSource({ | |||
|     surface: 'asphalt', | ||||
|     image: "https://i.imgur.com/kX3rl3v.jpg", | ||||
|     "image:1": "https://i.imgur.com/oHAJqMB.jpg", | ||||
|    // "opening_hours":"mo-fr 09:00-18:00",
 | ||||
|     "opening_hours": "mo-fr 09:00-18:00", | ||||
|     _country: "be", | ||||
| }) | ||||
| 
 | ||||
| const state = new State(undefined) | ||||
| State.state = state | ||||
| 
 | ||||
| const ohData = new UIEventSource<OpeningHour[]>([{ | ||||
|     weekday: 1, | ||||
|     startHour: 10, | ||||
|     startMinutes: 0 | ||||
|     , endHour: 12, | ||||
|     endMinutes: 0 | ||||
| }]) | ||||
| new OpeningHoursPicker(ohData).AttachTo("maindiv") | ||||
| new VariableUiElement(ohData.map(OH.ToString)).AttachTo("extradiv") | ||||
| /* | ||||
| const allSpecials = SpecialVisualizations.specialVisualizations.map(spec => { | ||||
|     try{ | ||||
| 
 | ||||
|  | @ -28,4 +43,4 @@ const allSpecials = SpecialVisualizations.specialVisualizations.map(spec => { | |||
|         return new FixedUiElement("Could not construct "+spec.funcName+" due to "+e).SetClass("alert") | ||||
|     } | ||||
| }) | ||||
| new Combine(allSpecials).AttachTo("maindiv") | ||||
| new Combine(allSpecials).AttachTo("maindiv")*/ | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue