/** * 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 "./OpeningHours"; import {InputElement} from "../Input/InputElement"; import Translations from "../i18n/Translations"; import {Translation} from "../i18n/Translation"; import {FixedUiElement} from "../Base/FixedUiElement"; import {VariableUiElement} from "../Base/VariableUIElement"; import Combine from "../Base/Combine"; import OpeningHoursRange from "./OpeningHoursRange"; export default class OpeningHoursPickerTable extends InputElement { public static readonly days: 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 ] /* These html-elements are an overlay over the table columns and act as a host for the ranges in the weekdays */ public readonly weekdayElements: HTMLElement[] = Utils.TimesT(7, () => document.createElement("div")) private readonly source: UIEventSource; constructor(source?: UIEventSource) { super(); this.source = source ?? new UIEventSource([]); this.SetStyle("width:100%;height:100%;display:block;"); } IsValid(t: OpeningHour[]): boolean { return true; } GetValue(): UIEventSource { return this.source; } protected InnerConstructElement(): HTMLElement { const table = document.createElement("table") table.classList.add("oh-table") const cellHeightInPx = 14; const headerRow = document.createElement("tr") headerRow.appendChild(document.createElement("th")) headerRow.classList.add("relative") 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", "relative") // We need to round! The table height is rounded as following, we use this to calculate the actual number of pixels afterwards fullColumnSpan.style.height = ((cellHeightInPx) * 48) + "px" 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") 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) } evenRow.style.height = (cellHeightInPx) + "px"; evenRow.style.maxHeight = evenRow.style.height; evenRow.style.minHeight = evenRow.style.height; 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) } oddRow.style.minHeight = evenRow.style.height; oddRow.style.maxHeight = evenRow.style.height; table.appendChild(oddRow) } /**** Event handling below ***/ 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.data.push(oh); } self.source.ping(); // Clear the highlighting let header = table.rows[0]; for (let j = 1; j < header.cells.length; j++) { header.cells[j].classList?.remove("oh-timecol-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] cell?.classList?.remove("oh-timecell-selected"); row.classList?.remove("oh-timerow-selected"); } } } table.onmouseup = () => { endSelection(); }; table.onmouseleave = () => { endSelection(); }; let lastSelectionIend, lastSelectionJEnd; function selectAllBetween(iEnd, jEnd) { if (lastSelectionIend === iEnd && lastSelectionJEnd === jEnd) { return; // We already did this } lastSelectionIend = iEnd; lastSelectionJEnd = 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; } let header = table.rows[0]; for (let j = 1; j < header.cells.length; j++) { let cell = header.cells[j] cell.classList?.remove("oh-timecol-selected-round-left"); cell.classList?.remove("oh-timecol-selected-round-right"); if (jStart + 1 <= j && j <= jEnd + 1) { cell.classList?.add("oh-timecol-selected") if (jStart + 1 == j) { cell.classList?.add("oh-timecol-selected-round-left"); } if (jEnd + 1 == j) { cell.classList?.add("oh-timecol-selected-round-right"); } } else { cell.classList?.remove("oh-timecol-selected") } } for (let i = 1; i < table.rows.length; i++) { let row = table.rows[i]; if (iStart <= i && i <= iEnd) { row.classList?.add("oh-timerow-selected") } else { row.classList?.remove("oh-timerow-selected") } 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) { // This is the first column of a full hour -> This is the time indication (skip) 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) 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]; if (touch.clientX === undefined || touch.clientY === undefined) { continue; } const elUnderTouch = document.elementFromPoint( touch.clientX, touch.clientY ); // @ts-ignore const f = elUnderTouch.onmouseenter; if (f) { f(); } } } cell.ontouchend = (ev) => { ev.preventDefault(); endSelection(); } } } return table } }