forked from MapComplete/MapComplete
Refactoring: port opening hours visualisation to svelte
This commit is contained in:
parent
3b2c2462c5
commit
cc96df94e9
12 changed files with 290 additions and 285 deletions
|
@ -1,22 +0,0 @@
|
|||
import Combine from "./Combine"
|
||||
import Translations from "../i18n/Translations"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import SvelteUIElement from "./SvelteUIElement"
|
||||
import { default as LoadingSvg } from "../../assets/svg/Loading.svelte"
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export default class Loading extends Combine {
|
||||
constructor(msg?: BaseUIElement | string) {
|
||||
const t = Translations.W(msg) ?? Translations.t.general.loading
|
||||
t.SetClass("pl-2")
|
||||
super([
|
||||
new SvelteUIElement(LoadingSvg)
|
||||
.SetClass("animate-spin self-center")
|
||||
.SetStyle("width: 1.5rem; height: 1.5rem; min-width: 1.5rem;"),
|
||||
t,
|
||||
])
|
||||
this.SetClass("flex p-1")
|
||||
}
|
||||
}
|
|
@ -2,8 +2,8 @@
|
|||
import { createEventDispatcher } from "svelte"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { ariaLabel, ariaLabelStore } from "../../Utils/ariaLabel"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { ariaLabelStore } from "../../Utils/ariaLabel"
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
|
||||
/**
|
||||
* A round button with an icon and possible a small text, which hovers above the map
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
import { Translation } from "../i18n/Translation"
|
||||
import WeblateLink from "./WeblateLink.svelte"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import FromHtml from "./FromHtml.svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let t: Translation
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* The 'Toggle' is a UIElement showing either one of two elements, depending on the state.
|
||||
* It can be used to implement e.g. checkboxes or collapsible elements
|
||||
*/
|
||||
|
|
|
@ -967,6 +967,52 @@ changes // => [[36000,61200], ["10:00", "17:00"]]
|
|||
}
|
||||
return oh
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param changeHours number of seconds 'till the start of the day, assuming sorted
|
||||
* @param changeHourText
|
||||
* @param maxDiff minimum required seconds between two items to be in the same group
|
||||
*
|
||||
* OH.partitionOHForDistance([0, 15, 3615], ["start", "15s", "1h15s"]) // => [{changeHours: [0, 3615], changeTexts: ["start", "1h15s"]}, {changeHours: [15], changeTexts: ["15 seconds"]}}]
|
||||
*
|
||||
*/
|
||||
public static partitionOHForDistance(changeHours: number[], changeHourText: string[], maxDiff = 3600): {
|
||||
changeHours: number[],
|
||||
changeTexts: string[]
|
||||
}[] {
|
||||
const partitionedHours: { changeHours: number[], changeTexts: string[] }[] = [
|
||||
{ changeHours: [changeHours[0]], changeTexts: [changeHourText[0]] }
|
||||
]
|
||||
for (let i = 1 /*skip the first one, inited ^*/; i < changeHours.length; i++) {
|
||||
const moment = changeHours[i]
|
||||
const text = changeHourText[i]
|
||||
let depth = 0
|
||||
while (depth < partitionedHours.length) {
|
||||
const candidate = partitionedHours[depth]
|
||||
const lastMoment = candidate.changeHours.at(-1)
|
||||
const diff = moment - lastMoment
|
||||
if (diff >= maxDiff) {
|
||||
candidate.changeHours.push(moment)
|
||||
candidate.changeTexts.push(text)
|
||||
break
|
||||
}
|
||||
depth++
|
||||
}
|
||||
if (depth == partitionedHours.length) {
|
||||
// No candidate found - make a new list
|
||||
partitionedHours.push({
|
||||
changeTexts: [text],
|
||||
changeHours: [moment]
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return partitionedHours
|
||||
}
|
||||
}
|
||||
|
||||
export class ToTextualDescription {
|
||||
|
|
|
@ -1,255 +0,0 @@
|
|||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Combine from "../Base/Combine"
|
||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import { OH, OpeningRange, ToTextualDescription } from "./OpeningHours"
|
||||
import Translations from "../i18n/Translations"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import Toggle from "../Input/Toggle"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import Table from "../Base/Table"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import Loading from "../Base/Loading"
|
||||
import opening_hours from "opening_hours"
|
||||
import Locale from "../i18n/Locale"
|
||||
import SpecialCase from "./Visualisation/SpecialCase.svelte"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import OpeningHoursRangeElement from "./Visualisation/OpeningHoursRangeElement.svelte"
|
||||
|
||||
export default class OpeningHoursVisualization extends Toggle {
|
||||
private static readonly weekdays: 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,
|
||||
]
|
||||
|
||||
constructor(
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
key: string,
|
||||
prefix = "",
|
||||
postfix = ""
|
||||
) {
|
||||
const openingHoursStore = OH.CreateOhObjectStore(tags, key, prefix, postfix)
|
||||
const ohTable = new VariableUiElement(
|
||||
openingHoursStore.map((opening_hours_obj) => {
|
||||
if (opening_hours_obj === undefined) {
|
||||
return new FixedUiElement("No opening hours defined with key " + key).SetClass(
|
||||
"alert"
|
||||
)
|
||||
}
|
||||
|
||||
if (opening_hours_obj === "error") {
|
||||
return Translations.t.general.opening_hours.error_loading
|
||||
}
|
||||
|
||||
const applicableWeek = OH.createRangesForApplicableWeek(opening_hours_obj)
|
||||
const textual = ToTextualDescription.createTextualDescriptionFor(
|
||||
opening_hours_obj,
|
||||
applicableWeek.ranges
|
||||
)
|
||||
const vis = OpeningHoursVisualization.CreateFullVisualisation(
|
||||
opening_hours_obj,
|
||||
applicableWeek.ranges,
|
||||
applicableWeek.startingMonday
|
||||
)
|
||||
Locale.language.mapD((lng) => {
|
||||
console.debug("Setting OH description to", lng, textual)
|
||||
vis.ConstructElement().ariaLabel = textual?.textFor(lng)
|
||||
})
|
||||
return vis
|
||||
})
|
||||
)
|
||||
|
||||
super(
|
||||
ohTable,
|
||||
new Loading(Translations.t.general.opening_hours.loadingCountry),
|
||||
tags.map((tgs) => tgs._country !== undefined)
|
||||
)
|
||||
this.SetClass("no-weblate")
|
||||
}
|
||||
|
||||
private static CreateFullVisualisation(
|
||||
oh: opening_hours,
|
||||
ranges: OpeningRange[][],
|
||||
lastMonday: Date
|
||||
): BaseUIElement {
|
||||
// First, a small sanity check. The business might be permanently closed, 24/7 opened or be another special case
|
||||
if (ranges.some((range) => range.length > 0)) {
|
||||
// The normal case: we have items for the coming days
|
||||
return OpeningHoursVisualization.ConstructVizTable(oh, ranges, lastMonday)
|
||||
}
|
||||
// The special case that range is completely empty
|
||||
return new SvelteUIElement(SpecialCase, { oh })
|
||||
}
|
||||
|
||||
private static ConstructVizTable(
|
||||
oh: any,
|
||||
ranges: {
|
||||
isOpen: boolean
|
||||
isSpecial: boolean
|
||||
comment: string
|
||||
startDate: Date
|
||||
endDate: Date
|
||||
}[][],
|
||||
rangeStart: Date
|
||||
): BaseUIElement {
|
||||
const isWeekstable: boolean = oh.isWeekStable()
|
||||
const [changeHours, changeHourText] = OH.allChangeMoments(ranges)
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
|
||||
const todayIndex = Math.ceil(
|
||||
(today.getTime() - rangeStart.getTime()) / (1000 * 60 * 60 * 24)
|
||||
)
|
||||
// By default, we always show the range between 8 - 19h, in order to give a stable impression
|
||||
// Ofc, a bigger range is used if needed
|
||||
const earliestOpen = Math.min(8 * 60 * 60, ...changeHours)
|
||||
let latestclose = Math.max(...changeHours)
|
||||
// We always make sure there is 30m of leeway in order to give enough room for the closing entry
|
||||
latestclose = Math.max(19 * 60 * 60, latestclose + 30 * 60)
|
||||
const availableArea = latestclose - earliestOpen
|
||||
|
||||
/*
|
||||
* The OH-visualisation is a table, consisting of 8 rows and 2 columns:
|
||||
* The first row is a header row (which is NOT passed as header but just as a normal row!) containing empty for the first column and one object giving all the end times
|
||||
* The other rows are one for each weekday: the first element showing 'mo', 'tu', ..., the second element containing the bars.
|
||||
* Note that the bars are actually an embedded <div> spanning the full width, containing multiple sub-elements
|
||||
* */
|
||||
|
||||
const [header, headerHeight] = OpeningHoursVisualization.ConstructHeaderElement(
|
||||
availableArea,
|
||||
changeHours,
|
||||
changeHourText,
|
||||
earliestOpen
|
||||
)
|
||||
|
||||
const weekdays = []
|
||||
const weekdayStyles = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const day = OpeningHoursVisualization.weekdays[i].Clone()
|
||||
day.SetClass("w-full h-full flex")
|
||||
|
||||
const rangesForDay = ranges[i].map((range) =>
|
||||
new SvelteUIElement(OpeningHoursRangeElement, {
|
||||
availableArea,
|
||||
earliestOpen,
|
||||
latestclose,
|
||||
range,
|
||||
isWeekstable
|
||||
})
|
||||
)
|
||||
const allRanges = new Combine([
|
||||
...OpeningHoursVisualization.CreateLinesAtChangeHours(
|
||||
changeHours,
|
||||
availableArea,
|
||||
earliestOpen
|
||||
),
|
||||
...rangesForDay,
|
||||
]).SetClass("w-full block")
|
||||
|
||||
let extraStyle = ""
|
||||
if (todayIndex == i) {
|
||||
extraStyle = "background-color: var(--subtle-detail-color);"
|
||||
allRanges.SetClass("ohviz-today")
|
||||
} else if (i >= 5) {
|
||||
extraStyle = "background-color: rgba(230, 231, 235, 1);"
|
||||
}
|
||||
weekdays.push([day, allRanges])
|
||||
weekdayStyles.push([
|
||||
"padding-left: 0.5em;" + extraStyle,
|
||||
`position: relative;` + extraStyle,
|
||||
])
|
||||
}
|
||||
return new Table(undefined, [[" ", header], ...weekdays], {
|
||||
contentStyle: [
|
||||
["width: 5%", `position: relative; height: ${headerHeight}`],
|
||||
...weekdayStyles,
|
||||
],
|
||||
})
|
||||
.SetClass("w-full")
|
||||
.SetStyle(
|
||||
"border-collapse: collapse; word-break; word-break: normal; word-wrap: normal"
|
||||
)
|
||||
}
|
||||
|
||||
private static CreateLinesAtChangeHours(
|
||||
changeHours: number[],
|
||||
availableArea: number,
|
||||
earliestOpen: number
|
||||
): BaseUIElement[] {
|
||||
const allLines: BaseUIElement[] = []
|
||||
for (const changeMoment of changeHours) {
|
||||
const offset = (100 * (changeMoment - earliestOpen)) / availableArea
|
||||
if (offset < 0 || offset > 100) {
|
||||
continue
|
||||
}
|
||||
const el = new FixedUiElement("").SetStyle(`left:${offset}%;`).SetClass("ohviz-line")
|
||||
allLines.push(el)
|
||||
}
|
||||
return allLines
|
||||
}
|
||||
|
||||
/**
|
||||
* The OH-Visualization header element, a single bar with hours
|
||||
* @param availableArea
|
||||
* @param changeHours
|
||||
* @param changeHourText
|
||||
* @param earliestOpen
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
private static ConstructHeaderElement(
|
||||
availableArea: number,
|
||||
changeHours: number[],
|
||||
changeHourText: string[],
|
||||
earliestOpen: number
|
||||
): [BaseUIElement, string] {
|
||||
const header: BaseUIElement[] = []
|
||||
|
||||
header.push(
|
||||
...OpeningHoursVisualization.CreateLinesAtChangeHours(
|
||||
changeHours,
|
||||
availableArea,
|
||||
earliestOpen
|
||||
)
|
||||
)
|
||||
|
||||
let showHigher = false
|
||||
let showHigherUsed = false
|
||||
for (let i = 0; i < changeHours.length; i++) {
|
||||
const changeMoment = changeHours[i]
|
||||
const offset = (100 * (changeMoment - earliestOpen)) / availableArea
|
||||
if (offset < 0 || offset > 100) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (i > 0 && (changeMoment - changeHours[i - 1]) / (60 * 60) < 2) {
|
||||
// Quite close to the previous value
|
||||
// We alternate the heights
|
||||
showHigherUsed = true
|
||||
showHigher = !showHigher
|
||||
} else {
|
||||
showHigher = false
|
||||
}
|
||||
|
||||
const el = new Combine([
|
||||
new FixedUiElement(changeHourText[i])
|
||||
.SetClass(
|
||||
"relative bg-white pl-1 pr-1 h-3 font-sm rounded-xl border-2 border-black border-opacity-50"
|
||||
)
|
||||
.SetStyle("left: -50%; word-break:initial"),
|
||||
])
|
||||
.SetStyle(`left:${offset}%;margin-top: ${showHigher ? "1.4rem;" : "0.1rem"}`)
|
||||
.SetClass("block absolute top-0 m-0 h-full box-border ohviz-time-indication")
|
||||
header.push(el)
|
||||
}
|
||||
const headerElem = new Combine(header)
|
||||
.SetClass(`w-full absolute block ${showHigherUsed ? "h-16" : "h-8"}`)
|
||||
.SetStyle("margin-top: -1rem")
|
||||
const headerHeight = showHigherUsed ? "4rem" : "2rem"
|
||||
return [headerElem, headerHeight]
|
||||
}
|
||||
}
|
34
src/UI/OpeningHours/Visualisation/OpeningHours.svelte
Normal file
34
src/UI/OpeningHours/Visualisation/OpeningHours.svelte
Normal file
|
@ -0,0 +1,34 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* Full opening hours visualisations table, dispatches to special cases
|
||||
*/
|
||||
|
||||
import { OH, ToTextualDescription } from "../OpeningHours"
|
||||
import opening_hours from "opening_hours"
|
||||
import { ariaLabel } from "../../../Utils/ariaLabel"
|
||||
import RegularOpeningHoursTable from "./RegularOpeningHoursTable.svelte"
|
||||
import SpecialCase from "./SpecialCase.svelte"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import type { OpeningRange } from "../OpeningHours"
|
||||
|
||||
export let opening_hours_obj: opening_hours
|
||||
|
||||
let applicableWeek = OH.createRangesForApplicableWeek(opening_hours_obj)
|
||||
let oh = opening_hours_obj
|
||||
|
||||
let textual: Translation = ToTextualDescription.createTextualDescriptionFor(oh, applicableWeek.ranges)
|
||||
let applicableWeekRanges: { ranges: OpeningRange[][]; startingMonday: Date } = OH.createRangesForApplicableWeek(oh)
|
||||
let ranges = applicableWeekRanges.ranges
|
||||
let lastMonday = applicableWeekRanges.startingMonday
|
||||
|
||||
</script>
|
||||
<div use:ariaLabel={textual} class="no-weblate">
|
||||
<!-- First, a small sanity check. The business might be permanently closed, 24/7 opened or be another special case -->
|
||||
{#if ranges.some((range) => range.length > 0)}
|
||||
<!-- The normal case: we have items for the coming days -->
|
||||
<RegularOpeningHoursTable {ranges} rangeStart={lastMonday} oh={opening_hours_obj} />
|
||||
{:else}
|
||||
<!-- The special case that range is completely empty -->
|
||||
<SpecialCase oh={opening_hours_obj} />
|
||||
{/if}
|
||||
</div>
|
38
src/UI/OpeningHours/Visualisation/OpeningHoursHeader.svelte
Normal file
38
src/UI/OpeningHours/Visualisation/OpeningHoursHeader.svelte
Normal file
|
@ -0,0 +1,38 @@
|
|||
<script lang="ts">
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
import Combine from "../../Base/Combine"
|
||||
import { FixedUiElement } from "../../Base/FixedUiElement"
|
||||
|
||||
/**
|
||||
* The element showing an "hour" in a bubble, above or below the opening hours table
|
||||
* Dumbly shows one row of what is given.
|
||||
*
|
||||
* Does not include lines
|
||||
*/
|
||||
export let availableArea: number
|
||||
export let changeHours: number[]
|
||||
export let changeHourText: string[]
|
||||
export let earliestOpen: number
|
||||
export let todayChangeMoments: Set<number>
|
||||
|
||||
function calcOffset(changeMoment: number) {
|
||||
return (100 * (changeMoment - earliestOpen)) / availableArea
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="w-full absolute block h-8" style="margin-top: -1rem">
|
||||
{#each changeHours as changeMoment, i}
|
||||
{#if calcOffset(changeMoment) >= 0 && calcOffset(changeMoment) <= 100}
|
||||
<div style={`left:${calcOffset(changeMoment)}%; margin-top: 0.1rem`}
|
||||
class="block absolute top-0 m-0 h-full box-border ohviz-time-indication">
|
||||
<div
|
||||
style="left: -50%; word-break: initial;"
|
||||
class:border-opacity-50={!todayChangeMoments?.has(changeMoment)}
|
||||
class="relative h-fit bg-white pl-1 pr-1 h-3 font-sm rounded-xl border-2 border-black">
|
||||
{changeHourText[i]}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
|
@ -19,7 +19,7 @@
|
|||
startOfDay.setHours(0, 0, 0, 0)
|
||||
let startpoint = (range.startDate.getTime() - startOfDay.getTime()) / 1000 - earliestOpen
|
||||
// prettier-ignore
|
||||
let width = (100 * (range.endDate.getTime() - range.startDate.getTime()) / 1000) / (latestclose - earliestOpen)
|
||||
let width = (100 * (range.endDate.getTime() - range.startDate.getTime()) / 1000) / availableArea
|
||||
let startPercentage = (100 * startpoint) / availableArea
|
||||
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<script lang="ts">/**
|
||||
* Wrapper around 'OpeningHours' so that the latter can deal with the opening_hours object directly
|
||||
*/
|
||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import type opening_hours from "opening_hours"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import Loading from "../../Base/Loading.svelte"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import OpeningHours from "./OpeningHours.svelte"
|
||||
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let opening_hours_obj: Store<opening_hours | "error">
|
||||
export let key: string
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
{#if $tags._country === undefined}
|
||||
<Loading>
|
||||
<Tr t={Translations.t.general.opening_hours.loadingCountry} />
|
||||
</Loading>
|
||||
{:else if $opening_hours_obj === undefined}
|
||||
<div class="alert">No opening hours defined with key {key}</div>
|
||||
{:else if $opening_hours_obj === "error"}
|
||||
<Tr cls="alert" t={Translations.t.general.opening_hours.error_loading} />
|
||||
{:else}
|
||||
<OpeningHours opening_hours_obj={$opening_hours_obj} />
|
||||
{/if}
|
|
@ -0,0 +1,129 @@
|
|||
<script lang="ts">/**
|
||||
* The main visualisation which shows ranges, one or more top/bottom headers, ...
|
||||
* Does not handle the special cases
|
||||
*/
|
||||
import opening_hours from "opening_hours"
|
||||
import OpeningHoursHeader from "./OpeningHoursHeader.svelte"
|
||||
import { default as Transl } from "../../Base/Tr.svelte" /* The IDE confuses <tr> (table row) and <Tr> (translation) as they are normally case insensitive -> import under a different name */
|
||||
import OpeningHoursRangeElement from "./OpeningHoursRangeElement.svelte"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import { OH } from "../OpeningHours"
|
||||
import { Utils } from "../../../Utils"
|
||||
|
||||
export let oh: opening_hours
|
||||
export let ranges: {
|
||||
isOpen: boolean
|
||||
isSpecial: boolean
|
||||
comment: string
|
||||
startDate: Date
|
||||
endDate: Date
|
||||
}[][] // Per weekday
|
||||
export let rangeStart: Date
|
||||
let isWeekstable: boolean = oh.isWeekStable()
|
||||
let today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
let todayIndex = Math.ceil((today.getTime() - rangeStart.getTime()) / (1000 * 60 * 60 * 24))
|
||||
|
||||
|
||||
let weekdayRanges = ranges.map(ranges => ranges.filter(r => r.startDate.getDay() != 0 && r.startDate.getDay() != 6))
|
||||
let weekendRanges = ranges.map(ranges => ranges.filter(r => r.startDate.getDay() == 0 || r.startDate.getDay() == 6))
|
||||
let todayRanges = ranges.map(((r, i) => r.filter(() => i === todayIndex)))
|
||||
|
||||
|
||||
const [changeHours, changeHourText] = OH.allChangeMoments(weekdayRanges)
|
||||
const [changeHoursWeekend, changeHourTextWeekend] = OH.allChangeMoments(weekendRanges)
|
||||
|
||||
const weekdayHeaders: {
|
||||
changeHours: number[];
|
||||
changeTexts: string[]
|
||||
}[] = OH.partitionOHForDistance(changeHours, changeHourText)
|
||||
const weekendDayHeaders: {
|
||||
changeHours: number[];
|
||||
changeTexts: string[]
|
||||
}[] = OH.partitionOHForDistance(changeHoursWeekend, changeHourTextWeekend)
|
||||
|
||||
let allChangeMoments: number[] = Utils.DedupT([...changeHours, ...changeHoursWeekend])
|
||||
let todayChangeMoments: Set<number> = new Set(OH.allChangeMoments(todayRanges)[0])
|
||||
// By default, we always show the range between 8 - 19h, in order to give a stable impression
|
||||
// Ofc, a bigger range is used if needed
|
||||
let earliestOpen = Math.min(8 * 60 * 60, ...changeHours)
|
||||
// We always make sure there is 30m of leeway in order to give enough room for the closing entry
|
||||
let latestclose = Math.max(19 * 60 * 60, Math.max(...changeHours) + 30 * 60)
|
||||
let availableArea = latestclose - earliestOpen
|
||||
|
||||
function calcLineOffset(moment: number) {
|
||||
return 100 * (moment - earliestOpen) / availableArea
|
||||
}
|
||||
|
||||
let weekdays: 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
|
||||
]
|
||||
|
||||
</script>
|
||||
<div class="w-full h-fit relative">
|
||||
{#each allChangeMoments as moment}
|
||||
<div class="w-full absolute h-full">
|
||||
<div class="w-full h-full flex">
|
||||
<div style="height: 5rem; width: 5%; min-width: 2.75rem" />
|
||||
<div class="grow">
|
||||
|
||||
<div class="border-x h-full"
|
||||
style={`width: calc( ${calcLineOffset(moment)}% ); border-color: ${todayChangeMoments.has(moment) ? "#000" : "#bbb"}`} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<table class="w-full" style="border-collapse: collapse; word-break: normal; word-wrap: normal">
|
||||
{#each weekdayHeaders as weekdayHeader}
|
||||
<tr>
|
||||
<td style="width: 5%; min-width: 2.75rem;"></td>
|
||||
<td class="relative h-8">
|
||||
<OpeningHoursHeader {earliestOpen} {availableArea} changeHours={weekdayHeader.changeHours}
|
||||
{todayChangeMoments}
|
||||
changeHourText={weekdayHeader.changeTexts} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
{#each weekdays as weekday, i}
|
||||
<tr class:interactive={i >= 5}>
|
||||
<td style="width: 5%">
|
||||
<Transl t={weekday} />
|
||||
</td>
|
||||
<td class="relative p-0 m-0" class:ohviz-today={i===todayIndex}>
|
||||
<div class="w-full" style="margin-left: -0px">
|
||||
{#each ranges[i] as range}
|
||||
<OpeningHoursRangeElement
|
||||
{availableArea}
|
||||
{earliestOpen}
|
||||
{latestclose}
|
||||
{range}
|
||||
{isWeekstable}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/each}
|
||||
|
||||
{#each weekendDayHeaders as weekdayHeader}
|
||||
<tr>
|
||||
<td style="width: 5%"></td>
|
||||
<td class="relative h-8">
|
||||
<OpeningHoursHeader {earliestOpen} {availableArea} changeHours={weekdayHeader.changeHours}
|
||||
{todayChangeMoments}
|
||||
changeHourText={weekdayHeader.changeTexts} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
</div>
|
|
@ -5,12 +5,11 @@ import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState
|
|||
import { HistogramViz } from "./Popup/HistogramViz"
|
||||
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
|
||||
import { MultiApplyViz } from "./Popup/MultiApplyViz"
|
||||
import { UIEventSource } from "../Logic/UIEventSource"
|
||||
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
||||
import AllTagsPanel from "./Popup/AllTagsPanel/AllTagsPanel.svelte"
|
||||
import { VariableUiElement } from "./Base/VariableUIElement"
|
||||
import { Translation } from "./i18n/Translation"
|
||||
import Translations from "./i18n/Translations"
|
||||
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
|
||||
import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis"
|
||||
import { LanguageElement } from "./Popup/LanguageElement/LanguageElement"
|
||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||
|
@ -43,6 +42,9 @@ import {
|
|||
} from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
|
||||
import ClearGPSHistory from "./BigComponents/ClearGPSHistory.svelte"
|
||||
import AllFeaturesStatistics from "./Statistics/AllFeaturesStatistics.svelte"
|
||||
import OpeningHoursWithError from "./OpeningHours/Visualisation/OpeningHoursWithError.svelte"
|
||||
import { OH } from "./OpeningHours/OpeningHours"
|
||||
import opening_hours from "opening_hours"
|
||||
|
||||
export default class SpecialVisualizations {
|
||||
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()
|
||||
|
@ -276,7 +278,12 @@ export default class SpecialVisualizations {
|
|||
"A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`",
|
||||
constr: (state, tagSource: UIEventSource<any>, args) => {
|
||||
const [key, prefix, postfix] = args
|
||||
return new OpeningHoursVisualization(tagSource, key, prefix, postfix)
|
||||
const openingHoursStore: Store<opening_hours | "error" | undefined> = OH.CreateOhObjectStore(tagSource, key, prefix, postfix)
|
||||
return new SvelteUIElement(OpeningHoursWithError, {
|
||||
tags: tagSource,
|
||||
key,
|
||||
opening_hours_obj: openingHoursStore
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue