forked from MapComplete/MapComplete
More work on opening hours
This commit is contained in:
parent
9970c4b8bb
commit
d1f286f466
11 changed files with 855 additions and 229 deletions
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 @@
|
|||
/**
|
||||
* 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 {UIElement} from "../../UIElement";
|
||||
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> {
|
||||
public readonly IsSelected: UIEventSource<boolean>;
|
||||
export default class OpeningHoursPicker extends InputElement<OpeningHour[]> {
|
||||
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();
|
||||
this.source = source ?? new UIEventSource<OpeningHour>(undefined);
|
||||
this.IsSelected = new UIEventSource<boolean>(false);
|
||||
this.SetStyle("width:100%;height:100%;display:block;")
|
||||
this._ohs = ohs;
|
||||
this._backgroundTable = new OpeningHoursPickerTable(this._weekdays);
|
||||
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 {
|
||||
let rows = "";
|
||||
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>`;
|
||||
return this._backgroundTable.Render();
|
||||
}
|
||||
|
||||
protected InnerUpdate() {
|
||||
const self = this;
|
||||
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) => {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
GetValue(): UIEventSource<OpeningHour[]> {
|
||||
return this._ohs
|
||||
}
|
||||
|
||||
IsValid(t: OpeningHour): boolean {
|
||||
|
||||
IsValid(t: OpeningHour[]): boolean {
|
||||
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 {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
|
||||
*/
|
||||
export default class OpeningHoursRange extends UIElement{
|
||||
private _parentCell: HTMLElement;
|
||||
constructor(parentCell : HTMLElement) {
|
||||
super();
|
||||
this._parentCell = parentCell;
|
||||
export default class OpeningHoursRange extends UIElement {
|
||||
private _oh: UIEventSource<OpeningHour>;
|
||||
|
||||
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 {
|
||||
this.SetStyle(`display:block;position:absolute;top:0;left:0;width:100%;background:blue;height:${this._parentCell.offsetHeight*2}px`)
|
||||
return "Hi";
|
||||
const oh = this._oh.data;
|
||||
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);
|
||||
private readonly _placeholder: UIElement;
|
||||
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private readonly _isArea: boolean;
|
||||
private readonly _htmlType: string;
|
||||
private readonly _textAreaRows: number;
|
||||
|
||||
private readonly _isValid: (string, country) => boolean;
|
||||
|
@ -17,6 +17,7 @@ export class TextField extends InputElement<string> {
|
|||
placeholder?: string | UIElement,
|
||||
value?: UIEventSource<string>,
|
||||
textArea?: boolean,
|
||||
htmlType?: string,
|
||||
textAreaRows?: number,
|
||||
isValid?: ((s: string, country?: string) => boolean)
|
||||
}) {
|
||||
|
@ -24,7 +25,7 @@ export class TextField extends InputElement<string> {
|
|||
const self = this;
|
||||
this.value = new UIEventSource<string>("");
|
||||
options = options ?? {};
|
||||
this._isArea = options.textArea ?? false;
|
||||
this._htmlType = options.textArea ? "area" : (options.htmlType ?? "text");
|
||||
this.value = options?.value ?? new UIEventSource<string>(undefined);
|
||||
|
||||
this._textAreaRows = options.textAreaRows;
|
||||
|
@ -58,15 +59,15 @@ export class TextField extends InputElement<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>`
|
||||
}
|
||||
|
||||
const placeholder = this._placeholder.InnerRender().replace("'", "'");
|
||||
|
||||
return `<span id="${this.id}"><form onSubmit='return false' class='form-text-field'>` +
|
||||
`<input type='text' placeholder='${placeholder}' id='txt-${this.id}'>` +
|
||||
`</form></span>`;
|
||||
return `<div id="${this.id}"><form onSubmit='return false' class='form-text-field'>` +
|
||||
`<input type='${this._htmlType}' placeholder='${placeholder}' id='txt-${this.id}'/>` +
|
||||
`</form></div>`;
|
||||
}
|
||||
|
||||
InnerUpdate() {
|
||||
|
@ -121,6 +122,9 @@ export class TextField extends InputElement<string> {
|
|||
}
|
||||
|
||||
public SetCursorPosition(i: number) {
|
||||
if(this._htmlType !== "text" && this._htmlType !== "area"){
|
||||
return;
|
||||
}
|
||||
const field = document.getElementById('txt-' + this.id);
|
||||
if(field === undefined || field === null){
|
||||
return;
|
||||
|
|
|
@ -14,7 +14,7 @@ interface TextFieldDef {
|
|||
explanation: string,
|
||||
isValid: ((s: string, country?: string) => boolean),
|
||||
reformat?: ((s: string, country?: string) => string),
|
||||
inputHelper?: (value:UIEventSource<string>) => InputElement<string>
|
||||
inputHelper?: (value:UIEventSource<string>) => InputElement<string>,
|
||||
}
|
||||
|
||||
export default class ValidatedTextField {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue