MapComplete/src/UI/InputElement/Helpers/OpeningHours/OHTable.svelte

239 lines
7.2 KiB
Svelte
Raw Normal View History

<script lang="ts">
import { UIEventSource } from "../../../../Logic/UIEventSource"
import type { OpeningHour } from "../../../OpeningHours/OpeningHours"
import { OH as OpeningHours } from "../../../OpeningHours/OpeningHours"
import { Translation } from "../../../i18n/Translation"
import Translations from "../../../i18n/Translations"
import Tr from "../../../Base/Tr.svelte"
import { Utils } from "../../../../Utils"
import { onMount } from "svelte"
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
import OHCell from "./OHCell.svelte"
export let value: UIEventSource<OpeningHour[]>
const wd = Translations.t.general.weekdays.abbreviations
const days: Translation[] = [
wd.monday,
wd.tuesday,
wd.wednesday,
wd.thursday,
wd.friday,
wd.saturday,
wd.sunday,
]
function range(n: number) {
return Utils.TimesT(n, n => n)
}
function clearSelection() {
const allCells = Array.from(document.getElementsByClassName("oh-timecell"))
for (const timecell of allCells) {
timecell.classList.remove("oh-timecell-selected")
}
}
function setSelectionNormalized(weekdayStart: number, weekdayEnd: number, hourStart: number, hourEnd: number) {
for (let wd = weekdayStart; wd <= weekdayEnd; wd++) {
for (let h = (hourStart); h < (hourEnd); h++) {
h = Math.floor(h)
if (h >= hourStart && h < hourEnd) {
const elFull = document.getElementById("oh-full-" + h + "-" + wd)
elFull?.classList?.add("oh-timecell-selected")
}
if (h + 0.5 < hourEnd) {
const elHalf = document.getElementById("oh-half-" + h + "-" + wd)
elHalf?.classList?.add("oh-timecell-selected")
}
}
}
}
function setSelection(weekdayStart: number, weekdayEnd: number, hourStart: number, hourEnd: number) {
let hourA = hourStart
let hourB = hourEnd
if (hourA > hourB) {
hourA = hourEnd - 0.5
hourB = hourStart + 0.5
}
if (hourA == hourB) {
hourA -= 0.5
hourB += 0.5
}
setSelectionNormalized(Math.min(weekdayStart, weekdayEnd), Math.max(weekdayStart, weekdayEnd),
hourA, hourB)
}
let selectionStart: [number, number] = undefined
function startSelection(weekday: number, hour: number) {
selectionStart = [weekday, hour]
}
function endSelection(weekday: number, hour: number) {
if (!selectionStart) {
return
}
setSelection(selectionStart[0], weekday, selectionStart[1], hour + 0.5)
hour += 0.5
let start = Math.min(selectionStart[1], hour)
let end = Math.max(selectionStart[1], hour)
if (selectionStart[1] > hour) {
end += 0.5
start -= 0.5
}
if (end === start) {
end += 0.5
start -= 0.5
}
let startMinutes = Math.round((start * 60) % 60)
let endMinutes = Math.round((end * 60) % 60)
let newOhs = [...value.data]
for (let wd = Math.min(selectionStart[0], weekday); wd <= Math.max(selectionStart[0], weekday); wd++) {
const oh: OpeningHour = {
startHour: Math.floor(start),
endHour: Math.floor(end),
startMinutes,
endMinutes,
weekday: wd,
}
newOhs.push(oh)
}
value.set(OpeningHours.MergeTimes(newOhs))
selectionStart = undefined
clearSelection()
}
function moved(weekday: number, hour: number) {
if (selectionStart) {
clearSelection()
setSelection(selectionStart[0], weekday, selectionStart[1], hour + 0.5)
}
}
let totalHeight = 0
onMount(() => {
requestAnimationFrame(() => {
const mondayMorning = document.getElementById("oh-full-" + 0 + "-" + 0)
const sundayEvening = document.getElementById("oh-half-" + 23 + "-" + 6)
const top = mondayMorning.getBoundingClientRect().top
const bottom = sundayEvening.getBoundingClientRect().bottom
totalHeight = bottom - top
})
})
/**
* Determines 'top' and 'height-attributes, returns a CSS-string'
* @param oh
*/
function rangeStyle(oh: OpeningHour, totalHeight: number): string {
const top = (oh.startHour + oh.startMinutes / 60) * totalHeight / 24
const height = (oh.endHour - oh.startHour + (oh.endMinutes - oh.startMinutes) / 60) * totalHeight / 24
return `top: ${top}px; height: ${height}px; z-index: 20`
}
</script>
<table class="oh-table no-weblate w-full" cellspacing="0" cellpadding="0">
<tr>
<th style="width: 9%">
<!-- Top-left cell -->
<button class="absolute top-0 left-0 p-1 rounded-full" on:click={() => value.set([])} style="z-index: 10">
<TrashIcon class="w-5 h-5" />
</button>
</th>
{#each days as wd}
<th style="width: 13%">
<Tr cls="w-full" t={wd} />
</th>
{/each}
</tr>
<tr class="h-0">
<!-- Virtual row to add the ranges to-->
<td style="width: 9%" />
{#each range(7) as wd}
<td style="width: 13%; position: relative;">
<div class="h-0 pointer-events-none" style="z-index: 10">
{#each $value.filter(oh => oh.weekday === wd) as range }
<div class="absolute pointer-events-none px-1 md:px-2 w-full "
style={rangeStyle(range, totalHeight)}
>
<div class="rounded-xl border-interactive h-full low-interaction flex flex-col justify-between">
<div class:hidden={range.endHour - range.startHour < 3}>
{OpeningHours.hhmm(range.startHour, range.startMinutes)}
</div>
<button class="w-fit rounded-full p-1 self-center pointer-events-auto"
on:click={() => {
const cleaned = value.data.filter(v => !OpeningHours.isSame(v, range))
console.log("Cleaned", cleaned, value.data)
value.set(cleaned)
}}>
<TrashIcon class="w-6 h-6" />
</button>
<div class:hidden={range.endHour - range.startHour < 3}>
{OpeningHours.hhmm(range.endHour, range.endMinutes)}
</div>
</div>
</div>
{/each}
</div>
</td>
{/each}
</tr>
{#each range(24) as h}
<tr style="height: 0.75rem; width: 9%"> <!-- even row, for the hour -->
<td rowspan={ h < 23 ? 2: 1 }
class="relative text-sm sm:text-base oh-left-col oh-timecell-full border-box interactive"
style={ h < 23 ? "top: 0.75rem" : "height:0; top: 0.75rem"}>
{#if h < 23}
{h + 1}:00
{/if}
</td>
{#each range(7) as wd}
<OHCell type="full" {h} {wd} on:start={() => startSelection(wd, h)} on:end={() => endSelection(wd, h)}
on:move={() => moved(wd, h)} on:clear={() => clearSelection()} />
{/each}
</tr>
<tr style="height: 0.75rem"> <!-- odd row, for the half hour -->
{#if h === 23}
<td/>
{/if}
{#each range(7) as wd}
<OHCell type="half" {h} {wd} on:start={() => startSelection(wd, h)} on:end={() => endSelection(wd, h)}
on:move={() => moved(wd, h)} on:clear={() => clearSelection()} />
{/each}
</tr>
{/each}
</table>
<style>
th {
top: 0;
position: sticky;
z-index: 10;
}
</style>