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();
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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,60 +24,106 @@ 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[]>;
|
||||
|
||||
/*
|
||||
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<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;");
|
||||
}
|
||||
|
||||
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 += `<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>';
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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<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 =
|
||||
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";
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
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])
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -79,8 +79,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,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;
|
||||
}
|
|
@ -121,6 +121,7 @@
|
|||
"zoomInToSeeThisLayer": "Zoom in to see this layer",
|
||||
"title": "Select layers"
|
||||
},
|
||||
"loadingCountry": "Determining country...",
|
||||
"weekdays": {
|
||||
"abbreviations": {
|
||||
"monday": "Mon",
|
||||
|
|
27
test.ts
27
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<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…
Reference in a new issue