forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
827d9ae685
664 changed files with 33303 additions and 29790 deletions
|
|
@ -0,0 +1,48 @@
|
|||
<script lang="ts">/**
|
||||
* Multiple 'SingleCollectionTime'-rules togehter
|
||||
*/
|
||||
import { Stores, UIEventSource } from "../../../../Logic/UIEventSource"
|
||||
import SingleCollectionTime from "./SingleCollectionTime.svelte"
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { Lists } from "../../../../Utils/Lists"
|
||||
|
||||
export let value: UIEventSource<string>
|
||||
|
||||
let initialRules: string[] = Lists.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(Lists.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 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 { Lists } from "../../../../Utils/Lists"
|
||||
import { Translation } from "../../../i18n/Translation"
|
||||
|
||||
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= Lists.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 = Lists.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 => Lists.noNull(days.map((selected, i) => selected ? daysOfTheWeek[i] : undefined)))
|
||||
let valuesConcat: Store<string[]> = values.bindD(values => Stores.concat(values))
|
||||
.mapD(values => Lists.noEmpty(values))
|
||||
valuesConcat.mapD(times => {
|
||||
console.log(times)
|
||||
times = Lists.noNull(times)
|
||||
if (!times || times?.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
times?.sort(/*concatted, new array*/)
|
||||
return (Lists.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>
|
||||
|
|
@ -6,25 +6,35 @@
|
|||
import MaplibreMap from "../../Map/MaplibreMap.svelte"
|
||||
import Direction_stroke from "../../../assets/svg/Direction_stroke.svelte"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
|
||||
import type Feature from "geojson"
|
||||
import { GeoOperations } from "../../../Logic/GeoOperations"
|
||||
/**
|
||||
* A visualisation to pick a direction on a map background.
|
||||
*/
|
||||
export let value: UIEventSource<undefined | string>
|
||||
export let state: SpecialVisualizationState = undefined
|
||||
|
||||
export let mapProperties: Partial<MapProperties> & {
|
||||
readonly location: UIEventSource<{ lon: number; lat: number }>
|
||||
export let args: any[] = []
|
||||
export let feature: Feature
|
||||
let [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
let mapProperties: MapProperties = {
|
||||
location: new UIEventSource({ lon, lat }),
|
||||
zoom: new UIEventSource(args[0] !== undefined ? Number(args[0]) : 17),
|
||||
rasterLayer: state.mapProperties.rasterLayer,
|
||||
rotation: state.mapProperties.rotation,
|
||||
}
|
||||
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||
let mla = new MapLibreAdaptor(map, mapProperties)
|
||||
mla.allowMoving.setData(false)
|
||||
mla.allowZooming.setData(false)
|
||||
state?.mapProperties?.rasterLayer?.addCallbackAndRunD((l) => mla.rasterLayer.set(l))
|
||||
|
||||
let rotation = new UIEventSource(value.data)
|
||||
rotation.addCallbackD(rotation => {
|
||||
const r = (rotation + mapProperties.rotation.data + 360) % 360
|
||||
console.log("Setting value to", r)
|
||||
value.setData(""+Math.floor(r))
|
||||
}, [mapProperties.rotation])
|
||||
let directionElem: HTMLElement | undefined
|
||||
$: value.addCallbackAndRunD((degrees) => {
|
||||
if (directionElem === undefined) {
|
||||
$: rotation.addCallbackAndRunD((degrees) => {
|
||||
if (!directionElem?.style) {
|
||||
return
|
||||
}
|
||||
directionElem.style.rotate = degrees + "deg"
|
||||
|
|
@ -32,13 +42,14 @@
|
|||
|
||||
let mainElem: HTMLElement
|
||||
|
||||
|
||||
function onPosChange(x: number, y: number) {
|
||||
const rect = mainElem.getBoundingClientRect()
|
||||
const dx = -(rect.left + rect.right) / 2 + x
|
||||
const dy = (rect.top + rect.bottom) / 2 - y
|
||||
const angle = (180 * Math.atan2(dy, dx)) / Math.PI
|
||||
const angleGeo = Math.floor((450 - angle) % 360)
|
||||
value.setData("" + angleGeo)
|
||||
rotation.setData(angleGeo)
|
||||
}
|
||||
|
||||
let isDown = false
|
||||
|
|
@ -46,7 +57,7 @@
|
|||
|
||||
<div
|
||||
bind:this={mainElem}
|
||||
class="relative h-48 w-48 cursor-pointer overflow-hidden"
|
||||
class="relative h-48 min-w-48 w-full cursor-pointer overflow-hidden rounded-xl"
|
||||
on:click={(e) => onPosChange(e.x, e.y)}
|
||||
on:mousedown={(e) => {
|
||||
isDown = true
|
||||
|
|
@ -71,7 +82,7 @@
|
|||
<MaplibreMap mapProperties={mla} {map} />
|
||||
</div>
|
||||
|
||||
<div bind:this={directionElem} class="absolute left-0 top-0 h-full w-full">
|
||||
<div bind:this={directionElem} class="absolute left-0 top-0 h-full w-full p-1">
|
||||
<Direction_stroke />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
export let value: UIEventSource<number>
|
||||
export let feature: Feature
|
||||
|
|
@ -84,7 +85,7 @@
|
|||
},
|
||||
]
|
||||
},
|
||||
[mapLocation]
|
||||
[mapLocation], onDestroy
|
||||
)
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
forceIndex = floors.data.indexOf(level)
|
||||
})
|
||||
|
||||
Stores.Chronic(50).addCallback(() => stabilize())
|
||||
Stores.chronic(50).addCallback(() => stabilize())
|
||||
floors.addCallback((floors) => {
|
||||
forceIndex = floors.findIndex((s) => s === value.data)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
let element: HTMLTableElement
|
||||
|
||||
function range(n: number) {
|
||||
return Utils.TimesT(n, (n) => n)
|
||||
return Utils.timesT(n, (n) => n)
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { GeoOperations } from "../../../Logic/GeoOperations"
|
||||
import If from "../../Base/If.svelte"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
/**
|
||||
* The value exported to outside, saved only when the button is pressed
|
||||
|
|
@ -61,7 +62,7 @@
|
|||
} else {
|
||||
return -1
|
||||
}
|
||||
})
|
||||
}, onDestroy)
|
||||
|
||||
beta.map(
|
||||
(beta) => {
|
||||
|
|
@ -77,7 +78,7 @@
|
|||
previewDegrees.setData(beta + "°")
|
||||
previewPercentage.setData(degreesToPercentage(beta))
|
||||
},
|
||||
[valuesign, beta]
|
||||
[valuesign, beta], onDestroy
|
||||
)
|
||||
|
||||
function onSave() {
|
||||
|
|
|
|||
10
src/UI/InputElement/Helpers/TimeInput.svelte
Normal file
10
src/UI/InputElement/Helpers/TimeInput.svelte
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* Simple wrapper around the HTML-time field.
|
||||
*/
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
|
||||
export let value: UIEventSource<undefined | string>
|
||||
</script>
|
||||
|
||||
<input bind:value={$value} type="time" />
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import Translations from "../../i18n/Translations"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import { ImmutableStore, Store, Stores, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import Wikidata, { WikidataResponse } from "../../../Logic/Web/Wikidata"
|
||||
import Locale from "../../i18n/Locale"
|
||||
import Loading from "../../Base/Loading.svelte"
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
import { Utils } from "../../../Utils"
|
||||
import WikidataValidator from "../Validators/WikidataValidator"
|
||||
import Searchbar from "../../Base/Searchbar.svelte"
|
||||
import { Lists } from "../../../Utils/Lists"
|
||||
|
||||
const t = Translations.t.general.wikipedia
|
||||
|
||||
|
|
@ -64,7 +65,7 @@
|
|||
})
|
||||
WikidataValidator._searchCache.set(key, promise)
|
||||
}
|
||||
return Stores.FromPromiseWithErr(promise)
|
||||
return UIEventSource.fromPromiseWithErr(promise)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -80,7 +81,7 @@
|
|||
seen.push(item)
|
||||
}
|
||||
}
|
||||
return Utils.NoNull(seen).filter((i) => !knownIds.has(i.id))
|
||||
return Lists.noNull(seen).filter((i) => !knownIds.has(i.id))
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
54
src/UI/InputElement/Helpers/WikidataInputHelper.svelte
Normal file
54
src/UI/InputElement/Helpers/WikidataInputHelper.svelte
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
*
|
||||
Wrapper around 'WikidataInput.svelte' which handles the arguments
|
||||
*/
|
||||
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import Locale from "../../i18n/Locale"
|
||||
import { Utils } from "../../../Utils"
|
||||
import Wikidata from "../../../Logic/Web/Wikidata"
|
||||
import WikidataInput from "../Helpers/WikidataInput.svelte"
|
||||
import type { Feature } from "geojson"
|
||||
import { onDestroy } from "svelte"
|
||||
import WikidataValidator from "../Validators/WikidataValidator"
|
||||
import { Lists } from "../../../Utils/Lists"
|
||||
|
||||
export let args: (string | number | boolean)[] = []
|
||||
export let feature: Feature
|
||||
|
||||
export let value: UIEventSource<string>
|
||||
|
||||
let searchKey: string = <string>args[0] ?? "name"
|
||||
|
||||
let searchFor: string =
|
||||
searchKey
|
||||
.split(";")
|
||||
.map((k) => feature?.properties[k]?.toLowerCase())
|
||||
.find((foundValue) => !!foundValue) ?? ""
|
||||
|
||||
const options: any = args[1]
|
||||
|
||||
let searchForValue: UIEventSource<string> = new UIEventSource(searchFor)
|
||||
|
||||
onDestroy(
|
||||
Locale.language.addCallbackAndRunD((lg) => {
|
||||
console.log(options)
|
||||
if (searchFor !== undefined && options !== undefined) {
|
||||
const post = options["removePostfixes"] ?? []
|
||||
const pre = options["removePrefixes"] ?? []
|
||||
const clipped = WikidataValidator.removePostAndPrefixes(searchFor, pre, post, lg)
|
||||
console.log("Got clipped value:", clipped, post, pre)
|
||||
searchForValue.setData(clipped)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
let instanceOf: number[] = Lists.noNull((options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)))
|
||||
let notInstanceOf: number[] = Lists.noNull((options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)))
|
||||
|
||||
let allowMultipleArg = options?.["multiple"] ?? "yes"
|
||||
let allowMultiple = allowMultipleArg === "yes" || "" + allowMultipleArg === "true"
|
||||
</script>
|
||||
|
||||
<WikidataInput searchValue={searchForValue} {value} {instanceOf} {notInstanceOf} {allowMultiple} />
|
||||
Loading…
Add table
Add a link
Reference in a new issue