mapcomplete/UI/OpeningHours/OpeningHoursPickerTable.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

332 lines
12 KiB
TypeScript
Raw Normal View History

2020-10-04 01:04:46 +02:00
/**
* 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"
2021-06-16 14:23:53 +02:00
import { Translation } from "../i18n/Translation"
import { FixedUiElement } from "../Base/FixedUiElement"
import { VariableUiElement } from "../Base/VariableUIElement"
import Combine from "../Base/Combine"
import OpeningHoursRange from "./OpeningHoursRange"
2020-10-06 01:37:02 +02:00
export default class OpeningHoursPickerTable extends InputElement<OpeningHour[]> {
2021-06-16 14:23:53 +02:00
public static readonly days: Translation[] = [
2020-10-06 02:09:09 +02:00
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,
]
2021-06-16 14:23:53 +02:00
/*
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")
2022-09-08 21:40:48 +02:00
)
private readonly source: UIEventSource<OpeningHour[]>
2020-10-04 01:04:46 +02:00
2021-06-16 14:23:53 +02:00
constructor(source?: UIEventSource<OpeningHour[]>) {
2021-06-10 01:36:20 +02:00
super()
2020-10-06 01:37:02 +02:00
this.source = source ?? new UIEventSource<OpeningHour[]>([])
2020-10-04 01:04:46 +02:00
this.SetStyle("width:100%;height:100%;display:block;")
2021-06-16 14:23:53 +02:00
}
IsValid(t: OpeningHour[]): boolean {
return true
}
GetValue(): UIEventSource<OpeningHour[]> {
return this.source
}
2020-10-04 01:04:46 +02:00
2021-06-16 14:23:53 +02:00
protected InnerConstructElement(): HTMLElement {
const table = document.createElement("table")
table.classList.add("oh-table")
2022-08-07 21:54:32 +02:00
table.classList.add("relative") // Workaround for webkit-based viewers, see #1019
2021-06-16 16:39:48 +02:00
const cellHeightInPx = 14
2021-06-16 14:23:53 +02:00
const headerRow = document.createElement("tr")
headerRow.appendChild(document.createElement("th"))
2021-06-16 16:39:48 +02:00
headerRow.classList.add("relative")
2021-06-16 14:23:53 +02:00
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())
2021-06-16 14:23:53 +02:00
const fullColumnSpan = this.weekdayElements[i]
fullColumnSpan.classList.add("w-full", "relative")
2021-06-16 16:39:48 +02:00
// 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"
2021-06-16 14:23:53 +02:00
const ranges = new VariableUiElement(
this.source
.map((ohs) => (ohs ?? []).filter((oh: OpeningHour) => oh.weekday === i))
2021-06-16 14:23:53 +02:00
.map((ohsForToday) => {
return new Combine(
ohsForToday.map(
(oh) =>
new OpeningHoursRange(oh, () => {
2021-06-16 14:23:53 +02:00
this.source.data.splice(this.source.data.indexOf(oh), 1)
this.source.ping()
})
2022-09-08 21:40:48 +02:00
)
)
2021-06-16 14:23:53 +02:00
})
)
fullColumnSpan.appendChild(ranges.ConstructElement())
2021-06-16 14:23:53 +02:00
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)
2021-06-16 14:23:53 +02:00
cell.appendChild(fullColumnSpanWrapper)
headerRow.appendChild(cell)
}
2021-06-10 14:05:26 +02:00
2021-06-16 14:23:53 +02:00
table.appendChild(headerRow)
2021-06-10 14:05:26 +02:00
2020-10-04 01:04:46 +02:00
const self = this
for (let h = 0; h < 24; h++) {
2021-06-16 14:23:53 +02:00
const hs = Utils.TwoDigits(h)
const firstCell = document.createElement("td")
firstCell.rowSpan = 2
2021-06-16 16:39:48 +02:00
firstCell.classList.add("oh-left-col", "oh-timecell-full", "border-box")
2021-06-16 14:23:53 +02:00
firstCell.appendChild(new FixedUiElement(hs + ":00").ConstructElement())
2020-10-04 01:04:46 +02:00
2021-06-16 14:23:53 +02:00
const evenRow = document.createElement("tr")
evenRow.appendChild(firstCell)
2020-10-04 01:04:46 +02:00
2021-06-16 14:23:53 +02:00
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"
2021-06-16 16:39:48 +02:00
evenRow.style.maxHeight = evenRow.style.height
evenRow.style.minHeight = evenRow.style.height
2021-06-16 14:23:53 +02:00
table.appendChild(evenRow)
2021-06-10 14:05:26 +02:00
2021-06-16 14:23:53 +02:00
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)
}
2021-06-16 16:39:48 +02:00
oddRow.style.minHeight = evenRow.style.height
oddRow.style.maxHeight = evenRow.style.height
2021-06-16 14:23:53 +02:00
table.appendChild(oddRow)
2020-10-04 01:04:46 +02:00
}
2021-06-16 14:23:53 +02:00
/**** Event handling below ***/
2020-10-04 01:04:46 +02:00
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),
}
2020-10-23 01:20:48 +02:00
if (oh.endHour > 23) {
2020-10-04 01:04:46 +02:00
oh.endHour = 24
oh.endMinutes = 0
}
2020-10-06 01:37:02 +02:00
self.source.data.push(oh)
2020-10-04 01:04:46 +02:00
}
2020-10-06 01:37:02 +02:00
self.source.ping()
2020-10-04 01:04:46 +02:00
// Clear the highlighting
2020-10-23 01:20:48 +02:00
let header = table.rows[0]
for (let j = 1; j < header.cells.length; j++) {
header.cells[j].classList?.remove("oh-timecol-selected")
}
2020-10-04 01:04:46 +02:00
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]
2020-10-21 20:54:15 +02:00
cell?.classList?.remove("oh-timecell-selected")
row.classList?.remove("oh-timerow-selected")
2020-10-04 01:04:46 +02:00
}
}
}
table.onmouseup = () => {
endSelection()
}
table.onmouseleave = () => {
endSelection()
}
2020-10-23 01:20:48 +02:00
let lastSelectionIend, lastSelectionJEnd
2021-06-16 14:23:53 +02:00
2020-10-04 01:04:46 +02:00
function selectAllBetween(iEnd, jEnd) {
2020-10-23 01:20:48 +02:00
if (lastSelectionIend === iEnd && lastSelectionJEnd === jEnd) {
return // We already did this
}
lastSelectionIend = iEnd
lastSelectionJEnd = jEnd
2020-10-04 01:04:46 +02:00
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
}
2020-10-23 01:20:48 +02:00
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")
}
}
2020-10-04 01:04:46 +02:00
for (let i = 1; i < table.rows.length; i++) {
2020-10-23 01:20:48 +02:00
let row = table.rows[i]
if (iStart <= i && i <= iEnd) {
row.classList?.add("oh-timerow-selected")
} else {
row.classList?.remove("oh-timerow-selected")
}
2020-10-04 01:04:46 +02:00
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) {
2020-10-23 01:20:48 +02:00
// This is the first column of a full hour -> This is the time indication (skip)
2020-10-04 01:04:46 +02:00
continue
}
offset = -1
}
2020-10-23 01:20:48 +02:00
2020-10-04 01:04:46 +02:00
if (iStart <= i && i <= iEnd && jStart <= j + offset && j + offset <= jEnd) {
cell?.classList?.add("oh-timecell-selected")
} else {
2021-06-16 14:23:53 +02:00
cell?.classList?.remove("oh-timecell-selected")
2020-10-04 01:04:46 +02:00
}
}
}
}
for (let i = 1; i < table.rows.length; i++) {
let row = table.rows[i]
for (let j = 0; j < row.cells.length; j++) {
2020-10-08 19:03:00 +02:00
let cell = row.cells[j]
2020-10-04 01:04:46 +02:00
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]
2021-06-16 14:23:53 +02:00
if (touch.clientX === undefined || touch.clientY === undefined) {
2020-10-05 20:42:54 +02:00
continue
}
2020-10-04 01:04:46 +02:00
const elUnderTouch = document.elementFromPoint(touch.clientX, touch.clientY)
// @ts-ignore
const f = elUnderTouch.onmouseenter
if (f) {
f()
}
}
}
cell.ontouchend = (ev) => {
ev.preventDefault()
2020-10-05 20:42:54 +02:00
endSelection()
2020-10-04 01:04:46 +02:00
}
}
}
2021-06-16 14:23:53 +02:00
return table
2020-10-04 01:04:46 +02:00
}
}