forked from MapComplete/MapComplete
Themes(postboxes): add 'points_in_time' input element, add 'collection_times' to posbox theme, fix #757
This commit is contained in:
parent
4c0a42d5b6
commit
602c51bcb9
15 changed files with 425 additions and 68 deletions
|
@ -139,10 +139,24 @@
|
|||
}
|
||||
],
|
||||
"tagRenderings": [
|
||||
"images",
|
||||
{
|
||||
"id": "minimap",
|
||||
"render": "{minimap(18): height: 5rem; overflow: hidden; border-radius:3rem; }"
|
||||
"id": "collection_times",
|
||||
"question": {
|
||||
"en": "When is the mail collected?"
|
||||
},
|
||||
"render": {
|
||||
"before": {
|
||||
"en": "<h3>Collection times</h3>"
|
||||
},
|
||||
"special": {
|
||||
"key": "collection_times",
|
||||
"type": "points_in_time"
|
||||
}
|
||||
},
|
||||
"freeform": {
|
||||
"key": "collection_times",
|
||||
"type": "points_in_time"
|
||||
}
|
||||
},
|
||||
{
|
||||
"builtin": "operator",
|
||||
|
@ -193,4 +207,4 @@
|
|||
"pakjes"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -353,6 +353,7 @@
|
|||
"open_during_ph": "During a public holiday, it is",
|
||||
"open_until": "Closes at {date}",
|
||||
"opensAt": "from",
|
||||
"ph": "Public holidays",
|
||||
"ph_closed": "closed",
|
||||
"ph_not_known": " ",
|
||||
"ph_open": "open",
|
||||
|
@ -373,6 +374,12 @@
|
|||
"versionInfo": "v{version} - generated on {date}"
|
||||
},
|
||||
"pickLanguage": "Select language",
|
||||
"points_in_time": {
|
||||
"closed": "Closed",
|
||||
"daily": "Every day",
|
||||
"weekdays": "On weekdays",
|
||||
"weekends": "On weekends"
|
||||
},
|
||||
"poweredByMapComplete": "Powered by MapComplete - crowdsourced, thematic maps with OpenStreetMap",
|
||||
"poweredByOsm": "Powered by OpenStreetMap",
|
||||
"questionBox": {
|
||||
|
|
|
@ -9157,6 +9157,12 @@
|
|||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"collection_times": {
|
||||
"question": "When is the mail collected?",
|
||||
"render": {
|
||||
"before": "<h3>Collection times</h3>"
|
||||
}
|
||||
},
|
||||
"operator": {
|
||||
"override": {
|
||||
"question": "Who operates this postbox?",
|
||||
|
|
|
@ -142,6 +142,12 @@ export class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJso
|
|||
) {
|
||||
continue
|
||||
}
|
||||
if (
|
||||
json.freeform["type"] === "points_in_time" &&
|
||||
txt.indexOf("{points_in_time(") >= 0
|
||||
) {
|
||||
continue
|
||||
}
|
||||
const keyFirstArg = ["canonical", "fediverse_link", "translated"]
|
||||
if (
|
||||
keyFirstArg.some(
|
||||
|
|
53
src/UI/CollectionTimes/CollectionTimeRange.svelte
Normal file
53
src/UI/CollectionTimes/CollectionTimeRange.svelte
Normal file
|
@ -0,0 +1,53 @@
|
|||
<script lang="ts">
|
||||
/*
|
||||
* Visualises collection times for a single collection range
|
||||
*/
|
||||
import { OH } from "../OpeningHours/OpeningHours"
|
||||
import type { OpeningRange } from "../OpeningHours/OpeningHours"
|
||||
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
|
||||
export let range: OpeningRange[][]
|
||||
const wt = Translations.t.general.weekdays
|
||||
const weekdays: Translation[] = [wt.sunday, wt.monday, wt.tuesday, wt.wednesday, wt.thursday, wt.friday, wt.saturday]
|
||||
let allTheSame = OH.weekdaysIdentical(range, 0, range.length - 1)
|
||||
let today = new Date().getDay()
|
||||
|
||||
let dayZero = -1
|
||||
for (let i = 0; i < range.length; i++) {
|
||||
const moments = range[i]
|
||||
if (moments.length === 0) {
|
||||
continue
|
||||
}
|
||||
const moment = moments[0]
|
||||
const day = moment.startDate.getDay()
|
||||
dayZero = day - i
|
||||
}
|
||||
function isToday(i:number ){
|
||||
i = (i+dayZero) % 7
|
||||
return i === today
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if allTheSame && range[0]?.length > 0}
|
||||
<div class="flex justify-between">
|
||||
<slot />
|
||||
{#each range[0] as moment (moment)}
|
||||
<div class="ml-8">{moment.startDate.toLocaleTimeString()}</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if dayZero >= 0 } <!-- /*If dayZero == -1, then we got no valid values at all*/ -->
|
||||
{#each range as moments, i (moments)}
|
||||
<div class="flex gap-x-4 justify-between w-full px-2" class:interactive={isToday(i)} class:text-bold={isToday(i)} >
|
||||
<Tr t={weekdays[(i + dayZero) % 7]} />
|
||||
{#if range[i].length > 0}
|
||||
{#each moments as moment (moment)}
|
||||
<div class="ml-8">{moment.startDate.toLocaleTimeString()}</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<Tr cls="italic subtle" t={Translations.t.general.points_in_time.closed}/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
55
src/UI/CollectionTimes/CollectionTimes.svelte
Normal file
55
src/UI/CollectionTimes/CollectionTimes.svelte
Normal file
|
@ -0,0 +1,55 @@
|
|||
<script lang="ts">
|
||||
|
||||
import CollectionTimeRange from "./CollectionTimeRange.svelte"
|
||||
import opening_hours from "opening_hours"
|
||||
import { OH } from "../OpeningHours/OpeningHours"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
|
||||
export let times: opening_hours
|
||||
let monday = OH.getMondayBefore(new Date())
|
||||
let sunday = new Date()
|
||||
sunday.setTime(monday.getTime() + 7 * 24 * 60 * 60 * 1000)
|
||||
let ranges = OH.getRanges(times, monday, sunday)
|
||||
let weekdays = ranges.slice(0, 5)
|
||||
let weekend = ranges.slice(5, 7)
|
||||
let everyDaySame = OH.weekdaysIdentical(ranges, 0, ranges.length - 1)
|
||||
let weekdaysAndWeekendsSame = OH.weekdaysIdentical(weekdays, 0, 4) && OH.weekdaysIdentical(weekend, 0, 1)
|
||||
const t = Translations.t.general.points_in_time
|
||||
</script>
|
||||
|
||||
|
||||
<div class="m-4 border">
|
||||
|
||||
{#if everyDaySame || !weekdaysAndWeekendsSame}
|
||||
<CollectionTimeRange range={ranges}>
|
||||
<Tr t={t.daily} />
|
||||
</CollectionTimeRange>
|
||||
{:else if times.isWeekStable()}
|
||||
<div class="flex flex-col w-fit">
|
||||
<CollectionTimeRange range={weekdays}>
|
||||
<Tr t={t.weekdays} />
|
||||
|
||||
</CollectionTimeRange>
|
||||
<CollectionTimeRange range={weekend}>
|
||||
<Tr t={t.weekends} />
|
||||
|
||||
</CollectionTimeRange>
|
||||
</div>
|
||||
{:else}
|
||||
{#each ranges as range (range)}
|
||||
{#if range.length > 0}
|
||||
<div class="flex justify-between">
|
||||
{range[0].startDate.toLocaleDateString()}
|
||||
<div class="flex gap-x-4">
|
||||
{#each range as moment}
|
||||
<div>
|
||||
{moment.startDate.toLocaleTimeString()}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
|
@ -0,0 +1,48 @@
|
|||
<script lang="ts">/**
|
||||
* Multiple 'SingleCollectionTime'-rules togehter
|
||||
*/
|
||||
import { Stores, UIEventSource } from "../../../../Logic/UIEventSource"
|
||||
import SingleCollectionTime from "./SingleCollectionTime.svelte"
|
||||
import { Utils } from "../../../../Utils"
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
|
||||
export let value: UIEventSource<string>
|
||||
|
||||
let initialRules: string[] = Utils.NoEmpty(value.data?.split(";")?.map(v => v.trim()))
|
||||
let singleRules: UIEventSource<UIEventSource<string>[]> = new UIEventSource(
|
||||
initialRules?.map(v => new UIEventSource(v)) ?? []
|
||||
)
|
||||
if(singleRules.data.length === 0){
|
||||
singleRules.data.push(new UIEventSource(undefined))
|
||||
}
|
||||
singleRules.bindD(stores => Stores.concat(stores)).addCallbackAndRunD(subrules => {
|
||||
console.log("Setting subrules", subrules)
|
||||
value.set(Utils.NoEmpty(subrules).join("; "))
|
||||
})
|
||||
|
||||
function rm(rule: UIEventSource){
|
||||
const index = singleRules.data.indexOf(rule)
|
||||
singleRules.data.splice(index, 1)
|
||||
singleRules.ping()
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="interactive">
|
||||
|
||||
{#each $singleRules as rule}
|
||||
<SingleCollectionTime value={rule}>
|
||||
<svelte:fragment slot="right">
|
||||
{#if $singleRules.length > 1}
|
||||
|
||||
<button on:click={() => { rm(rule) }} class="as-link">
|
||||
<TrashIcon class="w-6 h-6" />
|
||||
</button>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</SingleCollectionTime>
|
||||
{/each}
|
||||
<button on:click={() => {singleRules.data.push(new UIEventSource(undefined)); singleRules.ping()}}>Add schedule
|
||||
</button>
|
||||
</div>
|
|
@ -0,0 +1,122 @@
|
|||
<script lang="ts">
|
||||
|
||||
import PlusCircle from "@rgossiaux/svelte-heroicons/solid/PlusCircle"
|
||||
import TimeInput from "../TimeInput.svelte"
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Checkbox from "../../../Base/Checkbox.svelte"
|
||||
import Tr from "../../../Base/Tr.svelte"
|
||||
import { Stores, UIEventSource } from "../../../../Logic/UIEventSource"
|
||||
import Translations from "../../../i18n/Translations"
|
||||
import { OH } from "../../../OpeningHours/OpeningHours"
|
||||
import { Utils } from "../../../../Utils"
|
||||
|
||||
export let value: UIEventSource<string>
|
||||
|
||||
const wt = Translations.t.general.weekdays.abbreviations
|
||||
/*
|
||||
Single rule for collection times, e.g. "Mo-Fr 10:00, 17:00"
|
||||
*/
|
||||
let weekdays: Translation[] = [wt.monday, wt.tuesday, wt.wednesday, wt.thursday, wt.friday, wt.saturday, wt.sunday, Translations.t.general.opening_hours.ph]
|
||||
|
||||
let initialTimes= Utils.NoEmpty(value.data?.split(" ")?.[1]?.split(",")?.map(s => s.trim())??[])
|
||||
let values = new UIEventSource(initialTimes.map(t => new UIEventSource(t)))
|
||||
if(values.data.length === 0){
|
||||
values.data.push(new UIEventSource(""))
|
||||
}
|
||||
|
||||
|
||||
|
||||
let daysOfTheWeek = [...OH.days, "PH"]
|
||||
let selectedDays = daysOfTheWeek.map(() => new UIEventSource(false))
|
||||
|
||||
let initialDays = Utils.NoEmpty(value.data?.split(" ")?.[0]?.split(",") ?? [])
|
||||
for (const initialDay of initialDays) {
|
||||
if (initialDay.indexOf("-") > 0) {
|
||||
let [start, end] = initialDay.split("-")
|
||||
let startindex = daysOfTheWeek.indexOf(start)
|
||||
let stopindex = daysOfTheWeek.indexOf(end)
|
||||
for (let i = startindex; i <= stopindex; i++) {
|
||||
selectedDays[i]?.set(true)
|
||||
}
|
||||
} else {
|
||||
|
||||
let index = daysOfTheWeek.indexOf(initialDay)
|
||||
if (index >= 0) {
|
||||
selectedDays[index]?.set(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let selectedDaysBound = Stores.concat(selectedDays)
|
||||
.mapD(days => Utils.NoNull(days.map((selected, i) => selected ? daysOfTheWeek[i] : undefined)))
|
||||
let valuesConcat: Store<string[]> = values.bindD(values => Stores.concat(values))
|
||||
.mapD(values => Utils.NoEmpty(values))
|
||||
valuesConcat.mapD(times => {
|
||||
console.log(times)
|
||||
times = Utils.NoNull(times)
|
||||
if (!times || times?.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
times?.sort(/*concatted, new array*/)
|
||||
return (Utils.NoEmpty(selectedDaysBound.data).join(",") + " " + times).trim()
|
||||
}, [selectedDaysBound]).addCallbackAndRunD(v => value.set(v))
|
||||
|
||||
function selectWeekdays() {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
selectedDays[i].set(true)
|
||||
}
|
||||
for (let i = 5; i < selectedDays.length; i++) {
|
||||
selectedDays[i].set(false)
|
||||
}
|
||||
}
|
||||
|
||||
function clearDays() {
|
||||
for (let i = 0; i < selectedDays.length; i++) {
|
||||
selectedDays[i].set(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="rounded-xl my-2 p-2 low-interaction flex w-full justify-between flex-wrap">
|
||||
|
||||
<div class="flex flex-col">
|
||||
|
||||
<div class="flex flex-wrap">
|
||||
{#each $values as value, i}
|
||||
<div class="flex mx-4 gap-x-1 items-center">
|
||||
<TimeInput {value} />
|
||||
{#if $values.length > 1}
|
||||
<button class="as-link">
|
||||
<TrashIcon class="w-6 h-6" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<button on:click={() => {values.data.push(new UIEventSource(undefined)); values.ping()}}>
|
||||
<PlusCircle class="w-6 h-6" />
|
||||
Add time
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex w-fit flex-wrap">
|
||||
{#each daysOfTheWeek as day, i}
|
||||
<div class="w-fit">
|
||||
<Checkbox selected={selectedDays[i]}>
|
||||
<Tr t={weekdays[i]} />
|
||||
</Checkbox>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-between w-full">
|
||||
|
||||
<div class="flex flex-wrap gap-x-4">
|
||||
<button class="as-link text-sm" on:click={() => selectWeekdays()}>Select weekdays</button>
|
||||
<button class="as-link text-sm" on:click={() => clearDays()}>Clear days</button>
|
||||
|
||||
</div>
|
||||
<slot name="right" />
|
||||
</div>
|
||||
</div>
|
|
@ -21,6 +21,7 @@
|
|||
import WikidataInputHelper from "./Helpers/WikidataInputHelper.svelte"
|
||||
import DistanceInput from "./Helpers/DistanceInput.svelte"
|
||||
import TimeInput from "./Helpers/TimeInput.svelte"
|
||||
import CollectionTimes from "./Helpers/CollectionTimes/CollectionTimes.svelte"
|
||||
|
||||
export let type: ValidatorType
|
||||
export let value: UIEventSource<string | object>
|
||||
|
@ -42,6 +43,8 @@
|
|||
<DateInput {value} />
|
||||
{:else if type === "time"}
|
||||
<TimeInput {value} />
|
||||
{:else if type === "points_in_time"}
|
||||
<CollectionTimes {value} />
|
||||
{:else if type === "color"}
|
||||
<ColorInput {value} />
|
||||
{:else if type === "image"}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { UIEventSource } from "../../Logic/UIEventSource"
|
|||
import { MapProperties } from "../../Models/MapProperties"
|
||||
import { Feature } from "geojson"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import { ValidatorType } from "./Validators"
|
||||
|
||||
export interface InputHelperProperties {
|
||||
/**
|
||||
|
@ -25,11 +26,12 @@ export interface InputHelperProperties {
|
|||
}
|
||||
|
||||
export default class InputHelpers {
|
||||
public static hideInputField: string[] = ["translation", "simple_tag", "tag","time"]
|
||||
public static hideInputField: ValidatorType[] = ["translation", "simple_tag", "tag","time"]
|
||||
|
||||
/**
|
||||
* Constructs a mapProperties-object for the given properties.
|
||||
* Assumes that the first helper-args contains the desired zoom-level
|
||||
* Used for the 'direction' input helper
|
||||
* @param properties
|
||||
* @private
|
||||
*/
|
||||
|
|
|
@ -29,6 +29,7 @@ import NameSuggestionIndexValidator from "./Validators/NameSuggestionIndexValida
|
|||
import CurrencyValidator from "./Validators/CurrencyValidator"
|
||||
import RegexValidator from "./Validators/RegexValidator"
|
||||
import { TimeValidator } from "./Validators/TimeValidator"
|
||||
import CollectionTimesValidator from "./Validators/CollectionTimesValidator"
|
||||
|
||||
export type ValidatorType = (typeof Validators.availableTypes)[number]
|
||||
|
||||
|
@ -54,6 +55,7 @@ export default class Validators {
|
|||
"pfloat",
|
||||
"phone",
|
||||
"pnat",
|
||||
"points_in_time",
|
||||
"regex",
|
||||
"simple_tag",
|
||||
"slope",
|
||||
|
@ -97,6 +99,7 @@ export default class Validators {
|
|||
new NameSuggestionIndexValidator(),
|
||||
new CurrencyValidator(),
|
||||
new RegexValidator(),
|
||||
new CollectionTimesValidator()
|
||||
]
|
||||
|
||||
private static _byType = Validators._byTypeConstructor()
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import StringValidator from "./StringValidator"
|
||||
|
||||
export default class CollectionTimesValidator extends StringValidator{
|
||||
constructor() {
|
||||
super("points_in_time", "'Points in time' are points according to a fixed schedule, e.g. 'every monday at 10:00'. They are typically used for postbox collection times or times of mass at a place of worship")
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Utils } from "../../Utils"
|
||||
import opening_hours from "opening_hours"
|
||||
import opening_hours, { mode, optional_conf } from "opening_hours"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { Translation, TypedTranslation } from "../i18n/Translation"
|
||||
import Translations from "../i18n/Translations"
|
||||
|
@ -25,7 +25,7 @@ export interface OpeningRange {
|
|||
* Various utilities manipulating opening hours
|
||||
*/
|
||||
export class OH {
|
||||
private static readonly days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
||||
public static readonly days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
||||
private static readonly daysIndexed = {
|
||||
mo: 0,
|
||||
tu: 1,
|
||||
|
@ -570,7 +570,8 @@ changes // => [[36000,61200], ["10:00", "17:00"]]
|
|||
public static createOhObject(
|
||||
tags: Record<string, string | number> & { _lat: number; _lon: number; _country?: string },
|
||||
textToParse: string,
|
||||
country: string
|
||||
country: string,
|
||||
mode?: mode,
|
||||
) {
|
||||
return new opening_hours(
|
||||
textToParse,
|
||||
|
@ -582,11 +583,61 @@ changes // => [[36000,61200], ["10:00", "17:00"]]
|
|||
state: undefined,
|
||||
},
|
||||
},
|
||||
<any>{ tag_key: "opening_hours" }
|
||||
<optional_conf> {
|
||||
tag_key: "opening_hours",
|
||||
mode,
|
||||
},
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs the opening-ranges for either this week, or for next week if there are no more openings this week.
|
||||
* Note: 'today' is mostly used for testing
|
||||
*
|
||||
* const oh = new opening_hours("mar 15 - oct 15")
|
||||
* const ranges = OH.createRangesForApplicableWeek(oh, new Date(2025,4,20,10,0,0))
|
||||
* ranges // => {ranges: [[],[],[],[],[],[],[]], startingMonday: new Date(2025,4,18,24,0,0)}
|
||||
*/
|
||||
public static createRangesForApplicableWeek(
|
||||
oh: opening_hours,
|
||||
today?: Date,
|
||||
): {
|
||||
ranges: OpeningRange[][]
|
||||
startingMonday: Date
|
||||
} {
|
||||
today ??= new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const lastMonday = OH.getMondayBefore(today)
|
||||
const nextSunday = new Date(lastMonday)
|
||||
nextSunday.setDate(nextSunday.getDate() + 7)
|
||||
|
||||
if (!oh.getState() && !oh.getUnknown()) {
|
||||
// POI is currently closed
|
||||
const nextChange: Date = oh.getNextChange()
|
||||
if (
|
||||
// Shop isn't gonna open anymore in this timerange
|
||||
nextSunday < nextChange &&
|
||||
// And we are already in the weekend to show next week
|
||||
(today.getDay() == 0 || today.getDay() == 6)
|
||||
) {
|
||||
// We move the range to next week!
|
||||
lastMonday.setDate(lastMonday.getDate() + 7)
|
||||
nextSunday.setDate(nextSunday.getDate() + 7)
|
||||
}
|
||||
}
|
||||
|
||||
/* We calculate the ranges when it is opened! */
|
||||
return { startingMonday: lastMonday, ranges: OH.getRanges(oh, lastMonday, nextSunday) }
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Checks that the days are identical
|
||||
*
|
||||
* @param startday start index, inclusive
|
||||
* @param endday end index, INCLUSIVE (!)
|
||||
*
|
||||
* let ranges = <any> [
|
||||
* [
|
||||
* {
|
||||
|
@ -666,47 +717,6 @@ changes // => [[36000,61200], ["10:00", "17:00"]]
|
|||
* OH.weekdaysIdentical(ranges, 4, 5) // => false
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs the opening-ranges for either this week, or for next week if there are no more openings this week.
|
||||
* Note: 'today' is mostly used for testing
|
||||
*
|
||||
* const oh = new opening_hours("mar 15 - oct 15")
|
||||
* const ranges = OH.createRangesForApplicableWeek(oh, new Date(2025,4,20,10,0,0))
|
||||
* ranges // => {ranges: [[],[],[],[],[],[],[]], startingMonday: new Date(2025,4,18,24,0,0)}
|
||||
*/
|
||||
public static createRangesForApplicableWeek(
|
||||
oh: opening_hours,
|
||||
today?: Date
|
||||
): {
|
||||
ranges: OpeningRange[][]
|
||||
startingMonday: Date
|
||||
} {
|
||||
today ??= new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const lastMonday = OH.getMondayBefore(today)
|
||||
const nextSunday = new Date(lastMonday)
|
||||
nextSunday.setDate(nextSunday.getDate() + 7)
|
||||
|
||||
if (!oh.getState() && !oh.getUnknown()) {
|
||||
// POI is currently closed
|
||||
const nextChange: Date = oh.getNextChange()
|
||||
if (
|
||||
// Shop isn't gonna open anymore in this timerange
|
||||
nextSunday < nextChange &&
|
||||
// And we are already in the weekend to show next week
|
||||
(today.getDay() == 0 || today.getDay() == 6)
|
||||
) {
|
||||
// We move the range to next week!
|
||||
lastMonday.setDate(lastMonday.getDate() + 7)
|
||||
nextSunday.setDate(nextSunday.getDate() + 7)
|
||||
}
|
||||
}
|
||||
|
||||
/* We calculate the ranges when it is opened! */
|
||||
return { startingMonday: lastMonday, ranges: OH.getRanges(oh, lastMonday, nextSunday) }
|
||||
}
|
||||
|
||||
public static weekdaysIdentical(openingRanges: OpeningRange[][], startday = 0, endday = 4) {
|
||||
const monday = openingRanges[startday]
|
||||
for (let i = startday + 1; i <= endday; i++) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import type { OpeningRange } from "../OpeningHours"
|
||||
import { OH, ToTextualDescription } from "../OpeningHours"
|
||||
import { ToTextualDescription } from "../OpeningHours"
|
||||
import opening_hours from "opening_hours"
|
||||
import { ariaLabel } from "../../../Utils/ariaLabel"
|
||||
import RegularOpeningHoursTable from "./RegularOpeningHoursTable.svelte"
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationState,
|
||||
SpecialVisualizationSvelte,
|
||||
} from "../SpecialVisualization"
|
||||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { HistogramViz } from "./HistogramViz"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Feature } from "geojson"
|
||||
|
@ -28,6 +24,7 @@ import TagRenderingEditable from "./TagRendering/TagRenderingEditable.svelte"
|
|||
import AllTagsPanel from "./AllTagsPanel/AllTagsPanel.svelte"
|
||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import { TagUtils } from "../../Logic/Tags/TagUtils"
|
||||
import CollectionTimes from "../CollectionTimes/CollectionTimes.svelte"
|
||||
|
||||
class DirectionIndicatorVis extends SpecialVisualization {
|
||||
funcName = "direction_indicator"
|
||||
|
@ -41,7 +38,7 @@ class DirectionIndicatorVis extends SpecialVisualization {
|
|||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature
|
||||
feature: Feature,
|
||||
): BaseUIElement {
|
||||
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
||||
}
|
||||
|
@ -69,7 +66,7 @@ class DirectionAbsolute extends SpecialVisualization {
|
|||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
args: string[],
|
||||
): BaseUIElement {
|
||||
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
||||
const offset = args[1] === "" ? 0 : Number(args[1])
|
||||
|
@ -82,11 +79,11 @@ class DirectionAbsolute extends SpecialVisualization {
|
|||
})
|
||||
.mapD((value) => {
|
||||
const dir = GeoOperations.bearingToHuman(
|
||||
GeoOperations.parseBearing(value) + offset
|
||||
GeoOperations.parseBearing(value) + offset,
|
||||
)
|
||||
console.log("Human dir", dir)
|
||||
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +153,7 @@ class OpeningHoursState extends SpecialVisualizationSvelte {
|
|||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
args: string[],
|
||||
): SvelteUIElement {
|
||||
const keyToUse = args[0]
|
||||
const prefix = args[1]
|
||||
|
@ -198,7 +195,7 @@ class Canonical extends SpecialVisualization {
|
|||
return undefined
|
||||
}
|
||||
const allUnits: Unit[] = [].concat(
|
||||
...(state?.theme?.layers?.map((lyr) => lyr.units) ?? [])
|
||||
...(state?.theme?.layers?.map((lyr) => lyr.units) ?? []),
|
||||
)
|
||||
const unit = allUnits.filter((unit) => unit.isApplicableToKey(key))[0]
|
||||
if (unit === undefined) {
|
||||
|
@ -206,7 +203,7 @@ class Canonical extends SpecialVisualization {
|
|||
}
|
||||
const getCountry = () => tagSource.data._country
|
||||
return unit.asHumanLongValue(value, getCountry)
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -231,7 +228,7 @@ class PresetDescription extends SpecialVisualization {
|
|||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
): BaseUIElement {
|
||||
const translation = tagSource.map((tags) => {
|
||||
const layer = state.theme.getMatchingLayer(tags)
|
||||
|
@ -251,7 +248,7 @@ class PresetTypeSelect extends SpecialVisualizationSvelte {
|
|||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
selectedElement: Feature,
|
||||
layer: LayerConfig
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
const t = Translations.t.preset_type
|
||||
if (layer._basedOn !== layer.id) {
|
||||
|
@ -312,7 +309,7 @@ class TagsVis extends SpecialVisualization {
|
|||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[]
|
||||
argument: string[],
|
||||
): BaseUIElement {
|
||||
const key = argument[0] ?? "value"
|
||||
return new VariableUiElement(
|
||||
|
@ -329,14 +326,37 @@ class TagsVis extends SpecialVisualization {
|
|||
return parsed.asHumanString(true, false, {})
|
||||
} catch (e) {
|
||||
return new FixedUiElement(
|
||||
"Could not parse this tag: " + JSON.stringify(value) + " due to " + e
|
||||
"Could not parse this tag: " + JSON.stringify(value) + " due to " + e,
|
||||
).SetClass("alert")
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class PointsInTimeVis extends SpecialVisualization {
|
||||
docs = "Creates a visualisation for 'points in time', e.g. collection times of a postbox"
|
||||
group = "data"
|
||||
funcName = "points_in_time"
|
||||
args = [
|
||||
{
|
||||
name: "key",
|
||||
required: true,
|
||||
doc: "The key out of which the points_in_time will be parsed",
|
||||
},
|
||||
]
|
||||
|
||||
constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>, args: string[], feature: Feature, layer: LayerConfig): BaseUIElement {
|
||||
const key = args[0]
|
||||
const points_in_time = tagSource.map(tags => tags[key])
|
||||
const times = points_in_time.map(times =>
|
||||
OH.createOhObject(<any>tagSource.data, times, tagSource.data["_country"], 1), [tagSource])
|
||||
return new VariableUiElement(times.map(times =>
|
||||
new SvelteUIElement(CollectionTimes, { times }),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
export class DataVisualisations {
|
||||
public static initList(): SpecialVisualization[] {
|
||||
return [
|
||||
|
@ -346,6 +366,7 @@ export class DataVisualisations {
|
|||
new DirectionIndicatorVis(),
|
||||
new OpeningHoursTableVis(),
|
||||
new OpeningHoursState(),
|
||||
new PointsInTimeVis(),
|
||||
new Canonical(),
|
||||
new LanguageElement(),
|
||||
new PresetDescription(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue