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 }`.  
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
postfix \| Piece of text that will always be added to the end of the generated opening hours
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
| 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. |
 | 
			
		||||
| postfix | Piece of text that will always be added to the end of the generated opening hours                                                                     |
 | 
			
		||||
 | 
			
		||||
### Example usage 
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,13 +78,6 @@
 | 
			
		|||
    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 {
 | 
			
		||||
    border-right: var(--catch-detail-color);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,40 +3,12 @@ import { Utils } from "../../Utils"
 | 
			
		|||
export class ThemeMetaTagging {
 | 
			
		||||
   public static readonly themeName = "usersettings"
 | 
			
		||||
 | 
			
		||||
    public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
 | 
			
		||||
        Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
 | 
			
		||||
            feat.properties._description
 | 
			
		||||
                .match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
 | 
			
		||||
                ?.at(1)
 | 
			
		||||
        )
 | 
			
		||||
        Utils.AddLazyProperty(
 | 
			
		||||
            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"
 | 
			
		||||
   public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
 | 
			
		||||
      Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) ) 
 | 
			
		||||
      Utils.AddLazyProperty(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 ToSvelte from "../../Base/ToSvelte.svelte"
 | 
			
		||||
  import OpeningHoursInput from "../../OpeningHours/OpeningHoursInput"
 | 
			
		||||
  import OpeningHoursInput from "../../OpeningHours/OpeningHoursState"
 | 
			
		||||
  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 phSelectorValue = new UIEventSource("")
 | 
			
		||||
</script>
 | 
			
		||||
  export let args: string
 | 
			
		||||
  let prefix = ""
 | 
			
		||||
  let postfix = ""
 | 
			
		||||
  if (args) {
 | 
			
		||||
    try {
 | 
			
		||||
 | 
			
		||||
<ToSvelte construct={new OpeningHoursInput(value, phSelectorValue)} />
 | 
			
		||||
<PublicHolidaySelector value={phSelectorValue}/>
 | 
			
		||||
      const data = JSON.stringify(args)
 | 
			
		||||
      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 type { ValidatorType } from "./Validators"
 | 
			
		||||
  import InputHelpers from "./InputHelpers"
 | 
			
		||||
  import ToSvelte from "../Base/ToSvelte.svelte"
 | 
			
		||||
  import type { Feature } from "geojson"
 | 
			
		||||
  import ImageHelper from "./Helpers/ImageHelper.svelte"
 | 
			
		||||
  import TranslationInput from "./Helpers/TranslationInput.svelte"
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +18,6 @@
 | 
			
		|||
  import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
 | 
			
		||||
  import SlopeInput from "./Helpers/SlopeInput.svelte"
 | 
			
		||||
  import type { SpecialVisualizationState } from "../SpecialVisualization"
 | 
			
		||||
  import WikidataInput from "./Helpers/WikidataInput.svelte"
 | 
			
		||||
  import WikidataInputHelper from "./WikidataInputHelper.svelte"
 | 
			
		||||
 | 
			
		||||
  export let type: ValidatorType
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +46,7 @@
 | 
			
		|||
{:else if type === "simple_tag"}
 | 
			
		||||
  <SimpleTagInput {value} {args} on:submit />
 | 
			
		||||
{:else if type === "opening_hours"}
 | 
			
		||||
  <OpeningHoursInput {value} />
 | 
			
		||||
  <OpeningHoursInput {value} {args} />
 | 
			
		||||
{:else if type === "slope"}
 | 
			
		||||
  <SlopeInput {value} {feature} {state} />
 | 
			
		||||
{:else if type === "wikidata"}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -859,6 +859,9 @@ This list will be sorted
 | 
			
		|||
        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(
 | 
			
		||||
        weekdays: number[],
 | 
			
		||||
        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
 | 
			
		||||
        if (height > 2) {
 | 
			
		||||
        if (height > 3) {
 | 
			
		||||
            content = new Combine([startTime, deleteRange, endTime]).SetClass(
 | 
			
		||||
                "flex flex-col h-full justify-between"
 | 
			
		||||
            )
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +55,10 @@ export default class OpeningHoursRange extends BaseUIElement {
 | 
			
		|||
        return el
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the relative height, in number of hours to display
 | 
			
		||||
     * Range: ]0 - 24]
 | 
			
		||||
     */
 | 
			
		||||
    private getHeight(): number {
 | 
			
		||||
        const oh = this._oh
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,30 +3,19 @@
 | 
			
		|||
 * Keeps track of unparsed rules
 | 
			
		||||
 * Exports everything conveniently as a string, for direct use
 | 
			
		||||
 */
 | 
			
		||||
import OpeningHoursPicker from "./OpeningHoursPicker"
 | 
			
		||||
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 { 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> {
 | 
			
		||||
    private readonly _value: UIEventSource<string>
 | 
			
		||||
    private readonly _element: BaseUIElement
 | 
			
		||||
export default class OpeningHoursState {
 | 
			
		||||
    public readonly normalOhs: UIEventSource<OpeningHour[]>
 | 
			
		||||
    public readonly leftoverRules: Store<string[]>
 | 
			
		||||
    public readonly phSelectorValue: UIEventSource<string>
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        value: UIEventSource<string> = new UIEventSource<string>(""),
 | 
			
		||||
        phSelectorValue: UIEventSource<string> = new UIEventSource<string>(undefined),
 | 
			
		||||
        prefix = "",
 | 
			
		||||
        postfix = ""
 | 
			
		||||
        postfix = "",
 | 
			
		||||
    ) {
 | 
			
		||||
        super()
 | 
			
		||||
        this._value = value
 | 
			
		||||
        let valueWithoutPrefix = value
 | 
			
		||||
        if (prefix !== "" && postfix !== "") {
 | 
			
		||||
            valueWithoutPrefix = value.sync(
 | 
			
		||||
| 
						 | 
				
			
			@ -55,11 +44,11 @@ export default class OpeningHoursInput extends InputElement<string> {
 | 
			
		|||
                    }
 | 
			
		||||
 | 
			
		||||
                    return prefix + noPrefix + postfix
 | 
			
		||||
                }
 | 
			
		||||
                },
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const leftoverRules: Store<string[]> = valueWithoutPrefix.map((str) => {
 | 
			
		||||
        this.leftoverRules = valueWithoutPrefix.map((str) => {
 | 
			
		||||
            if (str === undefined) {
 | 
			
		||||
                return []
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -89,25 +78,25 @@ export default class OpeningHoursInput extends InputElement<string> {
 | 
			
		|||
                break
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.phSelectorValue = new UIEventSource<string>(ph ?? "")
 | 
			
		||||
 | 
			
		||||
        phSelectorValue.set(ph ?? "")
 | 
			
		||||
 | 
			
		||||
        // Note: MUST be bound AFTER the leftover rules!
 | 
			
		||||
        const rulesFromOhPicker: UIEventSource<OpeningHour[]> = valueWithoutPrefix.sync(
 | 
			
		||||
        this.normalOhs = valueWithoutPrefix.sync(
 | 
			
		||||
            (str) => {
 | 
			
		||||
                return OH.Parse(str)
 | 
			
		||||
            },
 | 
			
		||||
            [leftoverRules, phSelectorValue],
 | 
			
		||||
            [this.leftoverRules, this.phSelectorValue],
 | 
			
		||||
            (rules, oldString) => {
 | 
			
		||||
                // 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!
 | 
			
		||||
                let str = OH.ToString(rules) + ";"
 | 
			
		||||
                const ph = phSelectorValue.data
 | 
			
		||||
                const ph = this.phSelectorValue.data
 | 
			
		||||
                if (ph) {
 | 
			
		||||
                    str += ph + ";"
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                str += leftoverRules.data.join(";") + ";"
 | 
			
		||||
                str += this.leftoverRules.data.join(";") + ";"
 | 
			
		||||
 | 
			
		||||
                str = str.trim()
 | 
			
		||||
                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 str
 | 
			
		||||
            }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
                const leftoverWarning = new VariableUiElement(
 | 
			
		||||
                    leftoverRules.map((leftovers: string[]) => {
 | 
			
		||||
                        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">
 | 
			
		||||
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>
 | 
			
		||||
 | 
			
		||||
<main />
 | 
			
		||||
<main >
 | 
			
		||||
  <OHTable {value}/>
 | 
			
		||||
</main>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue