forked from MapComplete/MapComplete
parent
379f472adf
commit
44acfd2562
14 changed files with 421 additions and 584 deletions
|
@ -146,12 +146,10 @@ name | doc
|
||||||
------ | -----
|
------ | -----
|
||||||
options | A JSON-object of type `{ prefix: string, postfix: string }`.
|
options | A JSON-object of type `{ prefix: string, postfix: string }`.
|
||||||
|
|
||||||
subarg \| doc
|
| subarg | doc |
|
||||||
-------- \| -----
|
|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
prefix \| Piece of text that will always be added to the front of the generated opening hours. If the OSM-data does not start with this, it will fail to parse.
|
| prefix | Piece of text that will always be added to the front of the generated opening hours. If the OSM-data does not start with this, it will fail to parse. |
|
||||||
postfix \| Piece of text that will always be added to the end of the generated opening hours
|
| postfix | Piece of text that will always be added to the end of the generated opening hours |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Example usage
|
### Example usage
|
||||||
|
|
||||||
|
|
|
@ -78,13 +78,6 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-timecell-0 {
|
|
||||||
border-left: 10px solid rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.oh-timecell-6 {
|
|
||||||
border-right: 10px solid rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.oh-timecol-selected {
|
.oh-timecol-selected {
|
||||||
border-right: var(--catch-detail-color);
|
border-right: var(--catch-detail-color);
|
||||||
|
|
|
@ -3,40 +3,12 @@ import { Utils } from "../../Utils"
|
||||||
export class ThemeMetaTagging {
|
export class ThemeMetaTagging {
|
||||||
public static readonly themeName = "usersettings"
|
public static readonly themeName = "usersettings"
|
||||||
|
|
||||||
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
|
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
|
||||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
|
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
|
||||||
feat.properties._description
|
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' )
|
||||||
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
|
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
|
||||||
?.at(1)
|
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
|
||||||
)
|
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
|
||||||
Utils.AddLazyProperty(
|
feat.properties['__current_backgroun'] = 'initial_value'
|
||||||
feat.properties,
|
|
||||||
"_d",
|
|
||||||
() => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? ""
|
|
||||||
)
|
|
||||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
|
|
||||||
((feat) => {
|
|
||||||
const e = document.createElement("div")
|
|
||||||
e.innerHTML = feat.properties._d
|
|
||||||
return Array.from(e.getElementsByTagName("a")).filter(
|
|
||||||
(a) => a.href.match(/mastodon|en.osm.town/) !== null
|
|
||||||
)[0]?.href
|
|
||||||
})(feat)
|
|
||||||
)
|
|
||||||
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
|
|
||||||
((feat) => {
|
|
||||||
const e = document.createElement("div")
|
|
||||||
e.innerHTML = feat.properties._d
|
|
||||||
return Array.from(e.getElementsByTagName("a")).filter(
|
|
||||||
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
|
|
||||||
)[0]?.href
|
|
||||||
})(feat)
|
|
||||||
)
|
|
||||||
Utils.AddLazyProperty(
|
|
||||||
feat.properties,
|
|
||||||
"_mastodon_candidate",
|
|
||||||
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
|
|
||||||
)
|
|
||||||
feat.properties["__current_backgroun"] = "initial_value"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,104 +0,0 @@
|
||||||
import { InputElement } from "./InputElement"
|
|
||||||
import Translations from "../i18n/Translations"
|
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
|
||||||
import BaseUIElement from "../BaseUIElement"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export class DropDown<T> extends InputElement<T> {
|
|
||||||
private static _nextDropdownId = 0
|
|
||||||
|
|
||||||
private readonly _element: HTMLElement
|
|
||||||
|
|
||||||
private readonly _value: UIEventSource<T>
|
|
||||||
private readonly _values: { value: T; shown: string | BaseUIElement }[]
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* const dropdown = new DropDown<number>("test",[{value: 42, shown: "the answer"}])
|
|
||||||
* dropdown.GetValue().data // => 42
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
label: string | BaseUIElement,
|
|
||||||
values: { value: T; shown: string | BaseUIElement }[],
|
|
||||||
value: UIEventSource<T> = undefined,
|
|
||||||
options?: {
|
|
||||||
select_class?: string
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
super()
|
|
||||||
value = value ?? new UIEventSource<T>(values[0].value)
|
|
||||||
this._value = value
|
|
||||||
this._values = values
|
|
||||||
if (values.length <= 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = DropDown._nextDropdownId
|
|
||||||
DropDown._nextDropdownId++
|
|
||||||
|
|
||||||
const el = document.createElement("form")
|
|
||||||
this._element = el
|
|
||||||
el.id = "dropdown" + id
|
|
||||||
|
|
||||||
{
|
|
||||||
const labelEl = Translations.W(label)?.ConstructElement()
|
|
||||||
if (labelEl !== undefined) {
|
|
||||||
const labelHtml = document.createElement("label")
|
|
||||||
labelHtml.appendChild(labelEl)
|
|
||||||
labelHtml.htmlFor = el.id
|
|
||||||
el.appendChild(labelHtml)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options = options ?? {}
|
|
||||||
options.select_class =
|
|
||||||
options.select_class ?? "w-full bg-indigo-100 p-1 rounded hover:bg-indigo-200"
|
|
||||||
|
|
||||||
{
|
|
||||||
const select = document.createElement("select")
|
|
||||||
select.classList.add(...(options.select_class.split(" ") ?? []))
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
const option = document.createElement("option")
|
|
||||||
option.value = "" + i
|
|
||||||
option.appendChild(Translations.W(values[i].shown).ConstructElement())
|
|
||||||
select.appendChild(option)
|
|
||||||
}
|
|
||||||
el.appendChild(select)
|
|
||||||
|
|
||||||
select.onchange = () => {
|
|
||||||
const index = select.selectedIndex
|
|
||||||
value.setData(values[index].value)
|
|
||||||
}
|
|
||||||
|
|
||||||
value.addCallbackAndRun((selected) => {
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
const value = values[i].value
|
|
||||||
if (value === selected) {
|
|
||||||
select.selectedIndex = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onClick(() => {}) // by registering a click, the click event is consumed and doesn't bubble further to other elements, e.g. checkboxes
|
|
||||||
}
|
|
||||||
|
|
||||||
GetValue(): UIEventSource<T> {
|
|
||||||
return this._value
|
|
||||||
}
|
|
||||||
|
|
||||||
IsValid(t: T): boolean {
|
|
||||||
for (const value of this._values) {
|
|
||||||
if (value.value === t) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
return this._element
|
|
||||||
}
|
|
||||||
}
|
|
64
src/UI/InputElement/Helpers/OpeningHours/OHCell.svelte
Normal file
64
src/UI/InputElement/Helpers/OpeningHours/OHCell.svelte
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a pile of hacks to get the events working on mobile too
|
||||||
|
*/
|
||||||
|
export let wd: number
|
||||||
|
export let h: number
|
||||||
|
export let type: "full" | "half"
|
||||||
|
let dispatch = createEventDispatcher<{ "start", "end", "move","clear" }>()
|
||||||
|
let element: HTMLElement
|
||||||
|
|
||||||
|
function send(signal: "start" | "end" | "move", ev: Event) {
|
||||||
|
ev?.preventDefault()
|
||||||
|
dispatch(signal)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastElement: HTMLElement
|
||||||
|
|
||||||
|
function elementUnderTouch(ev: TouchEvent): HTMLElement {
|
||||||
|
for (const k in ev.targetTouches) {
|
||||||
|
const touch = ev.targetTouches[k]
|
||||||
|
if (touch.clientX === undefined || touch.clientY === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const el = document.elementFromPoint(touch.clientX, touch.clientY)
|
||||||
|
if (!el) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lastElement = <any>el
|
||||||
|
return <any>el
|
||||||
|
}
|
||||||
|
return lastElement
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
element.addEventListener("mousedown", (ev) => send("start", ev))
|
||||||
|
element.onmouseenter = (ev) => send("move", ev)
|
||||||
|
element.onmouseup = (ev) => send("end", ev)
|
||||||
|
|
||||||
|
element.addEventListener("touchstart", ev => dispatch("start", ev))
|
||||||
|
element.addEventListener("touchend", ev => {
|
||||||
|
|
||||||
|
const el = elementUnderTouch(ev)
|
||||||
|
if (el?.onmouseup) {
|
||||||
|
el?.onmouseup(<any>ev)
|
||||||
|
}else{
|
||||||
|
dispatch("clear")
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
element.addEventListener("touchmove", ev => {
|
||||||
|
elementUnderTouch(ev)?.onmouseenter(<any>ev)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<td bind:this={element} id={"oh-"+type+"-"+h+"-"+wd}
|
||||||
|
class:border-black={(h + 1) % 6 === 0}
|
||||||
|
class={`oh-timecell oh-timecell-${type} oh-timecell-${wd} `}
|
||||||
|
/>
|
238
src/UI/InputElement/Helpers/OpeningHours/OHTable.svelte
Normal file
238
src/UI/InputElement/Helpers/OpeningHours/OHTable.svelte
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
<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>
|
|
@ -4,12 +4,39 @@
|
||||||
*/
|
*/
|
||||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||||
import ToSvelte from "../../Base/ToSvelte.svelte"
|
import ToSvelte from "../../Base/ToSvelte.svelte"
|
||||||
import OpeningHoursInput from "../../OpeningHours/OpeningHoursInput"
|
import OpeningHoursInput from "../../OpeningHours/OpeningHoursState"
|
||||||
import PublicHolidaySelector from "../../OpeningHours/PublicHolidaySelector.svelte"
|
import PublicHolidaySelector from "../../OpeningHours/PublicHolidaySelector.svelte"
|
||||||
|
import OHTable from "./OpeningHours/OHTable.svelte"
|
||||||
|
import OpeningHoursState from "../../OpeningHours/OpeningHoursState"
|
||||||
|
import Popup from "../../Base/Popup.svelte"
|
||||||
|
|
||||||
export let value: UIEventSource<string>
|
export let value: UIEventSource<string>
|
||||||
export let phSelectorValue = new UIEventSource("")
|
export let args: string
|
||||||
</script>
|
let prefix = ""
|
||||||
|
let postfix = ""
|
||||||
|
if (args) {
|
||||||
|
try {
|
||||||
|
|
||||||
<ToSvelte construct={new OpeningHoursInput(value, phSelectorValue)} />
|
const data = JSON.stringify(args)
|
||||||
<PublicHolidaySelector value={phSelectorValue}/>
|
if (data["prefix"]) {
|
||||||
|
prefix = data["prefix"]
|
||||||
|
}
|
||||||
|
if (data["postfix"]) {
|
||||||
|
postfix = data["postfix"]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Could not parse arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = new OpeningHoursState(value)
|
||||||
|
let expanded = new UIEventSource(false)
|
||||||
|
</script>
|
||||||
|
<Popup bodyPadding="p-0" shown={expanded}>
|
||||||
|
<OHTable value={state.normalOhs} />
|
||||||
|
<div class="absolute w-full pointer-events-none bottom-0 flex justify-end">
|
||||||
|
<button on:click={() => expanded.set(false)} class="primary pointer-events-auto">Done</button>
|
||||||
|
</div>
|
||||||
|
</Popup>
|
||||||
|
<button on:click={() => expanded.set(true)}>Pick opening hours</button>
|
||||||
|
<PublicHolidaySelector value={state.phSelectorValue} />
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import type { ValidatorType } from "./Validators"
|
import type { ValidatorType } from "./Validators"
|
||||||
import InputHelpers from "./InputHelpers"
|
import InputHelpers from "./InputHelpers"
|
||||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
|
||||||
import type { Feature } from "geojson"
|
import type { Feature } from "geojson"
|
||||||
import ImageHelper from "./Helpers/ImageHelper.svelte"
|
import ImageHelper from "./Helpers/ImageHelper.svelte"
|
||||||
import TranslationInput from "./Helpers/TranslationInput.svelte"
|
import TranslationInput from "./Helpers/TranslationInput.svelte"
|
||||||
|
@ -19,7 +18,6 @@
|
||||||
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
|
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
|
||||||
import SlopeInput from "./Helpers/SlopeInput.svelte"
|
import SlopeInput from "./Helpers/SlopeInput.svelte"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
import WikidataInput from "./Helpers/WikidataInput.svelte"
|
|
||||||
import WikidataInputHelper from "./WikidataInputHelper.svelte"
|
import WikidataInputHelper from "./WikidataInputHelper.svelte"
|
||||||
|
|
||||||
export let type: ValidatorType
|
export let type: ValidatorType
|
||||||
|
@ -48,7 +46,7 @@
|
||||||
{:else if type === "simple_tag"}
|
{:else if type === "simple_tag"}
|
||||||
<SimpleTagInput {value} {args} on:submit />
|
<SimpleTagInput {value} {args} on:submit />
|
||||||
{:else if type === "opening_hours"}
|
{:else if type === "opening_hours"}
|
||||||
<OpeningHoursInput {value} />
|
<OpeningHoursInput {value} {args} />
|
||||||
{:else if type === "slope"}
|
{:else if type === "slope"}
|
||||||
<SlopeInput {value} {feature} {state} />
|
<SlopeInput {value} {feature} {state} />
|
||||||
{:else if type === "wikidata"}
|
{:else if type === "wikidata"}
|
||||||
|
|
|
@ -859,6 +859,9 @@ This list will be sorted
|
||||||
return ranges
|
return ranges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static isSame(a: OpeningHour, b: OpeningHour){
|
||||||
|
return a.weekday === b.weekday && a.startHour === b.startHour && a.startMinutes === b.startMinutes && a.endHour === b.endHour && a.endMinutes === b.endMinutes
|
||||||
|
}
|
||||||
private static multiply(
|
private static multiply(
|
||||||
weekdays: number[],
|
weekdays: number[],
|
||||||
timeranges: {
|
timeranges: {
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
|
||||||
import OpeningHoursPickerTable from "./OpeningHoursPickerTable"
|
|
||||||
import { OH, OpeningHour } from "./OpeningHours"
|
|
||||||
import { InputElement } from "../Input/InputElement"
|
|
||||||
|
|
||||||
export default class OpeningHoursPicker extends InputElement<OpeningHour[]> {
|
|
||||||
private readonly _ohs: UIEventSource<OpeningHour[]>
|
|
||||||
private readonly _backgroundTable: OpeningHoursPickerTable
|
|
||||||
|
|
||||||
constructor(ohs: UIEventSource<OpeningHour[]> = new UIEventSource<OpeningHour[]>([])) {
|
|
||||||
super()
|
|
||||||
this._ohs = ohs
|
|
||||||
|
|
||||||
ohs.addCallback((oh) => {
|
|
||||||
ohs.setData(OH.MergeTimes(oh))
|
|
||||||
})
|
|
||||||
|
|
||||||
this._backgroundTable = new OpeningHoursPickerTable(this._ohs)
|
|
||||||
this._backgroundTable.ConstructElement()
|
|
||||||
}
|
|
||||||
|
|
||||||
GetValue(): UIEventSource<OpeningHour[]> {
|
|
||||||
return this._ohs
|
|
||||||
}
|
|
||||||
|
|
||||||
IsValid(_: OpeningHour[]): boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
return this._backgroundTable.ConstructElement()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,332 +0,0 @@
|
||||||
/**
|
|
||||||
* 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<OpeningHour[]> {
|
|
||||||
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<OpeningHour[]>
|
|
||||||
|
|
||||||
constructor(source?: UIEventSource<OpeningHour[]>) {
|
|
||||||
super()
|
|
||||||
this.source = source ?? new UIEventSource<OpeningHour[]>([])
|
|
||||||
this.SetClass("w-full block")
|
|
||||||
}
|
|
||||||
|
|
||||||
IsValid(_: OpeningHour[]): boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
GetValue(): UIEventSource<OpeningHour[]> {
|
|
||||||
return this.source
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
const table = document.createElement("table")
|
|
||||||
table.classList.add("oh-table")
|
|
||||||
table.classList.add("no-weblate")
|
|
||||||
table.classList.add("relative") // Workaround for webkit-based viewers, see #1019
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -38,7 +38,7 @@ export default class OpeningHoursRange extends BaseUIElement {
|
||||||
})
|
})
|
||||||
|
|
||||||
let content: BaseUIElement
|
let content: BaseUIElement
|
||||||
if (height > 2) {
|
if (height > 3) {
|
||||||
content = new Combine([startTime, deleteRange, endTime]).SetClass(
|
content = new Combine([startTime, deleteRange, endTime]).SetClass(
|
||||||
"flex flex-col h-full justify-between"
|
"flex flex-col h-full justify-between"
|
||||||
)
|
)
|
||||||
|
@ -55,6 +55,10 @@ export default class OpeningHoursRange extends BaseUIElement {
|
||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the relative height, in number of hours to display
|
||||||
|
* Range: ]0 - 24]
|
||||||
|
*/
|
||||||
private getHeight(): number {
|
private getHeight(): number {
|
||||||
const oh = this._oh
|
const oh = this._oh
|
||||||
|
|
||||||
|
|
|
@ -3,30 +3,19 @@
|
||||||
* Keeps track of unparsed rules
|
* Keeps track of unparsed rules
|
||||||
* Exports everything conveniently as a string, for direct use
|
* Exports everything conveniently as a string, for direct use
|
||||||
*/
|
*/
|
||||||
import OpeningHoursPicker from "./OpeningHoursPicker"
|
|
||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
|
||||||
import Combine from "../Base/Combine"
|
|
||||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
|
||||||
import { OH, OpeningHour } from "./OpeningHours"
|
import { OH, OpeningHour } from "./OpeningHours"
|
||||||
import { InputElement } from "../Input/InputElement"
|
|
||||||
import Translations from "../i18n/Translations"
|
|
||||||
import BaseUIElement from "../BaseUIElement"
|
|
||||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
|
||||||
import PublicHolidaySelector from "./PublicHolidaySelector.svelte"
|
|
||||||
|
|
||||||
export default class OpeningHoursInput extends InputElement<string> {
|
export default class OpeningHoursState {
|
||||||
private readonly _value: UIEventSource<string>
|
public readonly normalOhs: UIEventSource<OpeningHour[]>
|
||||||
private readonly _element: BaseUIElement
|
public readonly leftoverRules: Store<string[]>
|
||||||
|
public readonly phSelectorValue: UIEventSource<string>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
value: UIEventSource<string> = new UIEventSource<string>(""),
|
value: UIEventSource<string> = new UIEventSource<string>(""),
|
||||||
phSelectorValue: UIEventSource<string> = new UIEventSource<string>(undefined),
|
|
||||||
prefix = "",
|
prefix = "",
|
||||||
postfix = ""
|
postfix = "",
|
||||||
) {
|
) {
|
||||||
super()
|
|
||||||
this._value = value
|
|
||||||
let valueWithoutPrefix = value
|
let valueWithoutPrefix = value
|
||||||
if (prefix !== "" && postfix !== "") {
|
if (prefix !== "" && postfix !== "") {
|
||||||
valueWithoutPrefix = value.sync(
|
valueWithoutPrefix = value.sync(
|
||||||
|
@ -55,11 +44,11 @@ export default class OpeningHoursInput extends InputElement<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return prefix + noPrefix + postfix
|
return prefix + noPrefix + postfix
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftoverRules: Store<string[]> = valueWithoutPrefix.map((str) => {
|
this.leftoverRules = valueWithoutPrefix.map((str) => {
|
||||||
if (str === undefined) {
|
if (str === undefined) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -89,25 +78,25 @@ export default class OpeningHoursInput extends InputElement<string> {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.phSelectorValue = new UIEventSource<string>(ph ?? "")
|
||||||
|
|
||||||
phSelectorValue.set(ph ?? "")
|
|
||||||
|
|
||||||
// Note: MUST be bound AFTER the leftover rules!
|
// Note: MUST be bound AFTER the leftover rules!
|
||||||
const rulesFromOhPicker: UIEventSource<OpeningHour[]> = valueWithoutPrefix.sync(
|
this.normalOhs = valueWithoutPrefix.sync(
|
||||||
(str) => {
|
(str) => {
|
||||||
return OH.Parse(str)
|
return OH.Parse(str)
|
||||||
},
|
},
|
||||||
[leftoverRules, phSelectorValue],
|
[this.leftoverRules, this.phSelectorValue],
|
||||||
(rules, oldString) => {
|
(rules, oldString) => {
|
||||||
// We always add a ';', to easily add new rules. We remove the ';' again at the end of the function
|
// We always add a ';', to easily add new rules. We remove the ';' again at the end of the function
|
||||||
// Important: spaces are _not_ allowed after a ';' as it'll destabilize the parsing!
|
// Important: spaces are _not_ allowed after a ';' as it'll destabilize the parsing!
|
||||||
let str = OH.ToString(rules) + ";"
|
let str = OH.ToString(rules) + ";"
|
||||||
const ph = phSelectorValue.data
|
const ph = this.phSelectorValue.data
|
||||||
if (ph) {
|
if (ph) {
|
||||||
str += ph + ";"
|
str += ph + ";"
|
||||||
}
|
}
|
||||||
|
|
||||||
str += leftoverRules.data.join(";") + ";"
|
str += this.leftoverRules.data.join(";") + ";"
|
||||||
|
|
||||||
str = str.trim()
|
str = str.trim()
|
||||||
while (str.endsWith(";")) {
|
while (str.endsWith(";")) {
|
||||||
|
@ -122,9 +111,9 @@ export default class OpeningHoursInput extends InputElement<string> {
|
||||||
return oldString // We pass a reference to the old string to stabilize the EventSource
|
return oldString // We pass a reference to the old string to stabilize the EventSource
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
/*
|
||||||
const leftoverWarning = new VariableUiElement(
|
const leftoverWarning = new VariableUiElement(
|
||||||
leftoverRules.map((leftovers: string[]) => {
|
leftoverRules.map((leftovers: string[]) => {
|
||||||
if (leftovers.length == 0) {
|
if (leftovers.length == 0) {
|
||||||
|
@ -137,25 +126,9 @@ export default class OpeningHoursInput extends InputElement<string> {
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
)
|
)*/
|
||||||
|
|
||||||
const ohPicker = new OpeningHoursPicker(rulesFromOhPicker)
|
|
||||||
|
|
||||||
this._element = new Combine([
|
|
||||||
leftoverWarning,
|
|
||||||
ohPicker,
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GetValue(): UIEventSource<string> {
|
|
||||||
return this._value
|
|
||||||
}
|
|
||||||
|
|
||||||
IsValid(_: string): boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
return this._element.ConstructElement()
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,4 +1,40 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import OHTable from "./InputElement/Helpers/OpeningHours/OHTable.svelte"
|
||||||
|
import { UIEventSource } from "../Logic/UIEventSource"
|
||||||
|
import type { OpeningHour } from "./OpeningHours/OpeningHours"
|
||||||
|
export let value: UIEventSource<OpeningHour[]> = new UIEventSource<OpeningHour[]>([
|
||||||
|
{
|
||||||
|
weekday: 3,
|
||||||
|
startMinutes: 0,
|
||||||
|
endMinutes: 0,
|
||||||
|
startHour: 12,
|
||||||
|
endHour: 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
weekday: 0,
|
||||||
|
startMinutes: 0,
|
||||||
|
endMinutes: 0,
|
||||||
|
startHour: 0,
|
||||||
|
endHour: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
weekday: 1,
|
||||||
|
startMinutes: 0,
|
||||||
|
endMinutes: 0,
|
||||||
|
startHour: 1,
|
||||||
|
endHour: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
weekday: 2,
|
||||||
|
startMinutes: 0,
|
||||||
|
endMinutes: 0,
|
||||||
|
startHour: 12,
|
||||||
|
endHour: 24
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main />
|
<main >
|
||||||
|
<OHTable {value}/>
|
||||||
|
</main>
|
||||||
|
|
Loading…
Reference in a new issue