More work on opening hours
This commit is contained in:
parent
9970c4b8bb
commit
d1f286f466
11 changed files with 855 additions and 229 deletions
|
@ -1,9 +1,111 @@
|
||||||
export interface OpeningHour {
|
export interface OpeningHour {
|
||||||
weekdayStart: number, // 0 is monday, 1 is tuesday, ...
|
weekday: number, // 0 is monday, 1 is tuesday, ...
|
||||||
weekdayEnd: number,
|
|
||||||
startHour: number,
|
startHour: number,
|
||||||
startMinutes: number,
|
startMinutes: number,
|
||||||
endHour: number,
|
endHour: number,
|
||||||
endMinutes: number
|
endMinutes: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class OpeningHourUtils {
|
||||||
|
/**
|
||||||
|
* Merge duplicate opening-hour element in place.
|
||||||
|
* Returns true if something changed
|
||||||
|
* @param ohs
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
public static MergeTimes(ohs: OpeningHour[]): OpeningHour[] {
|
||||||
|
const queue = [...ohs];
|
||||||
|
const newList = [];
|
||||||
|
while (queue.length > 0) {
|
||||||
|
let maybeAdd = queue.pop();
|
||||||
|
|
||||||
|
let doAddEntry = true;
|
||||||
|
if(maybeAdd.weekday == undefined){
|
||||||
|
doAddEntry = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = newList.length - 1; i >= 0 && doAddEntry; i--) {
|
||||||
|
let guard = newList[i];
|
||||||
|
if (maybeAdd.weekday != guard.weekday) {
|
||||||
|
// Not the same day
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OpeningHourUtils.startTimeLiesInRange(maybeAdd, guard) && OpeningHourUtils.endTimeLiesInRange(maybeAdd, guard)) {
|
||||||
|
// Guard fully covers 'maybeAdd': we can safely ignore maybeAdd
|
||||||
|
doAddEntry = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OpeningHourUtils.startTimeLiesInRange(guard, maybeAdd) && OpeningHourUtils.endTimeLiesInRange(guard, maybeAdd)) {
|
||||||
|
// 'maybeAdd' fully covers Guard - the guard is killed
|
||||||
|
newList.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OpeningHourUtils.startTimeLiesInRange(maybeAdd, guard) || OpeningHourUtils.endTimeLiesInRange(maybeAdd, guard)
|
||||||
|
|| OpeningHourUtils.startTimeLiesInRange(guard, maybeAdd) || OpeningHourUtils.endTimeLiesInRange(guard, maybeAdd)) {
|
||||||
|
// At this point, the maybeAdd overlaps the guard: we should extend the guard and retest it
|
||||||
|
newList.splice(i, 1);
|
||||||
|
let startHour = guard.startHour;
|
||||||
|
let startMinutes = guard.startMinutes;
|
||||||
|
if(OpeningHourUtils.startTime(maybeAdd)<OpeningHourUtils.startTime(guard)){
|
||||||
|
startHour = maybeAdd.startHour;
|
||||||
|
startMinutes = maybeAdd.startMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
let endHour = guard.endHour;
|
||||||
|
let endMinutes = guard.endMinutes;
|
||||||
|
if(OpeningHourUtils.endTime(maybeAdd)>OpeningHourUtils.endTime(guard)){
|
||||||
|
endHour = maybeAdd.endHour;
|
||||||
|
endMinutes = maybeAdd.endMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.push({
|
||||||
|
startHour: startHour,
|
||||||
|
startMinutes: startMinutes,
|
||||||
|
endHour:endHour,
|
||||||
|
endMinutes:endMinutes,
|
||||||
|
weekday: guard.weekday
|
||||||
|
});
|
||||||
|
|
||||||
|
doAddEntry = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (doAddEntry) {
|
||||||
|
newList.push(maybeAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New list can only differ from the old list by merging entries
|
||||||
|
// This means that the list is changed only if the lengths are different.
|
||||||
|
// If the lengths are the same, we might just as well return the old list and be a bit more stable
|
||||||
|
if (newList.length !== ohs.length) {
|
||||||
|
return newList;
|
||||||
|
} else {
|
||||||
|
return ohs;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static startTime(oh: OpeningHour): number {
|
||||||
|
return oh.startHour + oh.startMinutes / 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static endTime(oh: OpeningHour): number {
|
||||||
|
return oh.endHour + oh.endMinutes / 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static startTimeLiesInRange(checked: OpeningHour, mightLieIn: OpeningHour) {
|
||||||
|
return OpeningHourUtils.startTime(mightLieIn) <= OpeningHourUtils.startTime(checked) &&
|
||||||
|
OpeningHourUtils.startTime(checked) <= OpeningHourUtils.endTime(mightLieIn)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static endTimeLiesInRange(checked: OpeningHour, mightLieIn: OpeningHour) {
|
||||||
|
return OpeningHourUtils.startTime(mightLieIn) <= OpeningHourUtils.endTime(checked) &&
|
||||||
|
OpeningHourUtils.endTime(checked) <= OpeningHourUtils.endTime(mightLieIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
123
UI/Input/NumberField.ts
Normal file
123
UI/Input/NumberField.ts
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import {UIElement} from "../UIElement";
|
||||||
|
import {InputElement} from "./InputElement";
|
||||||
|
import Translations from "../i18n/Translations";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
|
||||||
|
export class NumberField extends InputElement<number> {
|
||||||
|
private readonly value: UIEventSource<number>;
|
||||||
|
public readonly enterPressed = new UIEventSource<string>(undefined);
|
||||||
|
private readonly _placeholder: UIElement;
|
||||||
|
private options?: {
|
||||||
|
placeholder?: string | UIElement,
|
||||||
|
value?: UIEventSource<number>,
|
||||||
|
isValid?: ((i: number) => boolean),
|
||||||
|
min?: number,
|
||||||
|
max?: number
|
||||||
|
};
|
||||||
|
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
|
private readonly _isValid: (i:number) => boolean;
|
||||||
|
|
||||||
|
constructor(options?: {
|
||||||
|
placeholder?: string | UIElement,
|
||||||
|
value?: UIEventSource<number>,
|
||||||
|
isValid?: ((i:number) => boolean),
|
||||||
|
min?: number,
|
||||||
|
max?:number
|
||||||
|
}) {
|
||||||
|
super(undefined);
|
||||||
|
this.options = options;
|
||||||
|
const self = this;
|
||||||
|
this.value = new UIEventSource<number>(undefined);
|
||||||
|
this.value = options?.value ?? new UIEventSource<number>(undefined);
|
||||||
|
|
||||||
|
this._isValid = options.isValid ?? ((i) => true);
|
||||||
|
|
||||||
|
this._placeholder = Translations.W(options.placeholder ?? "");
|
||||||
|
this.ListenTo(this._placeholder._source);
|
||||||
|
|
||||||
|
this.onClick(() => {
|
||||||
|
self.IsSelected.setData(true)
|
||||||
|
});
|
||||||
|
this.value.addCallback((t) => {
|
||||||
|
const field = document.getElementById("txt-"+this.id);
|
||||||
|
if (field === undefined || field === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
field.className = self.IsValid(t) ? "" : "invalid";
|
||||||
|
|
||||||
|
if (t === undefined || t === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
field.value = t;
|
||||||
|
});
|
||||||
|
this.dumbMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetValue(): UIEventSource<number> {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
InnerRender(): string {
|
||||||
|
|
||||||
|
const placeholder = this._placeholder.InnerRender().replace("'", "'");
|
||||||
|
|
||||||
|
let min = "";
|
||||||
|
if(this.options.min){
|
||||||
|
min = `min='${this.options.min}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let max = "";
|
||||||
|
if(this.options.min){
|
||||||
|
max = `max='${this.options.max}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<span id="${this.id}"><form onSubmit='return false' class='form-text-field'>` +
|
||||||
|
`<input type='number' ${min} ${max} placeholder='${placeholder}' id='txt-${this.id}'>` +
|
||||||
|
`</form></span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
InnerUpdate() {
|
||||||
|
const field = document.getElementById("txt-" + this.id);
|
||||||
|
const self = this;
|
||||||
|
field.oninput = () => {
|
||||||
|
|
||||||
|
// How much characters are on the right, not including spaces?
|
||||||
|
// @ts-ignore
|
||||||
|
const endDistance = field.value.substring(field.selectionEnd).replace(/ /g,'').length;
|
||||||
|
// @ts-ignore
|
||||||
|
let val: number = Number(field.value);
|
||||||
|
if (!self.IsValid(val)) {
|
||||||
|
self.value.setData(undefined);
|
||||||
|
} else {
|
||||||
|
self.value.setData(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.value.data !== undefined && this.value.data !== null) {
|
||||||
|
// @ts-ignore
|
||||||
|
field.value = this.value.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
field.addEventListener("focusin", () => self.IsSelected.setData(true));
|
||||||
|
field.addEventListener("focusout", () => self.IsSelected.setData(false));
|
||||||
|
|
||||||
|
|
||||||
|
field.addEventListener("keyup", function (event) {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
// @ts-ignore
|
||||||
|
self.enterPressed.setData(field.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
IsValid(t: number): boolean {
|
||||||
|
if (t === undefined || t === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return this._isValid(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,215 +1,73 @@
|
||||||
/**
|
import {UIElement} from "../../UIElement";
|
||||||
* This is the base-table which is selectable by hovering over it.
|
|
||||||
* It will genarate the currently selected opening hour.
|
|
||||||
*/
|
|
||||||
import {UIEventSource} from "../../../Logic/UIEventSource";
|
|
||||||
import {Utils} from "../../../Utils";
|
|
||||||
import {OpeningHour} from "../../../Logic/OpeningHours";
|
|
||||||
import {InputElement} from "../InputElement";
|
import {InputElement} from "../InputElement";
|
||||||
|
import {OpeningHour, OpeningHourUtils} from "../../../Logic/OpeningHours";
|
||||||
|
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||||
|
import OpeningHoursPickerTable from "./OpeningHoursPickerTable";
|
||||||
|
import OpeningHoursRange from "./OpeningHoursRange";
|
||||||
|
import Combine from "../../Base/Combine";
|
||||||
|
|
||||||
export default class OpeningHoursPicker extends InputElement<OpeningHour> {
|
export default class OpeningHoursPicker extends InputElement<OpeningHour[]> {
|
||||||
public readonly IsSelected: UIEventSource<boolean>;
|
private readonly _ohs: UIEventSource<OpeningHour[]>;
|
||||||
|
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
|
|
||||||
public static readonly days = ["Maan", "Din", "Woe", "Don", "Vrij", "Zat", "Zon"];
|
private readonly _backgroundTable: OpeningHoursPickerTable;
|
||||||
|
|
||||||
private readonly source: UIEventSource<OpeningHour>;
|
private readonly _weekdays: UIEventSource<UIElement[]> = new UIEventSource<UIElement[]>([]);
|
||||||
|
|
||||||
constructor(source: UIEventSource<OpeningHour> = undefined) {
|
constructor(ohs: UIEventSource<OpeningHour[]>) {
|
||||||
super();
|
super();
|
||||||
this.source = source ?? new UIEventSource<OpeningHour>(undefined);
|
this._ohs = ohs;
|
||||||
this.IsSelected = new UIEventSource<boolean>(false);
|
this._backgroundTable = new OpeningHoursPickerTable(this._weekdays);
|
||||||
this.SetStyle("width:100%;height:100%;display:block;")
|
const self = this;
|
||||||
|
|
||||||
|
this._backgroundTable.GetValue().addCallback(oh => {
|
||||||
|
if (oh) {
|
||||||
|
ohs.data.push(oh);
|
||||||
|
ohs.ping();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._ohs.addCallback(ohs => {
|
||||||
|
self._ohs.setData(OpeningHourUtils.MergeTimes(ohs));
|
||||||
|
})
|
||||||
|
|
||||||
|
ohs.addCallback(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(OpeningHourUtils.MergeTimes(self._ohs.data))
|
||||||
|
})
|
||||||
|
const r = new OpeningHoursRange(source);
|
||||||
|
perWeekday[oh.weekday].push(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
self._weekdays.data[i] = new Combine(perWeekday[i]);
|
||||||
|
}
|
||||||
|
self._weekdays.ping();
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
let rows = "";
|
return this._backgroundTable.Render();
|
||||||
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('<td class="oh-timecell oh-timecell-full"></td>', 7) +
|
|
||||||
'</tr><tr>' +
|
|
||||||
// Utils.Times('<td class="oh-timecell"></td>', 7) +
|
|
||||||
// '</tr><tr>' +
|
|
||||||
Utils.Times('<td class="oh-timecell oh-timecell-half"></td>', 7) +
|
|
||||||
// '</tr><tr>' +
|
|
||||||
// Utils.Times('<td class="oh-timecell"></td>', 7) +
|
|
||||||
'</tr>';
|
|
||||||
}
|
|
||||||
let days = OpeningHoursPicker.days.join("</th><th>");
|
|
||||||
return `<table id="oh-table-${this.id}" class="oh-table"><tr><th></th><th>${days}</tr>${rows}</table>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InnerUpdate() {
|
GetValue(): UIEventSource<OpeningHour[]> {
|
||||||
const self = this;
|
return this._ohs
|
||||||
const table = (document.getElementById(`oh-table-${this.id}`) as HTMLTableElement);
|
|
||||||
if (table === undefined || table === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mouseIsDown = false;
|
|
||||||
let selectionStart: [number, number] = undefined;
|
|
||||||
let selectionEnd: [number, number] = undefined;
|
|
||||||
|
|
||||||
function h(timeSegment: number) {
|
|
||||||
return Math.floor(timeSegment / 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
function m(timeSegment: number) {
|
|
||||||
return (timeSegment % 2) * 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startSelection(i: number, j: number, cell: HTMLElement) {
|
|
||||||
mouseIsDown = true;
|
|
||||||
selectionStart = [i, j];
|
|
||||||
selectionEnd = [i, j];
|
|
||||||
cell.classList.add("oh-timecell-selected")
|
|
||||||
}
|
|
||||||
|
|
||||||
function endSelection() {
|
|
||||||
if (selectionStart === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mouseIsDown = false
|
|
||||||
const dStart = Math.min(selectionStart[1], selectionEnd[1]);
|
|
||||||
const dEnd = Math.max(selectionStart[1], selectionEnd[1]);
|
|
||||||
const timeStart = Math.min(selectionStart[0], selectionEnd[0]) - 1;
|
|
||||||
const timeEnd = Math.max(selectionStart[0], selectionEnd[0]) - 1;
|
|
||||||
const oh: OpeningHour = {
|
|
||||||
weekdayStart: dStart,
|
|
||||||
weekdayEnd: dEnd,
|
|
||||||
startHour: h(timeStart),
|
|
||||||
startMinutes: m(timeStart),
|
|
||||||
endHour: h(timeEnd + 1),
|
|
||||||
endMinutes: m(timeEnd + 1)
|
|
||||||
}
|
|
||||||
self.source.setData(oh);
|
|
||||||
}
|
|
||||||
|
|
||||||
table.onmouseup = () => {
|
|
||||||
endSelection();
|
|
||||||
};
|
|
||||||
table.onmouseleave = () => {
|
|
||||||
endSelection();
|
|
||||||
};
|
|
||||||
|
|
||||||
function selectAllBetween(iEnd, jEnd) {
|
|
||||||
let iStart = selectionStart[0];
|
|
||||||
let jStart = selectionStart[1];
|
|
||||||
|
|
||||||
if (iStart > iEnd) {
|
|
||||||
const h = iStart;
|
|
||||||
iStart = iEnd;
|
|
||||||
iEnd = h;
|
|
||||||
}
|
|
||||||
if (jStart > jEnd) {
|
|
||||||
const h = jStart;
|
|
||||||
jStart = jEnd;
|
|
||||||
jEnd = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 1; i < table.rows.length; i++) {
|
|
||||||
let row = table.rows[i]
|
|
||||||
for (let j = 0; j < row.cells.length; j++) {
|
|
||||||
let cell = row.cells[j]
|
|
||||||
let offset = 0;
|
|
||||||
if (i % 2 == 1) {
|
|
||||||
if (j == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
offset = -1;
|
|
||||||
}
|
|
||||||
if (iStart <= i && i <= iEnd &&
|
|
||||||
jStart <= j + offset && j + offset <= jEnd) {
|
|
||||||
cell.classList.add("oh-timecell-selected")
|
|
||||||
} else {
|
|
||||||
cell.classList.remove("oh-timecell-selected")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 1; i < table.rows.length; i++) {
|
|
||||||
let row = table.rows[i]
|
|
||||||
for (let j = 0; j < row.cells.length; j++) {
|
|
||||||
let cell = row.cells[j]
|
|
||||||
let offset = 0;
|
|
||||||
if (i % 2 == 1) {
|
|
||||||
if (j == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
offset = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
cell.onmousedown = (ev) => {
|
IsValid(t: OpeningHour[]): boolean {
|
||||||
ev.preventDefault();
|
|
||||||
startSelection(i, j + offset, cell)
|
|
||||||
selectAllBetween(i, j + offset);
|
|
||||||
}
|
|
||||||
cell.ontouchstart = (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
startSelection(i, j + offset, cell);
|
|
||||||
selectAllBetween(i, j + offset);
|
|
||||||
}
|
|
||||||
cell.onmouseenter = () => {
|
|
||||||
if (mouseIsDown) {
|
|
||||||
selectionEnd = [i, j + offset];
|
|
||||||
selectAllBetween(i, j + offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
cell.ontouchmove = (ev: TouchEvent) => {
|
|
||||||
|
|
||||||
ev.preventDefault();
|
|
||||||
for (const k in ev.targetTouches) {
|
|
||||||
const touch = ev.targetTouches[k];
|
|
||||||
const elUnderTouch = document.elementFromPoint(
|
|
||||||
touch.screenX,
|
|
||||||
touch.screenY
|
|
||||||
);
|
|
||||||
// @ts-ignore
|
|
||||||
const f = elUnderTouch.onmouseenter;
|
|
||||||
if (f) {
|
|
||||||
f();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
cell.ontouchend = (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
for (const k in ev.targetTouches) {
|
|
||||||
const touch = ev.targetTouches[k];
|
|
||||||
const elUnderTouch = document.elementFromPoint(
|
|
||||||
touch.pageX,
|
|
||||||
touch.pageY
|
|
||||||
);
|
|
||||||
// @ts-ignore
|
|
||||||
const f = elUnderTouch.onmouseup;
|
|
||||||
if (f) {
|
|
||||||
f();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
IsValid(t: OpeningHour): boolean {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GetValue(): UIEventSource<OpeningHour> {
|
|
||||||
return this.source;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
248
UI/Input/OpeningHours/OpeningHoursPickerTable.ts
Normal file
248
UI/Input/OpeningHours/OpeningHoursPickerTable.ts
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
import {InputElement} from "../InputElement";
|
||||||
|
import {OpeningHour} from "../../../Logic/OpeningHours";
|
||||||
|
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||||
|
import {Utils} from "../../../Utils";
|
||||||
|
import {UIElement} from "../../UIElement";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the base-table which is selectable by hovering over it.
|
||||||
|
* It will genarate the currently selected opening hour.
|
||||||
|
*/
|
||||||
|
export default class OpeningHoursPickerTable extends InputElement<OpeningHour> {
|
||||||
|
public readonly IsSelected: UIEventSource<boolean>;
|
||||||
|
private readonly weekdays: UIEventSource<UIElement[]>;
|
||||||
|
|
||||||
|
public static readonly days = ["Maan", "Din", "Woe", "Don", "Vrij", "Zat", "Zon"];
|
||||||
|
|
||||||
|
private readonly source: UIEventSource<OpeningHour>;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(weekdays: UIEventSource<UIElement[]>, source?: UIEventSource<OpeningHour>) {
|
||||||
|
super(weekdays);
|
||||||
|
this.weekdays = weekdays;
|
||||||
|
this.source = source ?? new UIEventSource<OpeningHour>(undefined);
|
||||||
|
this.IsSelected = new UIEventSource<boolean>(false);
|
||||||
|
this.SetStyle("width:100%;height:100%;display:block;");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
InnerRender(): string {
|
||||||
|
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 => {
|
||||||
|
let innerContent = "";
|
||||||
|
if (h == 0) {
|
||||||
|
innerContent = self.weekdays.data[weekday]?.Render() ?? "";
|
||||||
|
}
|
||||||
|
return `<td id="${this.id}-timecell-${weekday}-${h}" class="oh-timecell oh-timecell-full"><div class="oh-timecell-inner"></div>${innerContent}</td>`;
|
||||||
|
}, 7) +
|
||||||
|
'</tr><tr>' +
|
||||||
|
Utils.Times(id => `<td id="${this.id}-timecell-${id}-${h}-30" class="oh-timecell oh-timecell-half"><div class="oh-timecell-inner"></div></td>`, 7) +
|
||||||
|
'</tr>';
|
||||||
|
}
|
||||||
|
let days = OpeningHoursPickerTable.days.join("</th><th>");
|
||||||
|
return `<table id="oh-table-${this.id}" class="oh-table"><tr><th></th><th>${days}</tr>${rows}</table>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InnerUpdate() {
|
||||||
|
const self = this;
|
||||||
|
const table = (document.getElementById(`oh-table-${this.id}`) as HTMLTableElement);
|
||||||
|
if (table === undefined || table === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const uielement of this.weekdays.data) {
|
||||||
|
uielement.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mouseIsDown = false;
|
||||||
|
let selectionStart: [number, number] = undefined;
|
||||||
|
let selectionEnd: [number, number] = undefined;
|
||||||
|
|
||||||
|
function h(timeSegment: number) {
|
||||||
|
return Math.floor(timeSegment / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function m(timeSegment: number) {
|
||||||
|
return (timeSegment % 2) * 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSelection(i: number, j: number) {
|
||||||
|
mouseIsDown = true;
|
||||||
|
selectionStart = [i, j];
|
||||||
|
selectionEnd = [i, j];
|
||||||
|
}
|
||||||
|
|
||||||
|
function endSelection() {
|
||||||
|
if (selectionStart === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mouseIsDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mouseIsDown = false
|
||||||
|
const dStart = Math.min(selectionStart[1], selectionEnd[1]);
|
||||||
|
const dEnd = Math.max(selectionStart[1], selectionEnd[1]);
|
||||||
|
const timeStart = Math.min(selectionStart[0], selectionEnd[0]) - 1;
|
||||||
|
const timeEnd = Math.max(selectionStart[0], selectionEnd[0]) - 1;
|
||||||
|
for (let weekday = dStart; weekday <= dEnd; weekday++) {
|
||||||
|
const oh: OpeningHour = {
|
||||||
|
weekday: weekday,
|
||||||
|
startHour: h(timeStart),
|
||||||
|
startMinutes: m(timeStart),
|
||||||
|
endHour: h(timeEnd + 1),
|
||||||
|
endMinutes: m(timeEnd + 1)
|
||||||
|
}
|
||||||
|
if(oh.endHour > 23){
|
||||||
|
oh.endHour = 24;
|
||||||
|
oh.endMinutes = 0;
|
||||||
|
}
|
||||||
|
self.source.setData(oh);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the highlighting
|
||||||
|
for (let i = 1; i < table.rows.length; i++) {
|
||||||
|
let row = table.rows[i]
|
||||||
|
for (let j = 0; j < row.cells.length; j++) {
|
||||||
|
let cell = row.cells[j]
|
||||||
|
cell?.classList?.remove("oh-timecell-selected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.onmouseup = () => {
|
||||||
|
endSelection();
|
||||||
|
};
|
||||||
|
table.onmouseleave = () => {
|
||||||
|
endSelection();
|
||||||
|
};
|
||||||
|
|
||||||
|
function selectAllBetween(iEnd, jEnd) {
|
||||||
|
let iStart = selectionStart[0];
|
||||||
|
let jStart = selectionStart[1];
|
||||||
|
|
||||||
|
if (iStart > iEnd) {
|
||||||
|
const h = iStart;
|
||||||
|
iStart = iEnd;
|
||||||
|
iEnd = h;
|
||||||
|
}
|
||||||
|
if (jStart > jEnd) {
|
||||||
|
const h = jStart;
|
||||||
|
jStart = jEnd;
|
||||||
|
jEnd = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < table.rows.length; i++) {
|
||||||
|
let row = table.rows[i]
|
||||||
|
for (let j = 0; j < row.cells.length; j++) {
|
||||||
|
let cell = row.cells[j]
|
||||||
|
if (cell === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let offset = 0;
|
||||||
|
if (i % 2 == 1) {
|
||||||
|
if (j == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
offset = -1;
|
||||||
|
}
|
||||||
|
if (iStart <= i && i <= iEnd &&
|
||||||
|
jStart <= j + offset && j + offset <= jEnd) {
|
||||||
|
cell?.classList?.add("oh-timecell-selected")
|
||||||
|
} else {
|
||||||
|
cell?.classList?.remove("oh-timecell-selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < table.rows.length; i++) {
|
||||||
|
let row = table.rows[i]
|
||||||
|
for (let j = 0; j < row.cells.length; j++) {
|
||||||
|
let cell = row.cells[j].getElementsByClassName("oh-timecell-inner")[0] as HTMLElement
|
||||||
|
let offset = 0;
|
||||||
|
if (i % 2 == 1) {
|
||||||
|
if (j == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
offset = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cell.onmousedown = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
startSelection(i, j + offset)
|
||||||
|
selectAllBetween(i, j + offset);
|
||||||
|
}
|
||||||
|
cell.ontouchstart = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
startSelection(i, j + offset);
|
||||||
|
selectAllBetween(i, j + offset);
|
||||||
|
}
|
||||||
|
cell.onmouseenter = () => {
|
||||||
|
if (mouseIsDown) {
|
||||||
|
selectionEnd = [i, j + offset];
|
||||||
|
selectAllBetween(i, j + offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cell.ontouchmove = (ev: TouchEvent) => {
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
for (const k in ev.targetTouches) {
|
||||||
|
const touch = ev.targetTouches[k];
|
||||||
|
const elUnderTouch = document.elementFromPoint(
|
||||||
|
touch.screenX,
|
||||||
|
touch.screenY
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
const f = elUnderTouch.onmouseenter;
|
||||||
|
if (f) {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.ontouchend = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
for (const k in ev.targetTouches) {
|
||||||
|
const touch = ev.targetTouches[k];
|
||||||
|
const elUnderTouch = document.elementFromPoint(
|
||||||
|
touch.pageX,
|
||||||
|
touch.pageY
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
const f = elUnderTouch.onmouseup;
|
||||||
|
if (f) {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
IsValid(t: OpeningHour): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetValue(): UIEventSource<OpeningHour> {
|
||||||
|
return this.source;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,19 +1,178 @@
|
||||||
import {UIElement} from "../../UIElement";
|
import {UIElement} from "../../UIElement";
|
||||||
|
import {UIEventSource} from "../../../Logic/UIEventSource";
|
||||||
|
import {OpeningHour} from "../../../Logic/OpeningHours";
|
||||||
|
import {TextField} from "../TextField";
|
||||||
|
import Combine from "../../Base/Combine";
|
||||||
|
import {Utils} from "../../../Utils";
|
||||||
|
import {FixedUiElement} from "../../Base/FixedUiElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
export default class OpeningHoursRange extends UIElement{
|
export default class OpeningHoursRange extends UIElement {
|
||||||
private _parentCell: HTMLElement;
|
private _oh: UIEventSource<OpeningHour>;
|
||||||
constructor(parentCell : HTMLElement) {
|
|
||||||
super();
|
|
||||||
this._parentCell = parentCell;
|
|
||||||
|
|
||||||
|
private _startTime: TextField;
|
||||||
|
private _endTime: TextField;
|
||||||
|
private _deleteRange: UIElement;
|
||||||
|
|
||||||
|
constructor(oh: UIEventSource<OpeningHour>) {
|
||||||
|
super(oh);
|
||||||
|
const self = this;
|
||||||
|
this._oh = oh;
|
||||||
|
this.SetClass("oh-timerange");
|
||||||
|
oh.addCallbackAndRun(oh => {
|
||||||
|
const el = document.getElementById(this.id) as HTMLElement;
|
||||||
|
self.InnerUpdate(el);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
this._deleteRange = new FixedUiElement("<img src='./assets/delete.svg'>")
|
||||||
|
.SetClass("oh-delete-range")
|
||||||
|
.onClick(() => {
|
||||||
|
oh.data.weekday = undefined;
|
||||||
|
oh.ping();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._startTime = new TextField({
|
||||||
|
value: oh.map(oh => {
|
||||||
|
if (oh) {
|
||||||
|
return Utils.TwoDigits(oh.startHour) + ":" + Utils.TwoDigits(oh.startMinutes);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
htmlType: "time"
|
||||||
|
});
|
||||||
|
|
||||||
|
this._endTime = new TextField({
|
||||||
|
value: oh.map(oh => {
|
||||||
|
if (oh) {
|
||||||
|
if (oh.endHour == 24) {
|
||||||
|
return "00:00";
|
||||||
|
}
|
||||||
|
return Utils.TwoDigits(oh.endHour) + ":" + Utils.TwoDigits(oh.endMinutes);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
htmlType: "time"
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function applyStartTime() {
|
||||||
|
if (self._startTime.GetValue().data === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const spl = self._startTime.GetValue().data.split(":");
|
||||||
|
oh.data.startHour = Number(spl[0]);
|
||||||
|
oh.data.startMinutes = Number(spl[1]);
|
||||||
|
|
||||||
|
if (oh.data.startHour >= oh.data.endHour) {
|
||||||
|
if (oh.data.startMinutes + 10 >= oh.data.endMinutes) {
|
||||||
|
oh.data.endHour = oh.data.startHour + 1;
|
||||||
|
oh.data.endMinutes = oh.data.startMinutes;
|
||||||
|
if (oh.data.endHour > 23) {
|
||||||
|
oh.data.endHour = 24;
|
||||||
|
oh.data.endMinutes = 0;
|
||||||
|
oh.data.startHour = Math.min(oh.data.startHour, 23);
|
||||||
|
oh.data.startMinutes = Math.min(oh.data.startMinutes, 45);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oh.ping();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyEndTime() {
|
||||||
|
if (self._endTime.GetValue().data === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const spl = self._endTime.GetValue().data.split(":");
|
||||||
|
let newEndHour = Number(spl[0]);
|
||||||
|
const newEndMinutes = Number(spl[1]);
|
||||||
|
if (newEndHour == 0 && newEndMinutes == 0) {
|
||||||
|
newEndHour = 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newEndHour == oh.data.endMinutes && newEndMinutes == oh.data.endMinutes) {
|
||||||
|
// NOthing to change
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
oh.data.endHour = newEndHour;
|
||||||
|
oh.data.endMinutes = newEndMinutes;
|
||||||
|
|
||||||
|
oh.ping();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._startTime.GetValue().addCallbackAndRun(startTime => {
|
||||||
|
const spl = startTime.split(":");
|
||||||
|
if (spl[0].startsWith('0') || spl[1].startsWith('0')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyStartTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._endTime.GetValue().addCallbackAndRun(endTime => {
|
||||||
|
const spl = endTime.split(":");
|
||||||
|
if (spl[0].startsWith('0') || spl[1].startsWith('0')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyEndTime()
|
||||||
|
});
|
||||||
|
this._startTime.enterPressed.addCallback(() => {
|
||||||
|
applyStartTime();
|
||||||
|
});
|
||||||
|
this._endTime.enterPressed.addCallbackAndRun(() => {
|
||||||
|
applyEndTime();
|
||||||
|
})
|
||||||
|
|
||||||
|
this._startTime.IsSelected.addCallback(isSelected => {
|
||||||
|
if (!isSelected) {
|
||||||
|
applyStartTime();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._endTime.IsSelected.addCallback(isSelected => {
|
||||||
|
if (!isSelected) {
|
||||||
|
applyEndTime();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
this.SetStyle(`display:block;position:absolute;top:0;left:0;width:100%;background:blue;height:${this._parentCell.offsetHeight*2}px`)
|
const oh = this._oh.data;
|
||||||
return "Hi";
|
if (oh === undefined) {
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
const height = this.getHeight();
|
||||||
|
return new Combine([this._startTime, this._deleteRange, this._endTime])
|
||||||
|
.SetClass(height < 2 ? "oh-timerange-inner-small" : "oh-timerange-inner")
|
||||||
|
.Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHeight(): number {
|
||||||
|
const oh = this._oh.data;
|
||||||
|
|
||||||
|
let endhour = oh.endHour;
|
||||||
|
if (oh.endHour == 0 && oh.endMinutes == 0) {
|
||||||
|
endhour = 24;
|
||||||
|
}
|
||||||
|
const height = (endhour - oh.startHour + ((oh.endMinutes - oh.startMinutes) / 60));
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InnerUpdate(el: HTMLElement) {
|
||||||
|
if (el == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const oh = this._oh.data;
|
||||||
|
if (oh === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const height = this.getHeight();
|
||||||
|
el.style.height = `${height * 200}%`
|
||||||
|
const upperDiff = (oh.startHour + oh.startMinutes / 60);
|
||||||
|
el.style.marginTop = `${2 * upperDiff * el.parentElement.offsetHeight - upperDiff*0.75}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@ export class TextField extends InputElement<string> {
|
||||||
public readonly enterPressed = new UIEventSource<string>(undefined);
|
public readonly enterPressed = new UIEventSource<string>(undefined);
|
||||||
private readonly _placeholder: UIElement;
|
private readonly _placeholder: UIElement;
|
||||||
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
private readonly _isArea: boolean;
|
private readonly _htmlType: string;
|
||||||
private readonly _textAreaRows: number;
|
private readonly _textAreaRows: number;
|
||||||
|
|
||||||
private readonly _isValid: (string, country) => boolean;
|
private readonly _isValid: (string, country) => boolean;
|
||||||
|
@ -17,6 +17,7 @@ export class TextField extends InputElement<string> {
|
||||||
placeholder?: string | UIElement,
|
placeholder?: string | UIElement,
|
||||||
value?: UIEventSource<string>,
|
value?: UIEventSource<string>,
|
||||||
textArea?: boolean,
|
textArea?: boolean,
|
||||||
|
htmlType?: string,
|
||||||
textAreaRows?: number,
|
textAreaRows?: number,
|
||||||
isValid?: ((s: string, country?: string) => boolean)
|
isValid?: ((s: string, country?: string) => boolean)
|
||||||
}) {
|
}) {
|
||||||
|
@ -24,7 +25,7 @@ export class TextField extends InputElement<string> {
|
||||||
const self = this;
|
const self = this;
|
||||||
this.value = new UIEventSource<string>("");
|
this.value = new UIEventSource<string>("");
|
||||||
options = options ?? {};
|
options = options ?? {};
|
||||||
this._isArea = options.textArea ?? false;
|
this._htmlType = options.textArea ? "area" : (options.htmlType ?? "text");
|
||||||
this.value = options?.value ?? new UIEventSource<string>(undefined);
|
this.value = options?.value ?? new UIEventSource<string>(undefined);
|
||||||
|
|
||||||
this._textAreaRows = options.textAreaRows;
|
this._textAreaRows = options.textAreaRows;
|
||||||
|
@ -58,15 +59,15 @@ export class TextField extends InputElement<string> {
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
|
|
||||||
if (this._isArea) {
|
if (this._htmlType === "area") {
|
||||||
return `<span id="${this.id}"><textarea id="txt-${this.id}" class="form-text-field" rows="${this._textAreaRows}" cols="50" style="max-width: 100%; width: 100%; box-sizing: border-box"></textarea></span>`
|
return `<span id="${this.id}"><textarea id="txt-${this.id}" class="form-text-field" rows="${this._textAreaRows}" cols="50" style="max-width: 100%; width: 100%; box-sizing: border-box"></textarea></span>`
|
||||||
}
|
}
|
||||||
|
|
||||||
const placeholder = this._placeholder.InnerRender().replace("'", "'");
|
const placeholder = this._placeholder.InnerRender().replace("'", "'");
|
||||||
|
|
||||||
return `<span id="${this.id}"><form onSubmit='return false' class='form-text-field'>` +
|
return `<div id="${this.id}"><form onSubmit='return false' class='form-text-field'>` +
|
||||||
`<input type='text' placeholder='${placeholder}' id='txt-${this.id}'>` +
|
`<input type='${this._htmlType}' placeholder='${placeholder}' id='txt-${this.id}'/>` +
|
||||||
`</form></span>`;
|
`</form></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerUpdate() {
|
InnerUpdate() {
|
||||||
|
@ -121,6 +122,9 @@ export class TextField extends InputElement<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SetCursorPosition(i: number) {
|
public SetCursorPosition(i: number) {
|
||||||
|
if(this._htmlType !== "text" && this._htmlType !== "area"){
|
||||||
|
return;
|
||||||
|
}
|
||||||
const field = document.getElementById('txt-' + this.id);
|
const field = document.getElementById('txt-' + this.id);
|
||||||
if(field === undefined || field === null){
|
if(field === undefined || field === null){
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface TextFieldDef {
|
||||||
explanation: string,
|
explanation: string,
|
||||||
isValid: ((s: string, country?: string) => boolean),
|
isValid: ((s: string, country?: string) => boolean),
|
||||||
reformat?: ((s: string, country?: string) => string),
|
reformat?: ((s: string, country?: string) => string),
|
||||||
inputHelper?: (value:UIEventSource<string>) => InputElement<string>
|
inputHelper?: (value:UIEventSource<string>) => InputElement<string>,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ValidatedTextField {
|
export default class ValidatedTextField {
|
||||||
|
|
12
Utils.ts
12
Utils.ts
|
@ -32,10 +32,17 @@ export class Utils {
|
||||||
return str.substr(0, 1).toUpperCase() + str.substr(1);
|
return str.substr(0, 1).toUpperCase() + str.substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Times(str: string, count: number): string {
|
public static TwoDigits(i: number) {
|
||||||
|
if (i < 10) {
|
||||||
|
return "0" + i;
|
||||||
|
}
|
||||||
|
return "" + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Times(f: ((i: number) => string), count: number): string {
|
||||||
let res = "";
|
let res = "";
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
res += str;
|
res += f(i);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -194,4 +201,5 @@ This is around ${secsPerCS} seconds/changeset.<br/> The next million (still ${st
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,33 +15,35 @@
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timecell:hover {
|
.oh-timecell-inner:hover {
|
||||||
background-color: lightsalmon !important;
|
background-color: #ffd1be;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timecell {
|
.oh-timecell {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-left: 1px solid #eee;
|
border-left: 1px solid #eee;
|
||||||
border-right: 1px solid #eee;
|
border-right: 1px solid #eee;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timecell-selected {
|
.oh-timecell-selected {
|
||||||
background-color: red;
|
background-color: orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timecell-half {
|
.oh-timecell-half .oh-timecell-inner{
|
||||||
border-top: 0.5px solid #eee
|
border-top: 0.5px solid #eee
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timecell-half.oh-timecell-selected {
|
.oh-timecell-half.oh-timecell-selected .oh-timecell-inner {
|
||||||
border-top: 0.5px solid lightsalmon;
|
border-top: 0.5px solid lightsalmon;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timecell-full {
|
.oh-timecell-full .oh-timecell-inner{
|
||||||
border-top: 1px solid #aaa
|
border-top: 1px solid #ccc
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timecell-full.oh-timecell-selected {
|
.oh-timecell-full.oh-timecell-selected .oh-timecell-inner {
|
||||||
border-top: 1px solid lightsalmon;
|
border-top: 1px solid lightsalmon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,3 +57,70 @@
|
||||||
background: #ddd;
|
background: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.oh-draggable-header {
|
||||||
|
background-color: blue;
|
||||||
|
height: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-timecell-inner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-timerange {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
margin: 2px;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: orange;
|
||||||
|
z-index: 1;
|
||||||
|
box-sizing: border-box
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-timerange-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-timerange-inner input {
|
||||||
|
width: calc(100% - 2em);
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right:1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-timerange-inner-small {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-timerange-inner-small input {
|
||||||
|
width: min-content;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right:1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-delete-range{
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
background:black;
|
||||||
|
border-radius:0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-delete-range img {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 2em;
|
||||||
|
}
|
10
test.ts
10
test.ts
|
@ -1,8 +1,11 @@
|
||||||
/*
|
//*
|
||||||
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
||||||
import OpeningHoursPicker from "./UI/Input/OpeningHoursPicker";
|
import OpeningHoursRange from "./UI/Input/OpeningHours/OpeningHoursRange";
|
||||||
|
import {UIEventSource} from "./Logic/UIEventSource";
|
||||||
|
import OpeningHoursPicker from "./UI/Input/OpeningHours/OpeningHoursPicker";
|
||||||
|
import {OpeningHour} from "./Logic/OpeningHours";
|
||||||
|
|
||||||
let oh = new OpeningHoursPicker();
|
let oh = new OpeningHoursPicker(new UIEventSource<OpeningHour[]>([]));
|
||||||
oh.SetStyle("height:100vh;display:block;").AttachTo('maindiv');
|
oh.SetStyle("height:100vh;display:block;").AttachTo('maindiv');
|
||||||
|
|
||||||
oh.GetValue().addCallback(data => console.log(data))
|
oh.GetValue().addCallback(data => console.log(data))
|
||||||
|
@ -15,6 +18,7 @@ new VariableUiElement(oh.GetValue().map(oh => {
|
||||||
oh.weekdayEnd + " " + oh.endHour + ":" + oh.endMinutes
|
oh.weekdayEnd + " " + oh.endHour + ":" + oh.endMinutes
|
||||||
})).AttachTo("extradiv");
|
})).AttachTo("extradiv");
|
||||||
|
|
||||||
|
|
||||||
/*/
|
/*/
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {TagRenderingOptions} from "../Customizations/TagRenderingOptions";
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
import {TagRendering} from "../UI/TagRendering";
|
import {TagRendering} from "../UI/TagRendering";
|
||||||
import {Basemap} from "../Logic/Leaflet/Basemap";
|
import {Basemap} from "../Logic/Leaflet/Basemap";
|
||||||
|
import {OpeningHour, OpeningHourUtils} from "../Logic/OpeningHours";
|
||||||
|
|
||||||
|
|
||||||
new T([
|
new T([
|
||||||
|
@ -121,5 +122,55 @@ new T([
|
||||||
equal(true, rendered.indexOf("Niet toegankelijk") > 0)
|
equal(true, rendered.indexOf("Niet toegankelijk") > 0)
|
||||||
|
|
||||||
}
|
}
|
||||||
],
|
], [
|
||||||
|
"Merge touching opening hours",
|
||||||
|
() => {
|
||||||
|
const oh1: OpeningHour = {
|
||||||
|
weekday: 0,
|
||||||
|
startHour: 10,
|
||||||
|
startMinutes: 0,
|
||||||
|
endHour: 11,
|
||||||
|
endMinutes: 0
|
||||||
|
};
|
||||||
|
const oh0: OpeningHour = {
|
||||||
|
weekday: 0,
|
||||||
|
startHour: 11,
|
||||||
|
startMinutes: 0,
|
||||||
|
endHour: 12,
|
||||||
|
endMinutes: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const merged = OpeningHourUtils.MergeTimes([oh0, oh1]);
|
||||||
|
const r = merged[0];
|
||||||
|
equal( merged.length, 1);
|
||||||
|
equal(r.startHour,10 );
|
||||||
|
equal(r.endHour, 12)
|
||||||
|
|
||||||
|
}
|
||||||
|
], [
|
||||||
|
"Merge overlapping opening hours",
|
||||||
|
() => {
|
||||||
|
const oh1: OpeningHour = {
|
||||||
|
weekday: 0,
|
||||||
|
startHour: 10,
|
||||||
|
startMinutes: 0,
|
||||||
|
endHour: 11,
|
||||||
|
endMinutes: 0
|
||||||
|
};
|
||||||
|
const oh0: OpeningHour = {
|
||||||
|
weekday: 0,
|
||||||
|
startHour: 10,
|
||||||
|
startMinutes: 30,
|
||||||
|
endHour: 12,
|
||||||
|
endMinutes: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const merged = OpeningHourUtils.MergeTimes([oh0, oh1]);
|
||||||
|
const r = merged[0];
|
||||||
|
equal( merged.length, 1);
|
||||||
|
equal(r.startHour,10 );
|
||||||
|
equal(r.endHour, 12)
|
||||||
|
|
||||||
|
}
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
|
|
Loading…
Reference in a new issue