forked from MapComplete/MapComplete
Fix opening hours input element
This commit is contained in:
parent
94f9a0de56
commit
64ec06bfc8
19 changed files with 643 additions and 599 deletions
|
@ -102,9 +102,13 @@ export default class SimpleMetaTagger {
|
||||||
|
|
||||||
SimpleMetaTagger.GetCountryCodeFor(lon, lat, (countries) => {
|
SimpleMetaTagger.GetCountryCodeFor(lon, lat, (countries) => {
|
||||||
try {
|
try {
|
||||||
|
const oldCountry = feature.properties["_country"];
|
||||||
feature.properties["_country"] = countries[0].trim().toLowerCase();
|
feature.properties["_country"] = countries[0].trim().toLowerCase();
|
||||||
const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id);
|
if (oldCountry !== feature.properties["_country"]) {
|
||||||
tagsSource.ping();
|
const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id);
|
||||||
|
tagsSource.ping();
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,9 @@ export default class Combine extends BaseUIElement {
|
||||||
protected InnerConstructElement(): HTMLElement {
|
protected InnerConstructElement(): HTMLElement {
|
||||||
const el = document.createElement("span")
|
const el = document.createElement("span")
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
|
||||||
for (const subEl of this.uiElements) {
|
for (const subEl of this.uiElements) {
|
||||||
if(subEl === undefined || subEl === null){
|
if(subEl === undefined || subEl === null){
|
||||||
continue;
|
continue;
|
||||||
|
@ -28,6 +31,11 @@ export default class Combine extends BaseUIElement {
|
||||||
el.appendChild(subHtml)
|
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;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,14 @@ export default class Table extends BaseUIElement {
|
||||||
|
|
||||||
private readonly _header: BaseUIElement[];
|
private readonly _header: BaseUIElement[];
|
||||||
private readonly _contents: 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();
|
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));
|
this._contents = contents.map(row => row.map(Translations.W));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,15 +32,23 @@ export default class Table extends BaseUIElement {
|
||||||
table.appendChild(tr)
|
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")
|
const tr = document.createElement("tr")
|
||||||
for (const elem of row) {
|
for (let j = 0; j < row.length; j++){
|
||||||
const htmlElem = elem.ConstructElement()
|
let elem = row[j];
|
||||||
|
const htmlElem = elem?.ConstructElement()
|
||||||
if (htmlElem === undefined) {
|
if (htmlElem === undefined) {
|
||||||
continue;
|
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")
|
const td = document.createElement("td")
|
||||||
|
td.style.cssText = style;
|
||||||
td.appendChild(htmlElem)
|
td.appendChild(htmlElem)
|
||||||
tr.appendChild(td)
|
tr.appendChild(td)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ export class VariableUiElement extends BaseUIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contents === undefined) {
|
if (contents === undefined) {
|
||||||
return
|
return el;
|
||||||
}
|
}
|
||||||
if (typeof contents === "string") {
|
if (typeof contents === "string") {
|
||||||
el.innerHTML = contents
|
el.innerHTML = contents
|
||||||
|
|
|
@ -102,6 +102,8 @@ export default abstract class BaseUIElement {
|
||||||
if(this.InnerConstructElement === undefined){
|
if(this.InnerConstructElement === undefined){
|
||||||
throw "ERROR! This is not a correct baseUIElement: "+this.constructor.name
|
throw "ERROR! This is not a correct baseUIElement: "+this.constructor.name
|
||||||
}
|
}
|
||||||
|
try{
|
||||||
|
|
||||||
|
|
||||||
const el = this.InnerConstructElement();
|
const el = this.InnerConstructElement();
|
||||||
|
|
||||||
|
@ -149,7 +151,13 @@ export default abstract class BaseUIElement {
|
||||||
el.addEventListener('mouseout', () => self._onHover.setData(false));
|
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{
|
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
|
endMinutes: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various utilities manipulating opening hours
|
||||||
|
*/
|
||||||
export class OH {
|
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 {
|
public static startTime(oh: OpeningHour): number {
|
||||||
return oh.startHour + oh.startMinutes / 60;
|
return oh.startHour + oh.startMinutes / 60;
|
||||||
}
|
}
|
||||||
|
@ -348,5 +357,125 @@ export class OH {
|
||||||
|
|
||||||
return ohs;
|
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 {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import OpeningHoursRange from "./OpeningHoursRange";
|
import OpeningHoursRange from "./OpeningHoursRange";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import OpeningHoursPickerTable from "./OpeningHoursPickerTable";
|
import OpeningHoursPickerTable from "./OpeningHoursPickerTable";
|
||||||
|
@ -8,63 +7,39 @@ import {InputElement} from "../Input/InputElement";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export default class OpeningHoursPicker extends InputElement<OpeningHour[]> {
|
export default class OpeningHoursPicker extends InputElement<OpeningHour[]> {
|
||||||
private readonly _ohs: UIEventSource<OpeningHour[]>;
|
|
||||||
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
|
private readonly _ohs: UIEventSource<OpeningHour[]>;
|
||||||
private readonly _backgroundTable: OpeningHoursPickerTable;
|
private readonly _backgroundTable: OpeningHoursPickerTable;
|
||||||
|
|
||||||
private readonly _weekdays: UIEventSource<BaseUIElement[]> = new UIEventSource<BaseUIElement[]>([]);
|
|
||||||
|
|
||||||
constructor(ohs: UIEventSource<OpeningHour[]> = new UIEventSource<OpeningHour[]>([])) {
|
constructor(ohs: UIEventSource<OpeningHour[]> = new UIEventSource<OpeningHour[]>([])) {
|
||||||
super();
|
super();
|
||||||
this._ohs = ohs;
|
this._ohs = ohs;
|
||||||
this._backgroundTable = new OpeningHoursPickerTable(this._weekdays, this._ohs);
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
|
ohs.addCallback(oh => {
|
||||||
this._ohs.addCallback(ohs => {
|
ohs.setData(OH.MergeTimes(oh));
|
||||||
self._ohs.setData(OH.MergeTimes(ohs));
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ohs.addCallbackAndRun(ohs => {
|
this._backgroundTable = new OpeningHoursPickerTable(this._ohs);
|
||||||
const perWeekday: UIElement[][] = [];
|
this._backgroundTable.ConstructElement()
|
||||||
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();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
ohs.ping();
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): BaseUIElement {
|
InnerRender(): BaseUIElement {
|
||||||
return this._backgroundTable;
|
return this._backgroundTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
return this._backgroundTable.ConstructElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
GetValue(): UIEventSource<OpeningHour[]> {
|
GetValue(): UIEventSource<OpeningHour[]> {
|
||||||
return this._ohs
|
return this._ohs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
IsValid(t: OpeningHour[]): boolean {
|
IsValid(t: OpeningHour[]): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected InnerConstructElement(): HTMLElement {
|
||||||
|
return this._backgroundTable.ConstructElement();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -3,19 +3,18 @@
|
||||||
* It will genarate the currently selected opening hour.
|
* It will genarate the currently selected opening hour.
|
||||||
*/
|
*/
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import {OpeningHour} from "./OpeningHours";
|
import {OpeningHour} from "./OpeningHours";
|
||||||
import {InputElement} from "../Input/InputElement";
|
import {InputElement} from "../Input/InputElement";
|
||||||
import Translations from "../i18n/Translations";
|
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[]> {
|
export default class OpeningHoursPickerTable extends InputElement<OpeningHour[]> {
|
||||||
public readonly IsSelected: UIEventSource<boolean>;
|
public static readonly days: Translation[] =
|
||||||
private readonly weekdays: UIEventSource<BaseUIElement[]>;
|
|
||||||
private readonly _element: HTMLTableElement
|
|
||||||
|
|
||||||
public static readonly days: BaseUIElement[] =
|
|
||||||
[
|
[
|
||||||
Translations.t.general.weekdays.abbreviations.monday,
|
Translations.t.general.weekdays.abbreviations.monday,
|
||||||
Translations.t.general.weekdays.abbreviations.tuesday,
|
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.saturday,
|
||||||
Translations.t.general.weekdays.abbreviations.sunday
|
Translations.t.general.weekdays.abbreviations.sunday
|
||||||
]
|
]
|
||||||
|
public readonly IsSelected: UIEventSource<boolean>;
|
||||||
|
|
||||||
private readonly source: UIEventSource<OpeningHour[]>;
|
private readonly source: UIEventSource<OpeningHour[]>;
|
||||||
|
|
||||||
private static _nextId = 0;
|
/*
|
||||||
|
These html-elements are an overlay over the table columns and act as a host for the ranges in the weekdays
|
||||||
|
*/
|
||||||
|
public readonly weekdayElements : HTMLElement[] = Utils.TimesT(7, () => document.createElement("div"))
|
||||||
|
|
||||||
constructor(weekdays: UIEventSource<BaseUIElement[]>, source?: UIEventSource<OpeningHour[]>) {
|
constructor(source?: UIEventSource<OpeningHour[]>) {
|
||||||
super();
|
super();
|
||||||
this.weekdays = weekdays;
|
|
||||||
this.source = source ?? new UIEventSource<OpeningHour[]>([]);
|
this.source = source ?? new UIEventSource<OpeningHour[]>([]);
|
||||||
this.IsSelected = new UIEventSource<boolean>(false);
|
this.IsSelected = new UIEventSource<boolean>(false);
|
||||||
this.SetStyle("width:100%;height:100%;display:block;");
|
this.SetStyle("width:100%;height:100%;display:block;");
|
||||||
|
}
|
||||||
|
|
||||||
|
IsValid(t: OpeningHour[]): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const id = OpeningHoursPickerTable._nextId;
|
GetValue(): UIEventSource<OpeningHour[]> {
|
||||||
OpeningHoursPickerTable._nextId ++ ;
|
return this.source;
|
||||||
|
|
||||||
|
|
||||||
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}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
protected InnerConstructElement(): HTMLElement {
|
||||||
return this._element
|
|
||||||
}
|
|
||||||
|
|
||||||
private InnerUpdate(table: HTMLTableElement) {
|
const table = document.createElement("table")
|
||||||
const self = this;
|
table.classList.add("oh-table")
|
||||||
if (table === undefined || table === null) {
|
|
||||||
return;
|
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 mouseIsDown = false;
|
||||||
let selectionStart: [number, number] = undefined;
|
let selectionStart: [number, number] = undefined;
|
||||||
|
@ -123,6 +168,7 @@ OpeningHoursPickerTable._nextId ++ ;
|
||||||
oh.endMinutes = 0;
|
oh.endMinutes = 0;
|
||||||
}
|
}
|
||||||
self.source.data.push(oh);
|
self.source.data.push(oh);
|
||||||
|
console.log("Created ", oh)
|
||||||
}
|
}
|
||||||
self.source.ping();
|
self.source.ping();
|
||||||
|
|
||||||
|
@ -149,6 +195,7 @@ OpeningHoursPickerTable._nextId ++ ;
|
||||||
};
|
};
|
||||||
|
|
||||||
let lastSelectionIend, lastSelectionJEnd;
|
let lastSelectionIend, lastSelectionJEnd;
|
||||||
|
|
||||||
function selectAllBetween(iEnd, jEnd) {
|
function selectAllBetween(iEnd, jEnd) {
|
||||||
|
|
||||||
if (lastSelectionIend === iEnd && lastSelectionJEnd === jEnd) {
|
if (lastSelectionIend === iEnd && lastSelectionJEnd === jEnd) {
|
||||||
|
@ -263,7 +310,7 @@ OpeningHoursPickerTable._nextId ++ ;
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
for (const k in ev.targetTouches) {
|
for (const k in ev.targetTouches) {
|
||||||
const touch = ev.targetTouches[k];
|
const touch = ev.targetTouches[k];
|
||||||
if(touch.clientX === undefined || touch.clientY === undefined){
|
if (touch.clientX === undefined || touch.clientY === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const elUnderTouch = document.elementFromPoint(
|
const elUnderTouch = document.elementFromPoint(
|
||||||
|
@ -287,15 +334,7 @@ OpeningHoursPickerTable._nextId ++ ;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return table
|
||||||
}
|
|
||||||
|
|
||||||
IsValid(t: OpeningHour[]): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetValue(): UIEventSource<OpeningHour[]> {
|
|
||||||
return this.source;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,73 +1,57 @@
|
||||||
/**
|
/**
|
||||||
* A single opening hours range, shown on top of the OH-picker table
|
* 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 Svg from "../../Svg";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import {OH, OpeningHour} from "./OpeningHours";
|
import {OH, OpeningHour} from "./OpeningHours";
|
||||||
import OpeningHoursPickerTable from "./OpeningHoursPickerTable";
|
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
|
|
||||||
export default class OpeningHoursRange extends UIElement {
|
export default class OpeningHoursRange extends BaseUIElement {
|
||||||
private _oh: UIEventSource<OpeningHour>;
|
private _oh: OpeningHour;
|
||||||
|
|
||||||
private readonly _startTime: BaseUIElement;
|
private readonly _onDelete: () => void;
|
||||||
private readonly _endTime: BaseUIElement;
|
|
||||||
private readonly _deleteRange: BaseUIElement;
|
|
||||||
private readonly _tableId: OpeningHoursPickerTable;
|
|
||||||
|
|
||||||
constructor(oh: UIEventSource<OpeningHour>, tableId: OpeningHoursPickerTable) {
|
constructor(oh: OpeningHour, onDelete: () => void) {
|
||||||
super(oh);
|
super();
|
||||||
this._tableId = tableId;
|
|
||||||
const self = this;
|
|
||||||
this._oh = oh;
|
this._oh = oh;
|
||||||
|
this._onDelete = onDelete;
|
||||||
this.SetClass("oh-timerange");
|
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 {
|
InnerConstructElement(): HTMLElement {
|
||||||
const oh = this._oh.data;
|
|
||||||
if (oh === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const height = this.getHeight();
|
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) {
|
if (height > 2) {
|
||||||
content = [this._startTime, this._deleteRange, this._endTime];
|
content = [startTime, deleteRange, endTime];
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Combine(content)
|
const el = new Combine(content)
|
||||||
.SetClass("oh-timerange-inner")
|
.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 {
|
private getHeight(): number {
|
||||||
const oh = this._oh.data;
|
const oh = this._oh;
|
||||||
|
|
||||||
let endhour = oh.endHour;
|
let endhour = oh.endHour;
|
||||||
if (oh.endHour == 0 && oh.endMinutes == 0) {
|
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));
|
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 editMode = new UIEventSource<boolean>(false);
|
||||||
|
|
||||||
const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration)
|
const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration)
|
||||||
|
answer.SetClass("w-full")
|
||||||
let rendering = answer;
|
let rendering = answer;
|
||||||
|
|
||||||
if (configuration.question !== undefined && State.state?.featureSwitchUserbadge?.data) {
|
if (configuration.question !== undefined && State.state?.featureSwitchUserbadge?.data) {
|
||||||
|
|
|
@ -109,7 +109,6 @@ export default class ShowDataLayer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private postProcessFeature(feature, leafletLayer: L.Layer) {
|
private postProcessFeature(feature, leafletLayer: L.Layer) {
|
||||||
const layer: LayerConfig = this._layerDict[feature._matching_layer_id];
|
const layer: LayerConfig = this._layerDict[feature._matching_layer_id];
|
||||||
if (layer === undefined) {
|
if (layer === undefined) {
|
||||||
|
@ -156,7 +155,7 @@ export default class ShowDataLayer {
|
||||||
if (selected === undefined || self._leafletMap.data === undefined) {
|
if (selected === undefined || self._leafletMap.data === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (popup.isOpen()) {
|
if (leafletLayer.getPopup().isOpen()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selected.properties.id === feature.properties.id) {
|
if (selected.properties.id === feature.properties.id) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import ReviewElement from "./Reviews/ReviewElement";
|
||||||
import MangroveReviews from "../Logic/Web/MangroveReviews";
|
import MangroveReviews from "../Logic/Web/MangroveReviews";
|
||||||
import Translations from "./i18n/Translations";
|
import Translations from "./i18n/Translations";
|
||||||
import ReviewForm from "./Reviews/ReviewForm";
|
import ReviewForm from "./Reviews/ReviewForm";
|
||||||
import OpeningHoursVisualization from "./OpeningHours/OhVisualization";
|
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization";
|
||||||
|
|
||||||
import State from "../State";
|
import State from "../State";
|
||||||
import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
|
import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
|
||||||
|
@ -120,11 +120,7 @@ export default class SpecialVisualizations {
|
||||||
doc: "The tagkey from which the table is constructed."
|
doc: "The tagkey from which the table is constructed."
|
||||||
}],
|
}],
|
||||||
constr: (state: State, tagSource: UIEventSource<any>, args) => {
|
constr: (state: State, tagSource: UIEventSource<any>, args) => {
|
||||||
let keyname = args[0];
|
return new OpeningHoursVisualization(tagSource, args[0])
|
||||||
if (keyname === undefined || keyname === "") {
|
|
||||||
keyname = keyname ?? "opening_hours"
|
|
||||||
}
|
|
||||||
return new OpeningHoursVisualization(tagSource, keyname)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -80,8 +80,6 @@ export abstract class UIElement extends BaseUIElement{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated The method should not be used
|
* @deprecated The method should not be used
|
||||||
*/
|
*/
|
||||||
|
|
8
Utils.ts
8
Utils.ts
|
@ -73,6 +73,14 @@ export class Utils {
|
||||||
return res;
|
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)) {
|
static DoEvery(millis: number, f: (() => void)) {
|
||||||
if (Utils.runningFromConsole) {
|
if (Utils.runningFromConsole) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -103,40 +103,31 @@
|
||||||
border-right: 10px solid var(--catch-detail-color) !important;
|
border-right: 10px solid var(--catch-detail-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.oh-draggable-header {
|
|
||||||
background-color: blue;
|
|
||||||
height: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.oh-timerange {
|
.oh-timerange {
|
||||||
|
color: var(--catch-detail-color-contrast);
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
margin-left: 2px;
|
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: calc(100% - 4px);
|
margin-left: calc(5% - 1px);
|
||||||
|
width: 90%;
|
||||||
background: var(--catch-detail-color);
|
background: var(--catch-detail-color);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
border: 2px solid var(--catch-detail-color);
|
||||||
|
overflow: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timerange-inner {
|
.oh-timerange-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-x: hidden;
|
justify-content: center;
|
||||||
justify-content: space-between;
|
|
||||||
align-content: center;
|
align-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: hidden;
|
overflow-x: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timerange-inner input {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.oh-timerange-inner-small {
|
.oh-timerange-inner-small {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -144,12 +135,6 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width:100%;
|
width:100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timerange-inner-small input {
|
|
||||||
width: min-content;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.oh-delete-range{
|
.oh-delete-range{
|
||||||
width: 1.5em;
|
width: 1.5em;
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
|
@ -162,10 +147,6 @@
|
||||||
max-width: 2em;
|
max-width: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timerange-label {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**** Opening hours visualization table ****/
|
/**** Opening hours visualization table ****/
|
||||||
|
|
||||||
|
@ -190,7 +171,6 @@
|
||||||
|
|
||||||
.ohviz-today .ohviz-range {
|
.ohviz-today .ohviz-range {
|
||||||
border: 1.5px solid black;
|
border: 1.5px solid black;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ohviz-day-off {
|
.ohviz-day-off {
|
||||||
|
@ -235,70 +215,12 @@
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ohviz-now {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
margin: 0;
|
|
||||||
height: 100%;
|
|
||||||
border: 1px solid black;
|
|
||||||
box-sizing: border-box
|
|
||||||
}
|
|
||||||
|
|
||||||
.ohviz-line {
|
.ohviz-line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-left: 1px solid #ccc;
|
border-left: 1px solid #999;
|
||||||
box-sizing: border-box
|
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",
|
"zoomInToSeeThisLayer": "Zoom in to see this layer",
|
||||||
"title": "Select layers"
|
"title": "Select layers"
|
||||||
},
|
},
|
||||||
|
"loadingCountry": "Determining country...",
|
||||||
"weekdays": {
|
"weekdays": {
|
||||||
"abbreviations": {
|
"abbreviations": {
|
||||||
"monday": "Mon",
|
"monday": "Mon",
|
||||||
|
|
27
test.ts
27
test.ts
|
@ -3,21 +3,36 @@ import SpecialVisualizations from "./UI/SpecialVisualizations";
|
||||||
import State from "./State";
|
import State from "./State";
|
||||||
import Combine from "./UI/Base/Combine";
|
import Combine from "./UI/Base/Combine";
|
||||||
import {FixedUiElement} from "./UI/Base/FixedUiElement";
|
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({
|
const tagsSource = new UIEventSource({
|
||||||
id:'id',
|
id: 'id',
|
||||||
name:'name',
|
name: 'name',
|
||||||
surface:'asphalt',
|
surface: 'asphalt',
|
||||||
image: "https://i.imgur.com/kX3rl3v.jpg",
|
image: "https://i.imgur.com/kX3rl3v.jpg",
|
||||||
"image:1": "https://i.imgur.com/oHAJqMB.jpg",
|
"image:1": "https://i.imgur.com/oHAJqMB.jpg",
|
||||||
// "opening_hours":"mo-fr 09:00-18:00",
|
"opening_hours": "mo-fr 09:00-18:00",
|
||||||
_country:"be",
|
_country: "be",
|
||||||
})
|
})
|
||||||
|
|
||||||
const state = new State(undefined)
|
const state = new State(undefined)
|
||||||
State.state = state
|
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 => {
|
const allSpecials = SpecialVisualizations.specialVisualizations.map(spec => {
|
||||||
try{
|
try{
|
||||||
|
|
||||||
|
@ -28,4 +43,4 @@ const allSpecials = SpecialVisualizations.specialVisualizations.map(spec => {
|
||||||
return new FixedUiElement("Could not construct "+spec.funcName+" due to "+e).SetClass("alert")
|
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