From 64ec06bfc8efd893f8f48f59c436f1456bc031a1 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 16 Jun 2021 14:23:53 +0200 Subject: [PATCH] Fix opening hours input element --- Logic/SimpleMetaTagger.ts | 8 +- UI/Base/Combine.ts | 8 + UI/Base/Table.ts | 22 +- UI/Base/VariableUIElement.ts | 2 +- UI/BaseUIElement.ts | 10 +- UI/OpeningHours/OhVisualization.ts | 324 ------------------- UI/OpeningHours/OpeningHours.ts | 129 ++++++++ UI/OpeningHours/OpeningHoursPicker.ts | 47 +-- UI/OpeningHours/OpeningHoursPickerTable.ts | 157 +++++---- UI/OpeningHours/OpeningHoursRange.ts | 99 ++---- UI/OpeningHours/OpeningHoursVisualization.ts | 292 +++++++++++++++++ UI/Popup/EditableTagRendering.ts | 1 + UI/ShowDataLayer.ts | 3 +- UI/SpecialVisualizations.ts | 8 +- UI/UIElement.ts | 2 - Utils.ts | 8 + css/openinghourstable.css | 94 +----- langs/en.json | 1 + test.ts | 27 +- 19 files changed, 643 insertions(+), 599 deletions(-) delete mode 100644 UI/OpeningHours/OhVisualization.ts create mode 100644 UI/OpeningHours/OpeningHoursVisualization.ts diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 4d13af724..8e6516c6d 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -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(); - const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id); - tagsSource.ping(); + if (oldCountry !== feature.properties["_country"]) { + const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id); + tagsSource.ping(); + } + } catch (e) { console.warn(e) } diff --git a/UI/Base/Combine.ts b/UI/Base/Combine.ts index 79d4a8f2f..d76c83934 100644 --- a/UI/Base/Combine.ts +++ b/UI/Base/Combine.ts @@ -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; } diff --git a/UI/Base/Table.ts b/UI/Base/Table.ts index f02f8c49d..fe4ee2be7 100644 --- a/UI/Base/Table.ts +++ b/UI/Base/Table.ts @@ -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) } diff --git a/UI/Base/VariableUIElement.ts b/UI/Base/VariableUIElement.ts index c064f9ce6..e7effe17f 100644 --- a/UI/Base/VariableUIElement.ts +++ b/UI/Base/VariableUIElement.ts @@ -18,7 +18,7 @@ export class VariableUiElement extends BaseUIElement { } if (contents === undefined) { - return + return el; } if (typeof contents === "string") { el.innerHTML = contents diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index 1570dd3ea..a919f0609 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -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{ diff --git a/UI/OpeningHours/OhVisualization.ts b/UI/OpeningHours/OhVisualization.ts deleted file mode 100644 index e060d0e6a..000000000 --- a/UI/OpeningHours/OhVisualization.ts +++ /dev/null @@ -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, 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 ? - `${e}` - : "" - ]); - } - - 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( - `
${changeHourText[i]}
` - ) - .SetStyle(`left:${offset}%`) - .SetClass("ohviz-time-indication"); - header.push(el); - } - - rows.push(new Combine([` `, - ``, - new Combine(header), ``])); - - 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( - [`${weekday}`, - ``, - ...innerContent, - ``])) - } - - - return new Combine([ - "", - ...rows.map(el => new Combine(["" ,el , ""])), - "
" - ]).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] - } - -} \ No newline at end of file diff --git a/UI/OpeningHours/OpeningHours.ts b/UI/OpeningHours/OpeningHours.ts index 5164e963e..ede3e87c4 100644 --- a/UI/OpeningHours/OpeningHours.ts +++ b/UI/OpeningHours/OpeningHours.ts @@ -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; + } + } diff --git a/UI/OpeningHours/OpeningHoursPicker.ts b/UI/OpeningHours/OpeningHoursPicker.ts index f38fb2565..a74c41c4d 100644 --- a/UI/OpeningHours/OpeningHoursPicker.ts +++ b/UI/OpeningHours/OpeningHoursPicker.ts @@ -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 { - private readonly _ohs: UIEventSource; public readonly IsSelected: UIEventSource = new UIEventSource(false); - + private readonly _ohs: UIEventSource; private readonly _backgroundTable: OpeningHoursPickerTable; - private readonly _weekdays: UIEventSource = new UIEventSource([]); constructor(ohs: UIEventSource = new UIEventSource([])) { 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(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 { return this._ohs } - IsValid(t: OpeningHour[]): boolean { return true; } + protected InnerConstructElement(): HTMLElement { + return this._backgroundTable.ConstructElement(); + } + } \ No newline at end of file diff --git a/UI/OpeningHours/OpeningHoursPickerTable.ts b/UI/OpeningHours/OpeningHoursPickerTable.ts index cd157aca1..bbdee204f 100644 --- a/UI/OpeningHours/OpeningHoursPickerTable.ts +++ b/UI/OpeningHours/OpeningHoursPickerTable.ts @@ -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 { - public readonly IsSelected: UIEventSource; - private readonly weekdays: UIEventSource; - 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,60 +24,106 @@ export default class OpeningHoursPickerTable extends InputElement Translations.t.general.weekdays.abbreviations.saturday, Translations.t.general.weekdays.abbreviations.sunday ] - - + public readonly IsSelected: UIEventSource; private readonly source: UIEventSource; + + /* + 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")) - private static _nextId = 0; - - constructor(weekdays: UIEventSource, source?: UIEventSource) { + constructor(source?: UIEventSource) { super(); - this.weekdays = weekdays; this.source = source ?? new UIEventSource([]); this.IsSelected = new UIEventSource(false); this.SetStyle("width:100%;height:100%;display:block;"); + } + IsValid(t: OpeningHour[]): boolean { + return true; + } - 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 += `${hs}:00` + - Utils.Times(weekday => ``, 7) + - '' + - Utils.Times(id => ``, 7) + - ''; - } - let days = OpeningHoursPickerTable.days.map((day, i) => { - const innerContent = self.weekdays.data[i]?.ConstructElement()?.innerHTML ?? ""; - return day.ConstructElement().innerHTML + ""+innerContent+""; - }).join(""); - - this._element = document.createElement("table") - const el = this._element; - this.SetClass("oh-table") - el.innerHTML =`${days}${rows}`; + GetValue(): UIEventSource { + return this.source; } protected InnerConstructElement(): HTMLElement { - return this._element - } - private InnerUpdate(table: HTMLTableElement) { - const self = this; - if (table === undefined || table === null) { - return; + 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) } + table.appendChild(headerRow) + + const self = this; + 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; let selectionStart: [number, number] = undefined; @@ -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) { @@ -218,9 +265,9 @@ OpeningHoursPickerTable._nextId ++ ; jStart <= j + offset && j + offset <= jEnd) { cell?.classList?.add("oh-timecell-selected") } else { - cell?.classList?.remove("oh-timecell-selected") + cell?.classList?.remove("oh-timecell-selected") } - + } @@ -263,7 +310,7 @@ OpeningHoursPickerTable._nextId ++ ; ev.preventDefault(); for (const k in ev.targetTouches) { const touch = ev.targetTouches[k]; - if(touch.clientX === undefined || touch.clientY === undefined){ + if (touch.clientX === undefined || touch.clientY === undefined) { continue; } const elUnderTouch = document.elementFromPoint( @@ -287,15 +334,7 @@ OpeningHoursPickerTable._nextId ++ ; } - - } - - IsValid(t: OpeningHour[]): boolean { - return true; - } - - GetValue(): UIEventSource { - return this.source; + return table } } \ No newline at end of file diff --git a/UI/OpeningHours/OpeningHoursRange.ts b/UI/OpeningHours/OpeningHoursRange.ts index 972a4a5b1..24ec3db4d 100644 --- a/UI/OpeningHours/OpeningHoursRange.ts +++ b/UI/OpeningHours/OpeningHoursRange.ts @@ -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; +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, 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 = - Svg.delete_icon_ui() - .SetClass("oh-delete-range") - .onClick(() => { - oh.data.weekday = undefined; - oh.ping(); - }); - - - 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; - } + 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") - let content = [this._deleteRange] + + const deleteRange = + Svg.delete_icon_ui() + .SetClass("oh-delete-range") + .onClick(() => { + this._onDelete() + }); + + + 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"; - - } - } \ No newline at end of file diff --git a/UI/OpeningHours/OpeningHoursVisualization.ts b/UI/OpeningHours/OpeningHoursVisualization.ts new file mode 100644 index 000000000..73d3b08cc --- /dev/null +++ b/UI/OpeningHours/OpeningHoursVisualization.ts @@ -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; + private readonly _key: string; + + constructor(tags: UIEventSource, 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
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)); + } + +} \ No newline at end of file diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts index 373f29ef6..a420c73b8 100644 --- a/UI/Popup/EditableTagRendering.ts +++ b/UI/Popup/EditableTagRendering.ts @@ -18,6 +18,7 @@ export default class EditableTagRendering extends Toggle { const editMode = new UIEventSource(false); const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration) + answer.SetClass("w-full") let rendering = answer; if (configuration.question !== undefined && State.state?.featureSwitchUserbadge?.data) { diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts index d559b7cf2..128a904a4 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer.ts @@ -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) { diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index b1f1a5559..86766c5b4 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -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, args) => { - let keyname = args[0]; - if (keyname === undefined || keyname === "") { - keyname = keyname ?? "opening_hours" - } - return new OpeningHoursVisualization(tagSource, keyname) + return new OpeningHoursVisualization(tagSource, args[0]) } }, diff --git a/UI/UIElement.ts b/UI/UIElement.ts index 565af7830..d1802bcda 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -79,8 +79,6 @@ export abstract class UIElement extends BaseUIElement{ } } - - /** * @deprecated The method should not be used diff --git a/Utils.ts b/Utils.ts index ba18b98d9..a7cbcb3e6 100644 --- a/Utils.ts +++ b/Utils.ts @@ -73,6 +73,14 @@ export class Utils { return res; } + public static TimesT(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; diff --git a/css/openinghourstable.css b/css/openinghourstable.css index 20d76cf29..a18958a5e 100644 --- a/css/openinghourstable.css +++ b/css/openinghourstable.css @@ -103,40 +103,31 @@ 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; + overflow-x: unset; } -.oh-timerange-inner input { - width: 100%; - box-sizing: border-box; - } - .oh-timerange-inner-small { display: flex; flex-direction: row; @@ -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; -} \ No newline at end of file diff --git a/langs/en.json b/langs/en.json index b92fc6e2b..f5cd5594e 100644 --- a/langs/en.json +++ b/langs/en.json @@ -121,6 +121,7 @@ "zoomInToSeeThisLayer": "Zoom in to see this layer", "title": "Select layers" }, + "loadingCountry": "Determining country...", "weekdays": { "abbreviations": { "monday": "Mon", diff --git a/test.ts b/test.ts index ee47136d1..eeb50e7af 100644 --- a/test.ts +++ b/test.ts @@ -3,21 +3,36 @@ 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({ - id:'id', - name:'name', - surface:'asphalt', + id: 'id', + name: 'name', + 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", - _country:"be", + "opening_hours": "mo-fr 09:00-18:00", + _country: "be", }) const state = new State(undefined) State.state = state +const ohData = new UIEventSource([{ + 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") \ No newline at end of file +new Combine(allSpecials).AttachTo("maindiv")*/ \ No newline at end of file