forked from MapComplete/MapComplete
Merge branch 'develop' into Robin-patch-1
This commit is contained in:
commit
0a812b0236
597 changed files with 18999 additions and 15074 deletions
|
@ -156,8 +156,8 @@
|
|||
</script>
|
||||
|
||||
<main>
|
||||
<div class="w-full low-interaction">
|
||||
<InsetSpacer height={AndroidPolyfill.getInsetSizes().top}/>
|
||||
<div class="low-interaction w-full">
|
||||
<InsetSpacer height={AndroidPolyfill.getInsetSizes().top} />
|
||||
</div>
|
||||
|
||||
<DrawerLeft shown={guistate.pageStates.menu}>
|
||||
|
@ -186,7 +186,7 @@
|
|||
<div class="link-underline flex w-full flex-col">
|
||||
<div class="flex items-center">
|
||||
<div class="m-1 flex-none md:hidden">
|
||||
<Logo alt="MapComplete Logo" class="mr-1 sm:mr-2 md:mr-4 h-10 w-10 sm:h-20 sm:w-20" />
|
||||
<Logo alt="MapComplete Logo" class="mr-1 h-10 w-10 sm:mr-2 sm:h-20 sm:w-20 md:mr-4" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<h1 class="m-0 break-words font-extrabold tracking-tight md:text-6xl">
|
||||
|
@ -275,8 +275,7 @@
|
|||
v{Constants.vNumber}
|
||||
</div>
|
||||
|
||||
<InsetSpacer height={AndroidPolyfill.getInsetSizes().bottom}/>
|
||||
|
||||
<InsetSpacer height={AndroidPolyfill.getInsetSizes().bottom} />
|
||||
|
||||
<div class="absolute top-0 h-0 w-0" style="margin-left: -10em">
|
||||
<MenuDrawer onlyLink={false} state={menuDrawerState} />
|
||||
|
|
|
@ -36,11 +36,10 @@ export interface TagRenderingChartOptions {
|
|||
groupToOtherCutoff?: 3 | number
|
||||
sort?: boolean
|
||||
hideUnkown?: boolean
|
||||
hideNotApplicable?: boolean,
|
||||
hideNotApplicable?: boolean
|
||||
chartType?: "pie" | "bar" | "doughnut"
|
||||
}
|
||||
export class ChartJsUtils {
|
||||
|
||||
/**
|
||||
* Gets the 'date' out of all features.properties,
|
||||
* returns a range with all dates from 'earliest' to 'latest' as to get one continuous range
|
||||
|
@ -178,7 +177,7 @@ export class ChartJsUtils {
|
|||
}
|
||||
data.push(...categoryCounts, ...otherData)
|
||||
labels.push(...(mappings?.map((m) => m.then.txt) ?? []), ...otherLabels)
|
||||
if(data.length === 0){
|
||||
if (data.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
return { labels, data }
|
||||
|
@ -196,139 +195,139 @@ export class ChartJsUtils {
|
|||
* @param options
|
||||
*/
|
||||
static createPerDayConfigForTagRendering(
|
||||
tr: TagRenderingConfig,
|
||||
features: (OsmFeature & { properties: { date: string } })[],
|
||||
options?: {
|
||||
period: "day" | "month"
|
||||
groupToOtherCutoff?: 3 | number
|
||||
// If given, take the sum of these fields to get the feature weight
|
||||
sumFields?: ReadonlyArray<string>
|
||||
hideUnknown?: boolean
|
||||
hideNotApplicable?: boolean
|
||||
}
|
||||
tr: TagRenderingConfig,
|
||||
features: (OsmFeature & { properties: { date: string } })[],
|
||||
options?: {
|
||||
period: "day" | "month"
|
||||
groupToOtherCutoff?: 3 | number
|
||||
// If given, take the sum of these fields to get the feature weight
|
||||
sumFields?: ReadonlyArray<string>
|
||||
hideUnknown?: boolean
|
||||
hideNotApplicable?: boolean
|
||||
}
|
||||
): ChartConfiguration {
|
||||
const { labels, data } = ChartJsUtils.extractDataAndLabels(tr, features, {
|
||||
sort: true,
|
||||
groupToOtherCutoff: options?.groupToOtherCutoff,
|
||||
hideNotApplicable: options?.hideNotApplicable,
|
||||
hideUnkown: options?.hideUnknown,
|
||||
})
|
||||
if (labels === undefined || data === undefined) {
|
||||
console.error(
|
||||
"Could not extract data and labels for ",
|
||||
tr,
|
||||
" with features",
|
||||
features,
|
||||
": no labels or no data"
|
||||
)
|
||||
throw "No labels or data given..."
|
||||
const { labels, data } = ChartJsUtils.extractDataAndLabels(tr, features, {
|
||||
sort: true,
|
||||
groupToOtherCutoff: options?.groupToOtherCutoff,
|
||||
hideNotApplicable: options?.hideNotApplicable,
|
||||
hideUnkown: options?.hideUnknown,
|
||||
})
|
||||
if (labels === undefined || data === undefined) {
|
||||
console.error(
|
||||
"Could not extract data and labels for ",
|
||||
tr,
|
||||
" with features",
|
||||
features,
|
||||
": no labels or no data"
|
||||
)
|
||||
throw "No labels or data given..."
|
||||
}
|
||||
|
||||
for (let i = labels.length; i >= 0; i--) {
|
||||
if (data[i]?.length != 0) {
|
||||
continue
|
||||
}
|
||||
data.splice(i, 1)
|
||||
labels.splice(i, 1)
|
||||
}
|
||||
|
||||
const datasets: {
|
||||
label: string /*themename*/
|
||||
data: number[] /*counts per day*/
|
||||
backgroundColor: string
|
||||
}[] = []
|
||||
const allDays = ChartJsUtils.getAllDays(features)
|
||||
let trimmedDays = allDays.map((d) => d.substring(0, 10))
|
||||
if (options?.period === "month") {
|
||||
trimmedDays = trimmedDays.map((d) => d.substring(0, 7))
|
||||
}
|
||||
trimmedDays = Lists.dedup(trimmedDays)
|
||||
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
const label = labels[i]
|
||||
const changesetsForTheme = data[i]
|
||||
const perDay: Record<string, OsmFeature[]> = {}
|
||||
for (const changeset of changesetsForTheme) {
|
||||
const csDate = new Date(changeset.properties.date)
|
||||
Utils.SetMidnight(csDate)
|
||||
let str = csDate.toISOString()
|
||||
str = str.substr(0, 10)
|
||||
if (options?.period === "month") {
|
||||
str = str.substr(0, 7)
|
||||
}
|
||||
if (perDay[str] === undefined) {
|
||||
perDay[str] = [changeset]
|
||||
} else {
|
||||
perDay[str].push(changeset)
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = labels.length; i >= 0; i--) {
|
||||
if (data[i]?.length != 0) {
|
||||
const countsPerDay: number[] = []
|
||||
for (let i = 0; i < trimmedDays.length; i++) {
|
||||
const day = trimmedDays[i]
|
||||
|
||||
const featuresForDay = perDay[day]
|
||||
if (!featuresForDay) {
|
||||
continue
|
||||
}
|
||||
data.splice(i, 1)
|
||||
labels.splice(i, 1)
|
||||
}
|
||||
|
||||
const datasets: {
|
||||
label: string /*themename*/
|
||||
data: number[] /*counts per day*/
|
||||
backgroundColor: string
|
||||
}[] = []
|
||||
const allDays = ChartJsUtils.getAllDays(features)
|
||||
let trimmedDays = allDays.map((d) => d.substring(0, 10))
|
||||
if (options?.period === "month") {
|
||||
trimmedDays = trimmedDays.map((d) => d.substring(0, 7))
|
||||
}
|
||||
trimmedDays = Lists.dedup(trimmedDays)
|
||||
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
const label = labels[i]
|
||||
const changesetsForTheme = data[i]
|
||||
const perDay: Record<string, OsmFeature[]> = {}
|
||||
for (const changeset of changesetsForTheme) {
|
||||
const csDate = new Date(changeset.properties.date)
|
||||
Utils.SetMidnight(csDate)
|
||||
let str = csDate.toISOString()
|
||||
str = str.substr(0, 10)
|
||||
if (options?.period === "month") {
|
||||
str = str.substr(0, 7)
|
||||
}
|
||||
if (perDay[str] === undefined) {
|
||||
perDay[str] = [changeset]
|
||||
} else {
|
||||
perDay[str].push(changeset)
|
||||
}
|
||||
}
|
||||
|
||||
const countsPerDay: number[] = []
|
||||
for (let i = 0; i < trimmedDays.length; i++) {
|
||||
const day = trimmedDays[i]
|
||||
|
||||
const featuresForDay = perDay[day]
|
||||
if (!featuresForDay) {
|
||||
continue
|
||||
}
|
||||
if (options.sumFields !== undefined) {
|
||||
let sum = 0
|
||||
for (const featuresForDayElement of featuresForDay) {
|
||||
const props = featuresForDayElement.properties
|
||||
for (const key of options.sumFields) {
|
||||
if (!props[key]) {
|
||||
continue
|
||||
}
|
||||
const v = Number(props[key])
|
||||
if (isNaN(v)) {
|
||||
continue
|
||||
}
|
||||
sum += v
|
||||
if (options.sumFields !== undefined) {
|
||||
let sum = 0
|
||||
for (const featuresForDayElement of featuresForDay) {
|
||||
const props = featuresForDayElement.properties
|
||||
for (const key of options.sumFields) {
|
||||
if (!props[key]) {
|
||||
continue
|
||||
}
|
||||
const v = Number(props[key])
|
||||
if (isNaN(v)) {
|
||||
continue
|
||||
}
|
||||
sum += v
|
||||
}
|
||||
countsPerDay[i] = sum
|
||||
} else {
|
||||
countsPerDay[i] = featuresForDay?.length ?? 0
|
||||
}
|
||||
countsPerDay[i] = sum
|
||||
} else {
|
||||
countsPerDay[i] = featuresForDay?.length ?? 0
|
||||
}
|
||||
let backgroundColor =
|
||||
ChartJsColours.borderColors[i % ChartJsColours.borderColors.length]
|
||||
if (label === "Unknown") {
|
||||
backgroundColor = ChartJsColours.unknownBorderColor
|
||||
}
|
||||
if (label === "Other") {
|
||||
backgroundColor = ChartJsColours.otherBorderColor
|
||||
}
|
||||
datasets.push({
|
||||
data: countsPerDay,
|
||||
backgroundColor,
|
||||
label,
|
||||
})
|
||||
}
|
||||
|
||||
const perDayData = {
|
||||
labels: trimmedDays,
|
||||
datasets,
|
||||
let backgroundColor =
|
||||
ChartJsColours.borderColors[i % ChartJsColours.borderColors.length]
|
||||
if (label === "Unknown") {
|
||||
backgroundColor = ChartJsColours.unknownBorderColor
|
||||
}
|
||||
if (label === "Other") {
|
||||
backgroundColor = ChartJsColours.otherBorderColor
|
||||
}
|
||||
datasets.push({
|
||||
data: countsPerDay,
|
||||
backgroundColor,
|
||||
label,
|
||||
})
|
||||
}
|
||||
|
||||
return <ChartConfiguration>{
|
||||
type: "bar",
|
||||
data: perDayData,
|
||||
options: {
|
||||
responsive: true,
|
||||
legend: {
|
||||
display: false,
|
||||
const perDayData = {
|
||||
labels: trimmedDays,
|
||||
datasets,
|
||||
}
|
||||
|
||||
return <ChartConfiguration>{
|
||||
type: "bar",
|
||||
data: perDayData,
|
||||
options: {
|
||||
responsive: true,
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -336,17 +335,16 @@ export class ChartJsUtils {
|
|||
*
|
||||
* @returns undefined if not enough parameters
|
||||
*/
|
||||
static createConfigForTagRendering<T extends { properties: Record<string, string> }>(tagRendering: TagRenderingConfig, features: T[],
|
||||
options?: TagRenderingChartOptions): ChartConfiguration{
|
||||
static createConfigForTagRendering<T extends { properties: Record<string, string> }>(
|
||||
tagRendering: TagRenderingConfig,
|
||||
features: T[],
|
||||
options?: TagRenderingChartOptions
|
||||
): ChartConfiguration {
|
||||
if (tagRendering.mappings?.length === 0 && tagRendering.freeform?.key === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const { labels, data } = ChartJsUtils.extractDataAndLabels(
|
||||
tagRendering,
|
||||
features,
|
||||
options
|
||||
)
|
||||
const { labels, data } = ChartJsUtils.extractDataAndLabels(tagRendering, features, options)
|
||||
if (labels === undefined || data === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
@ -403,22 +401,18 @@ export class ChartJsUtils {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static createHistogramConfig(keys: string[], counts: Map<string, number>){
|
||||
|
||||
const borderColor = [
|
||||
]
|
||||
const backgroundColor = [
|
||||
]
|
||||
static createHistogramConfig(keys: string[], counts: Map<string, number>) {
|
||||
const borderColor = []
|
||||
const backgroundColor = []
|
||||
|
||||
while (borderColor.length < keys.length) {
|
||||
borderColor.push(...ChartJsColours.borderColors)
|
||||
backgroundColor.push(...ChartJsColours.backgroundColors)
|
||||
}
|
||||
|
||||
return <ChartConfiguration>{
|
||||
return <ChartConfiguration>{
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: keys,
|
||||
|
@ -432,11 +426,12 @@ export class ChartJsUtils {
|
|||
},
|
||||
],
|
||||
},
|
||||
options: { scales: {
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
ticks: {
|
||||
stepSize: 1,
|
||||
callback: (value) =>Number(value).toFixed(0),
|
||||
callback: (value) => Number(value).toFixed(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
export let state: ThemeViewState = undefined
|
||||
|
||||
async function shareCurrentLink() {
|
||||
const title = state?.theme?.title?.txt ?? "MapComplete";
|
||||
const title = state?.theme?.title?.txt ?? "MapComplete"
|
||||
const textToShow = state?.theme?.description?.txt ?? ""
|
||||
await navigator.share({
|
||||
title,
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
export let size = "w-12 h-12"
|
||||
</script>
|
||||
<div class={size+" relative"}>
|
||||
<div class="absolute top-0 left-0">
|
||||
|
||||
<div class={size + " relative"}>
|
||||
<div class="absolute left-0 top-0">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="absolute top-0 left-0">
|
||||
<div class="absolute left-0 top-0">
|
||||
<Cross_bottom_right class={size} color="red" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -32,7 +32,10 @@
|
|||
return { bearing, dist }
|
||||
}
|
||||
)
|
||||
let bearingFromGps = state.geolocation.geolocationState.currentGPSLocation.mapD((coordinate) => GeoOperations.bearing([coordinate.longitude, coordinate.latitude], fcenter),onDestroy)
|
||||
let bearingFromGps = state.geolocation.geolocationState.currentGPSLocation.mapD(
|
||||
(coordinate) => GeoOperations.bearing([coordinate.longitude, coordinate.latitude], fcenter),
|
||||
onDestroy
|
||||
)
|
||||
let compass = Orientation.singleton.alpha
|
||||
|
||||
let relativeDirections = Translations.t.general.visualFeedback.directionsRelative
|
||||
|
@ -102,7 +105,8 @@
|
|||
})
|
||||
return mainTr.textFor(lang)
|
||||
},
|
||||
[compass, Locale.language], onDestroy
|
||||
[compass, Locale.language],
|
||||
onDestroy
|
||||
)
|
||||
|
||||
let label = labelFromCenter.map(
|
||||
|
@ -115,7 +119,8 @@
|
|||
}
|
||||
return labelFromCenter
|
||||
},
|
||||
[labelFromGps], onDestroy
|
||||
[labelFromGps],
|
||||
onDestroy
|
||||
)
|
||||
|
||||
function focusMap() {
|
||||
|
|
|
@ -21,14 +21,13 @@
|
|||
// Does not need a 'top-inset-spacer' as the code below will apply the padding automatically
|
||||
let height = 0
|
||||
|
||||
function setHeight(){
|
||||
function setHeight() {
|
||||
let topbar = document.getElementById("top-bar")
|
||||
height = (topbar?.clientHeight ?? 0) + AndroidPolyfill.getInsetSizes().top.data
|
||||
}
|
||||
|
||||
onMount(() => setHeight())
|
||||
AndroidPolyfill.getInsetSizes().top.addCallback(() => setHeight())
|
||||
|
||||
</script>
|
||||
|
||||
<Drawer
|
||||
|
@ -43,15 +42,14 @@
|
|||
rightOffset="inset-y-0 right-0"
|
||||
bind:hidden
|
||||
>
|
||||
<div class="flex flex-col h-full">
|
||||
|
||||
<div class="low-interaction h-screen">
|
||||
<div style={`padding-top: ${height}px`}>
|
||||
<div class="flex h-full flex-col overflow-y-auto">
|
||||
<slot />
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="low-interaction h-screen">
|
||||
<div style={`padding-top: ${height}px`}>
|
||||
<div class="flex h-full flex-col overflow-y-auto">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<InsetSpacer clss="low-interaction" height={AndroidPolyfill.getInsetSizes().bottom}/>
|
||||
<InsetSpacer clss="low-interaction" height={AndroidPolyfill.getInsetSizes().bottom} />
|
||||
</div>
|
||||
</Drawer>
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
<div
|
||||
class="pointer-events-none absolute bottom-0 right-0 h-full w-screen p-4 md:p-6"
|
||||
style="z-index: 21"
|
||||
on:click={() => { dispatch("close") }}
|
||||
on:click={() => {
|
||||
dispatch("close")
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="content normal-background pointer-events-auto relative h-full"
|
||||
|
@ -44,9 +46,9 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: hidden;
|
||||
box-shadow: 0 0 1rem #00000088;
|
||||
}
|
||||
.content {
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: hidden;
|
||||
box-shadow: 0 0 1rem #00000088;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,4 +8,4 @@
|
|||
export let clss: string = ""
|
||||
</script>
|
||||
|
||||
<div class={clss+" shrink-0"} style={"height: "+$height+"px"} />
|
||||
<div class={clss + " shrink-0"} style={"height: " + $height + "px"} />
|
||||
|
|
|
@ -40,15 +40,13 @@
|
|||
{#if $badge}
|
||||
{#if !$online}
|
||||
{#if !hiddenFail}
|
||||
<div class="alert">
|
||||
Your device is offline
|
||||
</div>
|
||||
<div class="alert">Your device is offline</div>
|
||||
{/if}
|
||||
{:else if !ignoreLoading && !hiddenFail && $loadingStatus === "loading"}
|
||||
<slot name="loading">
|
||||
<Loading />
|
||||
</slot>
|
||||
{:else if ($loadingStatus === "error" || $apiState === "readonly" || $apiState === "offline")}
|
||||
{:else if $loadingStatus === "error" || $apiState === "readonly" || $apiState === "offline"}
|
||||
{#if !hiddenFail}
|
||||
<slot name="error">
|
||||
<div class="alert flex flex-col items-center">
|
||||
|
|
|
@ -34,16 +34,16 @@
|
|||
</script>
|
||||
|
||||
{#if $showButton}
|
||||
<button class="as-link sidebar-button" on:click={openJosm}>
|
||||
<Josm_logo class="h-6 w-6" />
|
||||
<Tr t={t.editJosm} />
|
||||
</button>
|
||||
<button class="as-link sidebar-button" on:click={openJosm}>
|
||||
<Josm_logo class="h-6 w-6" />
|
||||
<Tr t={t.editJosm} />
|
||||
</button>
|
||||
|
||||
{#if $josmState === undefined}
|
||||
<!-- empty -->
|
||||
{:else if $josmState === "OK"}
|
||||
<Tr cls="thanks shrink-0 w-fit" t={t.josmOpened} />
|
||||
{:else}
|
||||
<Tr cls="alert shrink-0 w-fit" t={t.josmNotOpened} />
|
||||
{/if}
|
||||
{#if $josmState === undefined}
|
||||
<!-- empty -->
|
||||
{:else if $josmState === "OK"}
|
||||
<Tr cls="thanks shrink-0 w-fit" t={t.josmOpened} />
|
||||
{:else}
|
||||
<Tr cls="alert shrink-0 w-fit" t={t.josmNotOpened} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
|
||||
const shared =
|
||||
"in-page normal-background dark:bg-gray-800 rounded-lg border-gray-200 dark:border-gray-700 border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md"
|
||||
let defaultClass = "relative flex flex-col mx-auto w-full divide-y border-4 border-red-500 " + shared
|
||||
let defaultClass =
|
||||
"relative flex flex-col mx-auto w-full divide-y border-4 border-red-500 " + shared
|
||||
if (fullscreen) {
|
||||
defaultClass = shared
|
||||
}
|
||||
|
@ -43,7 +44,6 @@
|
|||
})
|
||||
let marginTop = AndroidPolyfill.getInsetSizes().top
|
||||
let marginBottom = AndroidPolyfill.getInsetSizes().bottom
|
||||
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
let layer = state.getMatchingLayer(selected.properties)
|
||||
|
||||
let stillMatches = tags.map(
|
||||
(tags) => !layer?.source?.osmTags || layer?.source?.osmTags?.matchesProperties(tags), onDestroy
|
||||
(tags) => !layer?.source?.osmTags || layer?.source?.osmTags?.matchesProperties(tags),
|
||||
onDestroy
|
||||
)
|
||||
onDestroy(
|
||||
stillMatches.addCallbackAndRunD((matches) => {
|
||||
|
|
|
@ -2,7 +2,6 @@ import BaseUIElement from "../BaseUIElement"
|
|||
|
||||
import { SvelteComponentTyped } from "svelte"
|
||||
|
||||
|
||||
/**
|
||||
* The SvelteUIComponent serves as a translating class which which wraps a SvelteElement into the BaseUIElement framework.
|
||||
* Also see ToSvelte.svelte for the opposite conversion
|
||||
|
|
|
@ -79,7 +79,6 @@ export default abstract class BaseUIElement {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
return el
|
||||
} catch (e) {
|
||||
const domExc = e as DOMException
|
||||
|
|
|
@ -1,56 +1,68 @@
|
|||
<script lang="ts">/**
|
||||
* Show a compass. The compass outline rotates with the map, the compass needle points to the actual north
|
||||
*/
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Orientation } from "../../Sensors/Orientation"
|
||||
import Compass_back from "../../assets/svg/Compass_back.svelte"
|
||||
import Compass_needle from "../../assets/svg/Compass_needle.svelte"
|
||||
import North_arrow from "../../assets/svg/North_arrow.svelte"
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Show a compass. The compass outline rotates with the map, the compass needle points to the actual north
|
||||
*/
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Orientation } from "../../Sensors/Orientation"
|
||||
import Compass_back from "../../assets/svg/Compass_back.svelte"
|
||||
import Compass_needle from "../../assets/svg/Compass_needle.svelte"
|
||||
import North_arrow from "../../assets/svg/North_arrow.svelte"
|
||||
|
||||
export let mapProperties: { rotation: UIEventSource<number>, allowRotating: Store<boolean> }
|
||||
let orientation = Orientation.singleton.alpha
|
||||
let gotNonZero = new UIEventSource(false)
|
||||
orientation.addCallbackAndRunD(o => {
|
||||
if (o !== undefined && o !== 0) {
|
||||
gotNonZero.set(true)
|
||||
}
|
||||
})
|
||||
let mapRotation = mapProperties.rotation
|
||||
export let size = "h-10 w-10"
|
||||
let wrapperClass = "absolute top-0 left-0 " + size
|
||||
let compassLoaded = Orientation.singleton.gotMeasurement
|
||||
let allowRotation = mapProperties.allowRotating
|
||||
|
||||
function clicked(e: Event) {
|
||||
if (mapProperties.rotation.data === 0) {
|
||||
if (mapProperties.allowRotating.data && compassLoaded.data) {
|
||||
mapProperties.rotation.set(orientation.data)
|
||||
export let mapProperties: { rotation: UIEventSource<number>; allowRotating: Store<boolean> }
|
||||
let orientation = Orientation.singleton.alpha
|
||||
let gotNonZero = new UIEventSource(false)
|
||||
orientation.addCallbackAndRunD((o) => {
|
||||
if (o !== undefined && o !== 0) {
|
||||
gotNonZero.set(true)
|
||||
}
|
||||
} else {
|
||||
mapProperties.rotation.set(0)
|
||||
}
|
||||
}
|
||||
})
|
||||
let mapRotation = mapProperties.rotation
|
||||
export let size = "h-10 w-10"
|
||||
let wrapperClass = "absolute top-0 left-0 " + size
|
||||
let compassLoaded = Orientation.singleton.gotMeasurement
|
||||
let allowRotation = mapProperties.allowRotating
|
||||
|
||||
function clicked(e: Event) {
|
||||
if (mapProperties.rotation.data === 0) {
|
||||
if (mapProperties.allowRotating.data && compassLoaded.data) {
|
||||
mapProperties.rotation.set(orientation.data)
|
||||
}
|
||||
} else {
|
||||
mapProperties.rotation.set(0)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $allowRotation || $gotNonZero}
|
||||
<button style="z-index: -1" class={"relative as-link pointer-events-auto "+size} on:click={(e) => clicked(e)}>
|
||||
{#if $allowRotation && !$compassLoaded && !$gotNonZero}
|
||||
<div class={"border-2 rounded-full border-gray-500 border-dotted "+wrapperClass}
|
||||
style={`transform: rotate(${-$mapRotation}deg); transition: transform linear 500ms`}>
|
||||
<North_arrow class="w-full" />
|
||||
</div>
|
||||
{:else}
|
||||
{#if $allowRotation}
|
||||
<div class={wrapperClass} style={`transform: rotate(${-$mapRotation}deg); transition: transform linear 500ms`}>
|
||||
<Compass_back class="w-full" />
|
||||
<button
|
||||
style="z-index: -1"
|
||||
class={"as-link pointer-events-auto relative " + size}
|
||||
on:click={(e) => clicked(e)}
|
||||
>
|
||||
{#if $allowRotation && !$compassLoaded && !$gotNonZero}
|
||||
<div
|
||||
class={"rounded-full border-2 border-dotted border-gray-500 " + wrapperClass}
|
||||
style={`transform: rotate(${-$mapRotation}deg); transition: transform linear 500ms`}
|
||||
>
|
||||
<North_arrow class="w-full" />
|
||||
</div>
|
||||
{:else}
|
||||
{#if $allowRotation}
|
||||
<div
|
||||
class={wrapperClass}
|
||||
style={`transform: rotate(${-$mapRotation}deg); transition: transform linear 500ms`}
|
||||
>
|
||||
<Compass_back class="w-full" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $compassLoaded && $gotNonZero}
|
||||
<div
|
||||
class={wrapperClass + (!$allowRotation ? " rounded-full bg-white bg-opacity-50" : "")}
|
||||
style={`transform: rotate(${-$orientation}deg)`}
|
||||
>
|
||||
<Compass_needle class="w-full" />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if $compassLoaded && $gotNonZero}
|
||||
<div class={wrapperClass+ (!$allowRotation ? " rounded-full bg-white bg-opacity-50" : "")}
|
||||
style={`transform: rotate(${-$orientation}deg)`}>
|
||||
<Compass_needle class="w-full" />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</button>
|
||||
</button>
|
||||
{/if}
|
||||
|
|
|
@ -151,12 +151,12 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-8 flex flex-col items-center gap-x-2 border-t border-dashed border-gray-300 pt-4">
|
||||
<div class="mr-4 flex w-96 flex-wrap md:flex-nowrap items-center justify-center">
|
||||
<div class="mr-4 flex w-96 flex-wrap items-center justify-center md:flex-nowrap">
|
||||
<a href="https://nlnet.nl/entrust" class="p-2">
|
||||
<NGI0Entrust_tag class="grow-0 w-48"/>
|
||||
<NGI0Entrust_tag class="w-48 grow-0" />
|
||||
</a>
|
||||
<a href="https://nlnet.nl" class="p-2">
|
||||
<Nlnet class="grow-0 w-48"/>
|
||||
<Nlnet class="w-48 grow-0" />
|
||||
</a>
|
||||
</div>
|
||||
<span>
|
||||
|
|
|
@ -6,29 +6,31 @@
|
|||
import { Lists } from "../../Utils/Lists"
|
||||
|
||||
export let values: Store<string[]>
|
||||
let counts: Store<Map<string, number>> = values.map(
|
||||
(values) => {
|
||||
if (values === undefined) {
|
||||
return undefined
|
||||
}
|
||||
let counts: Store<Map<string, number>> = values.map((values) => {
|
||||
if (values === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
values = Utils.noNull(values)
|
||||
const counts = new Map<string, number>()
|
||||
for (const value of values) {
|
||||
const c = counts.get(value) ?? 0
|
||||
counts.set(value, c + 1)
|
||||
}
|
||||
values = Utils.noNull(values)
|
||||
const counts = new Map<string, number>()
|
||||
for (const value of values) {
|
||||
const c = counts.get(value) ?? 0
|
||||
counts.set(value, c + 1)
|
||||
}
|
||||
|
||||
return counts
|
||||
})
|
||||
return counts
|
||||
})
|
||||
|
||||
let max: Store<number> = counts.mapD(counts => Math.max(...Array.from(counts.values())))
|
||||
let keys: Store<string> = counts.mapD(counts => {
|
||||
let max: Store<number> = counts.mapD((counts) => Math.max(...Array.from(counts.values())))
|
||||
let keys: Store<string> = counts.mapD((counts) => {
|
||||
const keys = Lists.dedup(counts.keys())
|
||||
keys.sort(/*inplace sort*/)
|
||||
return keys
|
||||
})
|
||||
let config: Store<ChartConfiguration> = keys.mapD(keys => ChartJsUtils.createHistogramConfig(keys, counts.data), [counts])
|
||||
let config: Store<ChartConfiguration> = keys.mapD(
|
||||
(keys) => ChartJsUtils.createHistogramConfig(keys, counts.data),
|
||||
[counts]
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if $config}
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
}
|
||||
})
|
||||
let hotkeys = Hotkeys._docs
|
||||
|
||||
</script>
|
||||
|
||||
<div class:h-0={!onlyLink} class:h-full={onlyLink} class="overflow-hidden">
|
||||
|
|
|
@ -95,7 +95,6 @@
|
|||
let nrOfFailedImages = ImageUploadQueue.singleton.imagesInQueue
|
||||
let failedImagesOpen = pg.failedImages
|
||||
let loggedIn = state.osmConnection.isLoggedIn
|
||||
|
||||
</script>
|
||||
|
||||
<div class="low-interaction flex h-full flex-col overflow-hidden" class:hidden={!$shown}>
|
||||
|
@ -129,14 +128,13 @@
|
|||
<SidebarUnit>
|
||||
<LoginToggle {state}>
|
||||
<LoginButton clss="primary" osmConnection={state.osmConnection} slot="not-logged-in" />
|
||||
<div class="flex items-center gap-x-4 w-full m-2">
|
||||
<div class="m-2 flex w-full items-center gap-x-4">
|
||||
{#if $userdetails.img}
|
||||
<img alt="avatar" src={$userdetails.img} class="h-12 w-12 rounded-full" />
|
||||
{:else}
|
||||
<UserCircle class="h-14 w-14" color="gray"/>
|
||||
{:else}
|
||||
<UserCircle class="h-14 w-14" color="gray" />
|
||||
{/if}
|
||||
<div class="flex flex-col w-full gap-y-2">
|
||||
|
||||
<div class="flex w-full flex-col gap-y-2">
|
||||
<b>{$userdetails.name}</b>
|
||||
<LogoutButton clss="as-link small subtle text-sm" osmConnection={state.osmConnection} />
|
||||
</div>
|
||||
|
@ -205,7 +203,8 @@
|
|||
|
||||
<LanguagePicker
|
||||
preferredLanguages={state.userRelatedState.osmConnection.userDetails.mapD(
|
||||
(ud) => ud.languages, onDestroy
|
||||
(ud) => ud.languages,
|
||||
onDestroy
|
||||
)}
|
||||
/>
|
||||
</SidebarUnit>
|
||||
|
@ -219,12 +218,7 @@
|
|||
<Tr t={Translations.t.general.menu.aboutMapComplete} />
|
||||
</h3>
|
||||
|
||||
<a
|
||||
class="flex"
|
||||
href={($isAndroid
|
||||
? "https://mapcomplete.org"
|
||||
: ".")+"/studio.html"}
|
||||
>
|
||||
<a class="flex" href={($isAndroid ? "https://mapcomplete.org" : ".") + "/studio.html"}>
|
||||
<Pencil class="mr-2 h-6 w-6" />
|
||||
<Tr t={Translations.t.general.morescreen.createYourOwnTheme} />
|
||||
</a>
|
||||
|
@ -276,8 +270,11 @@
|
|||
</a>
|
||||
|
||||
{#if !state.theme}
|
||||
<a class="flex" href={($isAndroid ? "https://mapcomplete.org" : ".") +`/statistics.html`}
|
||||
target="_blank">
|
||||
<a
|
||||
class="flex"
|
||||
href={($isAndroid ? "https://mapcomplete.org" : ".") + `/statistics.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<ChartBar class="h-6 w-6" />
|
||||
<Tr
|
||||
t={Translations.t.general.attribution.openStatistics.Subs({ theme: "MapComplete" })}
|
||||
|
@ -334,5 +331,4 @@
|
|||
<InsetSpacer height={AndroidPolyfill.getInsetSizes().bottom} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -114,7 +114,7 @@
|
|||
})
|
||||
}
|
||||
const snappedLocation = new SnappingFeatureSource(
|
||||
new FeatureSourceMerger(...(Lists.noNull(snapSources))),
|
||||
new FeatureSourceMerger(...Lists.noNull(snapSources)),
|
||||
// We snap to the (constantly updating) map location
|
||||
mapProperties.location,
|
||||
{
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import type { Map as MlMap } from "maplibre-gl"
|
||||
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
|
@ -22,7 +21,6 @@
|
|||
import { OfflineBasemapManager } from "../../Logic/OfflineBasemapManager"
|
||||
import Checkbox from "../Base/Checkbox.svelte"
|
||||
|
||||
|
||||
export let state: ThemeViewState & SpecialVisualizationState = undefined
|
||||
export let autoDownload = state.autoDownloadOfflineBasemap
|
||||
|
||||
|
@ -35,20 +33,30 @@
|
|||
mapProperties.location.set(state.mapProperties.location.data)
|
||||
mapProperties.allowRotating.set(false)
|
||||
|
||||
|
||||
const offlineMapManager = OfflineBasemapManager.singleton
|
||||
let installing: Store<ReadonlyMap<string, object>> = offlineMapManager.installing
|
||||
let installed = offlineMapManager.installedAreas
|
||||
let focusTile: Store<{
|
||||
x: number;
|
||||
y: number;
|
||||
z: number
|
||||
} | undefined> = mapProperties.location.mapD(location => Tiles.embedded_tile(location.lat, location.lon, focusZ))
|
||||
let focusTileIsInstalled = focusTile.mapD(tile => offlineMapManager.isInstalled(tile), [installed])
|
||||
let focusTileIsInstalling = focusTile.mapD(tile => {
|
||||
const { x, y, z } = tile
|
||||
return installing.data?.has(`${z}-${x}-${y}.pmtiles`)
|
||||
}, [installing])
|
||||
let focusTile: Store<
|
||||
| {
|
||||
x: number
|
||||
y: number
|
||||
z: number
|
||||
}
|
||||
| undefined
|
||||
> = mapProperties.location.mapD((location) =>
|
||||
Tiles.embedded_tile(location.lat, location.lon, focusZ)
|
||||
)
|
||||
let focusTileIsInstalled = focusTile.mapD(
|
||||
(tile) => offlineMapManager.isInstalled(tile),
|
||||
[installed]
|
||||
)
|
||||
let focusTileIsInstalling = focusTile.mapD(
|
||||
(tile) => {
|
||||
const { x, y, z } = tile
|
||||
return installing.data?.has(`${z}-${x}-${y}.pmtiles`)
|
||||
},
|
||||
[installing]
|
||||
)
|
||||
|
||||
async function del(areaDescr: AreaDescription) {
|
||||
await offlineMapManager.deleteArea(areaDescr)
|
||||
|
@ -63,111 +71,120 @@
|
|||
const f = Tiles.asGeojson(z, x, y)
|
||||
f.properties = {
|
||||
id: "center_point_" + z + "_" + x + "_" + y,
|
||||
txt: "Tile " + x + " " + y
|
||||
txt: "Tile " + x + " " + y,
|
||||
}
|
||||
return [f]
|
||||
})
|
||||
let installedFeature: Store<Feature<Polygon>[]> = installed.map(meta =>
|
||||
(meta ?? [])
|
||||
.map(area => {
|
||||
const f = Tiles.asGeojson(area.minzoom, area.x, area.y)
|
||||
f.properties = {
|
||||
id: area.minzoom + "-" + area.x + "-" + area.y,
|
||||
downloaded: "yes",
|
||||
text: area.name + " " + new Date(area.dataVersion).toLocaleDateString() + " " + Utils.toHumanByteSize(Number(area.size))
|
||||
}
|
||||
return f
|
||||
}
|
||||
)
|
||||
let installedFeature: Store<Feature<Polygon>[]> = installed.map((meta) =>
|
||||
(meta ?? []).map((area) => {
|
||||
const f = Tiles.asGeojson(area.minzoom, area.x, area.y)
|
||||
f.properties = {
|
||||
id: area.minzoom + "-" + area.x + "-" + area.y,
|
||||
downloaded: "yes",
|
||||
text:
|
||||
area.name +
|
||||
" " +
|
||||
new Date(area.dataVersion).toLocaleDateString() +
|
||||
" " +
|
||||
Utils.toHumanByteSize(Number(area.size)),
|
||||
}
|
||||
return f
|
||||
})
|
||||
)
|
||||
new ShowDataLayer(map, {
|
||||
features: new StaticFeatureSource(installedFeature),
|
||||
layer: new LayerConfig({
|
||||
id: "downloaded",
|
||||
source: "special",
|
||||
lineRendering: [{
|
||||
color: "blue",
|
||||
width: {
|
||||
mappings: [
|
||||
{
|
||||
if: `id!~${focusZ}-.*`,
|
||||
then: "1"
|
||||
}
|
||||
]
|
||||
lineRendering: [
|
||||
{
|
||||
color: "blue",
|
||||
width: {
|
||||
mappings: [
|
||||
{
|
||||
if: `id!~${focusZ}-.*`,
|
||||
then: "1",
|
||||
},
|
||||
],
|
||||
},
|
||||
fillColor: {
|
||||
mappings: [
|
||||
{
|
||||
if: `id!~${focusZ}-.*`,
|
||||
then: "#00000000",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fillColor: {
|
||||
mappings: [
|
||||
{
|
||||
if: `id!~${focusZ}-.*`,
|
||||
then: "#00000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}],
|
||||
],
|
||||
pointRendering: [
|
||||
{
|
||||
location: ["point", "centroid"],
|
||||
label: "{text}",
|
||||
labelCss: "width: w-min",
|
||||
labelCssClasses: "bg-white rounded px-2 items-center flex flex-col"
|
||||
}
|
||||
]
|
||||
})
|
||||
labelCssClasses: "bg-white rounded px-2 items-center flex flex-col",
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
new ShowDataLayer(map, {
|
||||
features: new StaticFeatureSource(focusTileFeature),
|
||||
layer: new LayerConfig({
|
||||
id: "focustile",
|
||||
source: "special",
|
||||
lineRendering: [{
|
||||
color: "black"
|
||||
}],
|
||||
lineRendering: [
|
||||
{
|
||||
color: "black",
|
||||
},
|
||||
],
|
||||
pointRendering: [
|
||||
{
|
||||
location: ["point", "centroid"],
|
||||
label: "{txt}",
|
||||
labelCss: "width: max-content",
|
||||
labelCssClasses: "bg-white rounded px-2 flex"
|
||||
}
|
||||
]
|
||||
})
|
||||
labelCssClasses: "bg-white rounded px-2 flex",
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
<div class="flex flex-col h-full max-h-leave-room">
|
||||
<Checkbox selected={autoDownload}>Automatically download the basemap when browsing around</Checkbox>
|
||||
<div>
|
||||
If checked, MapComplete will automatically download the basemap to the cache for the area.
|
||||
This results in bigger initial data loads, but requires less internet over the long run.
|
||||
|
||||
If you plan to visit a region with less connectivity, you can also select the area you want to download below.
|
||||
<div class="max-h-leave-room flex h-full flex-col">
|
||||
<Checkbox selected={autoDownload}>
|
||||
Automatically download the basemap when browsing around
|
||||
</Checkbox>
|
||||
<div>
|
||||
If checked, MapComplete will automatically download the basemap to the cache for the area. This
|
||||
results in bigger initial data loads, but requires less internet over the long run. If you plan
|
||||
to visit a region with less connectivity, you can also select the area you want to download
|
||||
below.
|
||||
</div>
|
||||
{#if $installed === undefined}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="h-full overflow-auto pb-16">
|
||||
|
||||
<Accordion class="" inactiveClass="text-black">
|
||||
<AccordionItem paddingDefault="p-2">
|
||||
<div slot="header">Map</div>
|
||||
<div class="relative leave-room">
|
||||
<div class="rounded-lg absolute top-0 left-0 h-full w-full">
|
||||
<div class="leave-room relative">
|
||||
<div class="absolute left-0 top-0 h-full w-full rounded-lg">
|
||||
<MaplibreMap {map} {mapProperties} />
|
||||
</div>
|
||||
<div
|
||||
class="absolute top-0 left-0 h-full w-full flex flex-col justify-center items-center pointer-events-none">
|
||||
<div class="w-16 h-32 mb-16"></div>
|
||||
class="pointer-events-none absolute left-0 top-0 flex h-full w-full flex-col items-center justify-center"
|
||||
>
|
||||
<div class="mb-16 h-32 w-16" />
|
||||
{#if $focusTileIsInstalling}
|
||||
<div class="normal-background rounded-lg">
|
||||
<Loading>
|
||||
Data is being downloaded
|
||||
</Loading>
|
||||
<Loading>Data is being downloaded</Loading>
|
||||
</div>
|
||||
{:else}
|
||||
<button class="primary pointer-events-auto" on:click={() => download()}
|
||||
class:disabled={$focusTileIsInstalled}>
|
||||
<DownloadIcon class="w-8 h-8" />
|
||||
<button
|
||||
class="primary pointer-events-auto"
|
||||
on:click={() => download()}
|
||||
class:disabled={$focusTileIsInstalled}
|
||||
>
|
||||
<DownloadIcon class="h-8 w-8" />
|
||||
Download
|
||||
</button>
|
||||
{/if}
|
||||
|
@ -176,21 +193,19 @@
|
|||
</AccordionItem>
|
||||
|
||||
<AccordionItem paddingDefault="p-2">
|
||||
<div slot="header">
|
||||
Offline tile management
|
||||
</div>
|
||||
<div slot="header">Offline tile management</div>
|
||||
|
||||
<div class="leave-room">
|
||||
|
||||
|
||||
{Utils.toHumanByteSize(Utils.sum($installed.map(area => area.size)))}
|
||||
<button on:click={() => {
|
||||
installed?.data?.forEach(area => del(area))
|
||||
}}>
|
||||
{Utils.toHumanByteSize(Utils.sum($installed.map((area) => area.size)))}
|
||||
<button
|
||||
on:click={() => {
|
||||
installed?.data?.forEach((area) => del(area))
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="w-6" />
|
||||
Delete all
|
||||
</button>
|
||||
<table class="w-full ">
|
||||
<table class="w-full">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Map generation date</th>
|
||||
|
@ -198,12 +213,13 @@
|
|||
<th>Zoom ranges</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
{#each ($installed ?? []) as area }
|
||||
{#each $installed ?? [] as area}
|
||||
<tr>
|
||||
<td>{area.name}</td>
|
||||
<td>{area.dataVersion}</td>
|
||||
<td>{Utils.toHumanByteSize(area.size ?? -1)}</td>
|
||||
<td>{area.minzoom}
|
||||
<td>
|
||||
{area.minzoom}
|
||||
{#if area.maxzoom !== undefined}
|
||||
- {area.maxzoom}
|
||||
{:else}
|
||||
|
@ -218,10 +234,8 @@
|
|||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
|
@ -229,10 +243,10 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.leave-room {
|
||||
height: calc(100vh - 18rem);
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
color: var(--foreground-color);
|
||||
}
|
||||
.leave-room {
|
||||
height: calc(100vh - 18rem);
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
color: var(--foreground-color);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
)
|
||||
|
||||
let isAddNew = tags.mapD(
|
||||
(t) => t?.id?.startsWith(LastClickFeatureSource.newPointElementId) ?? false, onDestroy
|
||||
(t) => t?.id?.startsWith(LastClickFeatureSource.newPointElementId) ?? false,
|
||||
onDestroy
|
||||
)
|
||||
|
||||
export let layer: LayerConfig
|
||||
|
|
|
@ -10,7 +10,15 @@
|
|||
|
||||
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]
|
||||
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()
|
||||
|
||||
|
@ -24,8 +32,8 @@
|
|||
const day = moment.startDate.getDay()
|
||||
dayZero = day - i
|
||||
}
|
||||
function isToday(i:number ){
|
||||
i = (i+dayZero) % 7
|
||||
function isToday(i: number) {
|
||||
i = (i + dayZero) % 7
|
||||
return i === today
|
||||
}
|
||||
</script>
|
||||
|
@ -37,16 +45,21 @@
|
|||
<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*/ -->
|
||||
{: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)} >
|
||||
<div
|
||||
class="flex w-full justify-between gap-x-4 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}/>
|
||||
<Tr cls="italic subtle" t={Translations.t.general.points_in_time.closed} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import CollectionTimeRange from "./CollectionTimeRange.svelte"
|
||||
import opening_hours from "opening_hours"
|
||||
import { OH } from "../OpeningHours/OpeningHours"
|
||||
|
@ -14,26 +13,23 @@
|
|||
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)
|
||||
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">
|
||||
<div class="flex w-fit flex-col">
|
||||
<CollectionTimeRange range={weekdays}>
|
||||
<Tr t={t.weekdays} />
|
||||
|
||||
</CollectionTimeRange>
|
||||
<CollectionTimeRange range={weekend}>
|
||||
<Tr t={t.weekends} />
|
||||
|
||||
</CollectionTimeRange>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
* A switch that signals that the information should be downloaded.
|
||||
* The actual 'download' code is _not_ implemented here
|
||||
*/
|
||||
export let downloadInformation : UIEventSource<boolean>
|
||||
export let downloadInformation: UIEventSource<boolean>
|
||||
export let collapsed: boolean
|
||||
const t = Translations.t.external
|
||||
|
||||
|
@ -53,7 +53,7 @@
|
|||
<LoginToggle {state} silentFail>
|
||||
{#if !$sourceUrl || !$enableLogin}
|
||||
<!-- empty block -->
|
||||
{:else if !$downloadInformation}
|
||||
{:else if !$downloadInformation}
|
||||
<button on:click={() => downloadInformation.set(true)}>
|
||||
Attempt to download information from the website {$sourceUrl}
|
||||
</button>
|
||||
|
|
|
@ -138,13 +138,13 @@
|
|||
</ul>
|
||||
{#if diff.tr}
|
||||
<div class="h-48 w-48">
|
||||
<ChartJs config={ChartJsUtils.createConfigForTagRendering(
|
||||
diff.tr, diff.features,{
|
||||
<ChartJs
|
||||
config={ChartJsUtils.createConfigForTagRendering(diff.tr, diff.features, {
|
||||
groupToOtherCutoff: 0,
|
||||
chartType: "pie",
|
||||
sort: true,
|
||||
}
|
||||
)} />
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
Could not create a graph - this item type has no associated question
|
||||
|
|
|
@ -136,29 +136,29 @@
|
|||
</Popup>
|
||||
{#if error}
|
||||
{#if $online}
|
||||
<div class="interactive flex h-80 w-60 flex-col items-center justify-center p-4 text-center">
|
||||
{#if notFound}
|
||||
<div class="alert flex items-center">
|
||||
<TriangleOutline class="h-8 w-8 shrink-0" />
|
||||
Not found
|
||||
</div>
|
||||
This image is probably incorrect or deleted.
|
||||
{image.url}
|
||||
<slot name="not-found-extra" />
|
||||
{:else}
|
||||
<div class="alert flex items-center">
|
||||
<TriangleOutline class="h-8 w-8 shrink-0" />
|
||||
<Tr t={Translations.t.image.loadingFailed} />
|
||||
</div>
|
||||
{#if image.provider.name.toLowerCase() === "mapillary" && $isInStrictMode}
|
||||
<Tr t={Translations.t.image.mapillaryTrackingProtection} />
|
||||
{:else if $isInStrictMode}
|
||||
<Tr t={Translations.t.image.strictProtectionDetected} />
|
||||
{image.provider.name}
|
||||
<div class="subtle mt-8 text-sm">{image.url}</div>
|
||||
<div class="interactive flex h-80 w-60 flex-col items-center justify-center p-4 text-center">
|
||||
{#if notFound}
|
||||
<div class="alert flex items-center">
|
||||
<TriangleOutline class="h-8 w-8 shrink-0" />
|
||||
Not found
|
||||
</div>
|
||||
This image is probably incorrect or deleted.
|
||||
{image.url}
|
||||
<slot name="not-found-extra" />
|
||||
{:else}
|
||||
<div class="alert flex items-center">
|
||||
<TriangleOutline class="h-8 w-8 shrink-0" />
|
||||
<Tr t={Translations.t.image.loadingFailed} />
|
||||
</div>
|
||||
{#if image.provider.name.toLowerCase() === "mapillary" && $isInStrictMode}
|
||||
<Tr t={Translations.t.image.mapillaryTrackingProtection} />
|
||||
{:else if $isInStrictMode}
|
||||
<Tr t={Translations.t.image.strictProtectionDetected} />
|
||||
{image.provider.name}
|
||||
<div class="subtle mt-8 text-sm">{image.url}</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if image.status !== undefined && image.status !== "ready" && image.status !== "hidden"}
|
||||
<div class="flex h-80 w-60 flex-col justify-center p-4">
|
||||
|
|
|
@ -75,8 +75,8 @@
|
|||
)
|
||||
|
||||
let selected = new UIEventSource<P4CPicture>(undefined)
|
||||
let selectedAsFeature = selected.mapD((s) =>
|
||||
[
|
||||
let selectedAsFeature = selected.mapD(
|
||||
(s) => [
|
||||
<Feature<Point>>{
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
|
@ -89,13 +89,17 @@
|
|||
rotation: s.direction,
|
||||
},
|
||||
},
|
||||
], onDestroy)
|
||||
|
||||
let someLoading = imageState.state.mapD((stateRecord) =>
|
||||
Object.values(stateRecord).some((v) => v === "loading"), onDestroy
|
||||
],
|
||||
onDestroy
|
||||
)
|
||||
let errors = imageState.state.mapD((stateRecord) =>
|
||||
Object.keys(stateRecord).filter((k) => stateRecord[k] === "error"), onDestroy
|
||||
|
||||
let someLoading = imageState.state.mapD(
|
||||
(stateRecord) => Object.values(stateRecord).some((v) => v === "loading"),
|
||||
onDestroy
|
||||
)
|
||||
let errors = imageState.state.mapD(
|
||||
(stateRecord) => Object.keys(stateRecord).filter((k) => stateRecord[k] === "error"),
|
||||
onDestroy
|
||||
)
|
||||
let highlighted = new UIEventSource<string>(undefined)
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
export let failed: number
|
||||
export let state: SpecialVisualizationState
|
||||
const t = Translations.t.image
|
||||
let dispatch = createEventDispatcher<{retry}>()
|
||||
let dispatch = createEventDispatcher<{ retry }>()
|
||||
</script>
|
||||
|
||||
<div class="alert flex">
|
||||
<div class="flex flex-col items-start w-full items-center">
|
||||
<div class="flex w-full flex-col items-start items-center">
|
||||
{#if failed === 1}
|
||||
<Tr t={t.upload.one.failed} />
|
||||
{:else}
|
||||
|
@ -23,8 +23,8 @@
|
|||
<Tr cls="text-xs" t={t.upload.failReasonsAdvanced} />
|
||||
{#if state}
|
||||
<button class="primary pointer-events-auto" on:click={() => dispatch("retry")}>
|
||||
<ArrowPath class="w-6"/>
|
||||
<Tr t={Translations.t.general.retry}/>
|
||||
<ArrowPath class="w-6" />
|
||||
<Tr t={Translations.t.general.retry} />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
Number of images uploaded succesfully
|
||||
*/
|
||||
function getCount(input: Store<string[]>): Store<number> {
|
||||
if(!input){
|
||||
if (!input) {
|
||||
return new ImmutableStore(0)
|
||||
}
|
||||
if (featureId == "*") {
|
||||
|
@ -62,10 +62,9 @@
|
|||
|
||||
let progress = state.imageUploadManager.progressCurrentImage
|
||||
|
||||
function retry(){
|
||||
function retry() {
|
||||
state.imageUploadManager.uploadQueue()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if $debugging}
|
||||
|
@ -99,7 +98,14 @@
|
|||
{/if}
|
||||
|
||||
{#if $failed > dismissed}
|
||||
<UploadFailedMessage failed={$failed} on:click={() => (dismissed = $failed)} on:retry={() => {retry()}} {state} />
|
||||
<UploadFailedMessage
|
||||
failed={$failed}
|
||||
on:click={() => (dismissed = $failed)}
|
||||
on:retry={() => {
|
||||
retry()
|
||||
}}
|
||||
{state}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if showThankYou}
|
||||
|
|
|
@ -1,48 +1,58 @@
|
|||
<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"
|
||||
<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()
|
||||
}
|
||||
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>
|
||||
<SingleCollectionTime value={rule}>
|
||||
<svelte:fragment slot="right">
|
||||
{#if $singleRules.length > 1}
|
||||
<button
|
||||
on:click={() => {
|
||||
rm(rule)
|
||||
}}
|
||||
class="as-link"
|
||||
>
|
||||
<TrashIcon class="h-6 w-6" />
|
||||
</button>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</SingleCollectionTime>
|
||||
{/each}
|
||||
<button on:click={() => {singleRules.data.push(new UIEventSource(undefined)); singleRules.ping()}}>Add schedule
|
||||
<button
|
||||
on:click={() => {
|
||||
singleRules.data.push(new UIEventSource(undefined))
|
||||
singleRules.ping()
|
||||
}}
|
||||
>
|
||||
Add schedule
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import TimeInput from "../TimeInput.svelte"
|
||||
import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Checkbox from "../../../Base/Checkbox.svelte"
|
||||
|
@ -16,16 +15,28 @@
|
|||
/*
|
||||
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 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){
|
||||
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))
|
||||
|
||||
|
@ -39,7 +50,6 @@
|
|||
selectedDays[i]?.set(true)
|
||||
}
|
||||
} else {
|
||||
|
||||
let index = daysOfTheWeek.indexOf(initialDay)
|
||||
if (index >= 0) {
|
||||
selectedDays[index]?.set(true)
|
||||
|
@ -47,19 +57,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
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))
|
||||
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++) {
|
||||
|
@ -75,27 +92,28 @@
|
|||
selectedDays[i].set(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="rounded-xl my-2 p-2 low-interaction flex w-full justify-between flex-wrap">
|
||||
|
||||
<div class="low-interaction my-2 flex w-full flex-wrap justify-between rounded-xl p-2">
|
||||
<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">
|
||||
<div class="mx-4 flex items-center gap-x-1">
|
||||
<TimeInput {value} />
|
||||
{#if $values.length > 1}
|
||||
<button class="as-link">
|
||||
<TrashIcon class="w-6 h-6" />
|
||||
<TrashIcon class="h-6 w-6" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<button on:click={() => {values.data.push(new UIEventSource(undefined)); values.ping()}}>
|
||||
<PlusCircle class="w-6 h-6" />
|
||||
<button
|
||||
on:click={() => {
|
||||
values.data.push(new UIEventSource(undefined))
|
||||
values.ping()
|
||||
}}
|
||||
>
|
||||
<PlusCircle class="h-6 w-6" />
|
||||
Add time
|
||||
</button>
|
||||
</div>
|
||||
|
@ -108,14 +126,11 @@
|
|||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-between w-full">
|
||||
|
||||
<div class="flex w-full flex-wrap justify-between">
|
||||
<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>
|
||||
|
|
|
@ -27,11 +27,14 @@
|
|||
mla.allowMoving.setData(false)
|
||||
mla.allowZooming.setData(false)
|
||||
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])
|
||||
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
|
||||
$: rotation.addCallbackAndRunD((degrees) => {
|
||||
if (!directionElem?.style) {
|
||||
|
@ -42,7 +45,6 @@
|
|||
|
||||
let mainElem: HTMLElement
|
||||
|
||||
|
||||
function onPosChange(x: number, y: number) {
|
||||
const rect = mainElem.getBoundingClientRect()
|
||||
const dx = -(rect.left + rect.right) / 2 + x
|
||||
|
@ -57,7 +59,7 @@
|
|||
|
||||
<div
|
||||
bind:this={mainElem}
|
||||
class="relative h-48 min-w-48 w-full cursor-pointer overflow-hidden rounded-xl"
|
||||
class="relative h-48 w-full min-w-48 cursor-pointer overflow-hidden rounded-xl"
|
||||
on:click={(e) => onPosChange(e.x, e.y)}
|
||||
on:mousedown={(e) => {
|
||||
isDown = true
|
||||
|
|
|
@ -85,7 +85,8 @@
|
|||
},
|
||||
]
|
||||
},
|
||||
[mapLocation], onDestroy
|
||||
[mapLocation],
|
||||
onDestroy
|
||||
)
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
const state = new OpeningHoursState(value, prefix, postfix)
|
||||
let expanded = new UIEventSource(false)
|
||||
|
||||
function abort(){
|
||||
function abort() {
|
||||
expanded.set(false)
|
||||
}
|
||||
</script>
|
||||
|
@ -44,17 +44,19 @@
|
|||
<OHTable value={state.normalOhs} />
|
||||
<div class="absolute flex w-full justify-center" style="bottom: -3rem">
|
||||
<button on:click={() => abort()}>
|
||||
<XMark class="m-0 h-6 w-6"/>
|
||||
<XMark class="m-0 h-6 w-6" />
|
||||
<Tr t={Translations.t.general.cancel} />
|
||||
</button>
|
||||
<button class=" primary" on:click={() => expanded.set(false)}>
|
||||
<Check class="m-0 h-6 w-6 shrink-0 p-0" color="white" />
|
||||
<Tr t={Translations.t.general.confirm} />
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<CloseButton class="absolute top-0 right-0 z-10" style="margin-top: -1.0rem" on:click={() => abort()} />
|
||||
|
||||
<CloseButton
|
||||
class="absolute right-0 top-0 z-10"
|
||||
style="margin-top: -1.0rem"
|
||||
on:click={() => abort()}
|
||||
/>
|
||||
</Popup>
|
||||
|
||||
<button on:click={() => expanded.set(true)}>Pick opening hours</button>
|
||||
|
|
|
@ -78,7 +78,8 @@
|
|||
previewDegrees.setData(beta + "°")
|
||||
previewPercentage.setData(degreesToPercentage(beta))
|
||||
},
|
||||
[valuesign, beta], onDestroy
|
||||
[valuesign, beta],
|
||||
onDestroy
|
||||
)
|
||||
|
||||
function onSave() {
|
||||
|
|
|
@ -44,8 +44,12 @@
|
|||
})
|
||||
)
|
||||
|
||||
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 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"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { ValidatorType } from "./Validators"
|
||||
import Validators from "./Validators"
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
if (!range) {
|
||||
return true
|
||||
}
|
||||
if(typeof canonicalValue === "string"){
|
||||
if (typeof canonicalValue === "string") {
|
||||
canonicalValue = Number(canonicalValue)
|
||||
}
|
||||
if (canonicalValue < range.warnBelow) {
|
||||
|
|
|
@ -22,7 +22,7 @@ export abstract class Validator {
|
|||
public readonly textArea: boolean
|
||||
|
||||
public readonly isMeta?: boolean
|
||||
public readonly inputHelper : ComponentType = undefined
|
||||
public readonly inputHelper: ComponentType = undefined
|
||||
public readonly hideInputField: boolean = false
|
||||
|
||||
constructor(
|
||||
|
@ -83,5 +83,4 @@ export abstract class Validator {
|
|||
public validateArguments(args: string): undefined | string {
|
||||
return undefined
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -83,7 +83,6 @@ export default class Validators {
|
|||
new DirectionValidator(),
|
||||
new SlopeValidator(),
|
||||
|
||||
|
||||
new UrlValidator(),
|
||||
new EmailValidator(),
|
||||
new PhoneValidator(),
|
||||
|
|
|
@ -2,11 +2,12 @@ import StringValidator from "./StringValidator"
|
|||
import { ComponentType } from "svelte/types/runtime/internal/dev"
|
||||
import CollectionTimes from "../Helpers/CollectionTimes/CollectionTimes.svelte"
|
||||
|
||||
export default class CollectionTimesValidator extends StringValidator{
|
||||
export default class CollectionTimesValidator extends StringValidator {
|
||||
public readonly inputHelper: ComponentType = CollectionTimes
|
||||
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")
|
||||
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"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -7,5 +7,4 @@ export default class ColorValidator extends Validator {
|
|||
constructor() {
|
||||
super("color", "Shows a color picker")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,5 +29,4 @@ export default class DateValidator extends Validator {
|
|||
|
||||
return [year, month, day].join("-")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import IntValidator from "./IntValidator"
|
|||
import DirectionInput from "../Helpers/DirectionInput.svelte"
|
||||
|
||||
export default class DirectionValidator extends IntValidator {
|
||||
|
||||
public readonly inputHelper = DirectionInput
|
||||
constructor() {
|
||||
super(
|
||||
|
|
|
@ -3,7 +3,6 @@ import { Utils } from "../../../Utils"
|
|||
import { eliCategory } from "../../../Models/RasterLayerProperties"
|
||||
|
||||
export default class DistanceValidator extends Validator {
|
||||
|
||||
private readonly docs: string = [
|
||||
"#### Helper-arguments",
|
||||
"Options are:",
|
||||
|
@ -59,5 +58,4 @@ export default class DistanceValidator extends Validator {
|
|||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,6 +39,4 @@ export default class ImageUrlValidator extends UrlValidator {
|
|||
}
|
||||
return ImageUrlValidator.hasValidExternsion(str)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,7 @@ import { ComponentType } from "svelte/types/runtime/internal/dev"
|
|||
import OpeningHoursInput from "../Helpers/OpeningHoursInput.svelte"
|
||||
|
||||
export default class OpeningHoursValidator extends Validator {
|
||||
|
||||
public readonly inputHelper= OpeningHoursInput
|
||||
public readonly inputHelper = OpeningHoursInput
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
|
@ -44,6 +43,4 @@ export default class OpeningHoursValidator extends Validator {
|
|||
].join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import SimpleTagInput from "../Helpers/SimpleTagInput.svelte"
|
|||
export default class SimpleTagValidator extends Validator {
|
||||
private static readonly KeyValidator = new TagKeyValidator()
|
||||
public readonly inputHelper = SimpleTagInput
|
||||
public readonly hideInputField = true
|
||||
public readonly hideInputField = true
|
||||
|
||||
public readonly isMeta = true
|
||||
constructor() {
|
||||
|
@ -53,6 +53,4 @@ export default class SimpleTagValidator extends Validator {
|
|||
isValid(tag: string, _): boolean {
|
||||
return this.getFeedback(tag, _) === undefined
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import FloatValidator from "./FloatValidator"
|
|||
import SlopeInput from "../Helpers/SlopeInput.svelte"
|
||||
|
||||
export default class SlopeValidator extends FloatValidator {
|
||||
public readonly inputHelper =SlopeInput
|
||||
public readonly inputHelper = SlopeInput
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
|
@ -43,5 +43,4 @@ export default class SlopeValidator extends FloatValidator {
|
|||
}
|
||||
return super.reformat(str) + lastChar
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,5 +21,4 @@ export default class TagValidator extends Validator {
|
|||
isValid(tag: string, _): boolean {
|
||||
return this.getFeedback(tag, _) === undefined
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Validator } from "../Validator"
|
|||
import TimeInput from "../Helpers/TimeInput.svelte"
|
||||
|
||||
export class TimeValidator extends Validator {
|
||||
|
||||
public readonly inputmode = "time"
|
||||
public readonly inputHelper = TimeInput
|
||||
public readonly hideInputField = true
|
||||
|
@ -10,6 +9,4 @@ export class TimeValidator extends Validator {
|
|||
constructor() {
|
||||
super("time", "A time picker")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -17,5 +17,4 @@ export default class TranslationValidator extends Validator {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -188,5 +188,4 @@ Another example is to search for species and trees:
|
|||
}
|
||||
return clipped
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,10 @@
|
|||
? tags.map((tags) => rotation.GetRenderValue(tags).Subs(tags).txt)
|
||||
: new ImmutableStore("0deg")
|
||||
if (rotation?.render?.txt === "{alpha}deg") {
|
||||
_rotation = Orientation.singleton.alpha.map((alpha) => (alpha ? alpha + "deg" : "0deg "), onDestroy)
|
||||
_rotation = Orientation.singleton.alpha.map(
|
||||
(alpha) => (alpha ? alpha + "deg" : "0deg "),
|
||||
onDestroy
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -78,7 +78,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
if (!MapLibreAdaptor.pmtilesInited) {
|
||||
const offlineManager = OfflineBasemapManager.singleton
|
||||
maplibregl.addProtocol("pmtiles", new Protocol().tile)
|
||||
maplibregl.addProtocol("pmtilesoffl", (request, abort) => offlineManager.tilev4(request, abort))
|
||||
maplibregl.addProtocol("pmtilesoffl", (request, abort) =>
|
||||
offlineManager.tilev4(request, abort)
|
||||
)
|
||||
MapLibreAdaptor.pmtilesInited = true
|
||||
}
|
||||
this._maplibreMap = maplibreMap
|
||||
|
@ -755,5 +757,4 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import PointRenderingConfig, {
|
||||
allowed_location_codes,
|
||||
PointRenderingLocation
|
||||
PointRenderingLocation,
|
||||
} from "../../Models/ThemeConfig/PointRenderingConfig"
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import { type Alignment, Map as MlMap, Marker } from "maplibre-gl"
|
||||
|
@ -51,15 +51,17 @@ export class PointRenderingLayer {
|
|||
/**
|
||||
* Basically 'features', but only if 'visible' is true
|
||||
*/
|
||||
const featuresIfVisibleStore: Store<(Feature<Point, { id: string }> & {
|
||||
locationType: PointRenderingLocation
|
||||
})[]> =
|
||||
new IfVisibleFeatureSource(features, visibility).features.map(features =>
|
||||
PointRenderingLayer.extractLocations(features, config.location)
|
||||
)
|
||||
const featuresIfVisibleStore: Store<
|
||||
(Feature<Point, { id: string }> & {
|
||||
locationType: PointRenderingLocation
|
||||
})[]
|
||||
> = new IfVisibleFeatureSource(features, visibility).features.map((features) =>
|
||||
PointRenderingLayer.extractLocations(features, config.location)
|
||||
)
|
||||
|
||||
|
||||
let featuresToDraw: FeatureSource<Feature<Point, { id: string }> & { locationType: PointRenderingLocation }>
|
||||
let featuresToDraw: FeatureSource<
|
||||
Feature<Point, { id: string }> & { locationType: PointRenderingLocation }
|
||||
>
|
||||
if (preprocess) {
|
||||
featuresToDraw = preprocess(new StaticFeatureSource(featuresIfVisibleStore))
|
||||
} else {
|
||||
|
@ -78,7 +80,8 @@ export class PointRenderingLayer {
|
|||
return
|
||||
}
|
||||
allowed_location_codes.forEach((code) => {
|
||||
const marker = this._allMarkers.get(<OsmId>selected.properties.id)
|
||||
const marker = this._allMarkers
|
||||
.get(<OsmId>selected.properties.id)
|
||||
?.get(code)
|
||||
?.getElement()
|
||||
if (marker === undefined) {
|
||||
|
@ -88,46 +91,54 @@ export class PointRenderingLayer {
|
|||
this._markedAsSelected.push(marker)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* All locations that this layer should be rendered
|
||||
* @private
|
||||
*/
|
||||
private static extractLocations(features: Feature<Geometry, {
|
||||
id: string
|
||||
}>[], locations: Set<PointRenderingLocation>): (Feature<Point, { id: string }> & {
|
||||
private static extractLocations(
|
||||
features: Feature<
|
||||
Geometry,
|
||||
{
|
||||
id: string
|
||||
}
|
||||
>[],
|
||||
locations: Set<PointRenderingLocation>
|
||||
): (Feature<Point, { id: string }> & {
|
||||
locationType: PointRenderingLocation
|
||||
})[] {
|
||||
if(!features){
|
||||
if (!features) {
|
||||
return []
|
||||
}
|
||||
const resultingFeatures: (Feature<Point, { id: string }> & { locationType: PointRenderingLocation })[] = []
|
||||
const resultingFeatures: (Feature<Point, { id: string }> & {
|
||||
locationType: PointRenderingLocation
|
||||
})[] = []
|
||||
|
||||
function registerFeature(feature: Feature<any, {
|
||||
id: string
|
||||
}>, location: [number, number], locationType: PointRenderingLocation) {
|
||||
function registerFeature(
|
||||
feature: Feature<
|
||||
any,
|
||||
{
|
||||
id: string
|
||||
}
|
||||
>,
|
||||
location: [number, number],
|
||||
locationType: PointRenderingLocation
|
||||
) {
|
||||
resultingFeatures.push({
|
||||
...feature,
|
||||
locationType,
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: location
|
||||
}
|
||||
coordinates: location,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for (const feature of features) {
|
||||
for (const location of locations) {
|
||||
if (feature?.geometry === undefined) {
|
||||
console.warn(
|
||||
"Got an invalid feature:",
|
||||
feature,
|
||||
" while rendering",
|
||||
location
|
||||
)
|
||||
console.warn("Got an invalid feature:", feature, " while rendering", location)
|
||||
}
|
||||
if (location === "waypoints") {
|
||||
if (feature.geometry.type === "LineString") {
|
||||
|
@ -157,7 +168,6 @@ export class PointRenderingLayer {
|
|||
}
|
||||
|
||||
return resultingFeatures
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,12 +175,11 @@ export class PointRenderingLayer {
|
|||
* @private
|
||||
*/
|
||||
private hideUnneededElements(featuresToDraw: Feature<Geometry, { id: string }>[]) {
|
||||
const idsToShow = new Set(featuresToDraw.map(f => f.properties.id))
|
||||
const idsToShow = new Set(featuresToDraw.map((f) => f.properties.id))
|
||||
|
||||
for (const key of this._allMarkers.keys()) {
|
||||
const shouldBeShown = idsToShow.has(key)
|
||||
for (const marker of this._allMarkers.get(key).values()) {
|
||||
|
||||
if (!shouldBeShown) {
|
||||
marker.addClassName("hidden")
|
||||
} else {
|
||||
|
@ -180,13 +189,14 @@ export class PointRenderingLayer {
|
|||
}
|
||||
}
|
||||
|
||||
private updateFeatures(allPointLocations: (Feature<Point> & { locationType: PointRenderingLocation })[]) {
|
||||
private updateFeatures(
|
||||
allPointLocations: (Feature<Point> & { locationType: PointRenderingLocation })[]
|
||||
) {
|
||||
const cache = this._allMarkers
|
||||
for (const feature of allPointLocations) {
|
||||
const id = <OsmId>feature.properties.id
|
||||
const locationType: PointRenderingLocation = feature.locationType
|
||||
|
||||
|
||||
let marker = cache.get(id)?.get(locationType)
|
||||
if (marker) {
|
||||
const oldLoc = marker.getLngLat()
|
||||
|
@ -218,7 +228,6 @@ export class PointRenderingLayer {
|
|||
.setLngLat(feature.geometry.coordinates)
|
||||
.addTo(this._map)*/
|
||||
|
||||
|
||||
let store: Store<Record<string, string>>
|
||||
if (this._fetchStore) {
|
||||
store = this._fetchStore(feature.properties.id)
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
</div>
|
||||
|
||||
{#if !$isOnline}
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="alert flex items-center gap-x-4">
|
||||
<CrossedOut>
|
||||
<WifiIcon />
|
||||
|
|
|
@ -361,14 +361,13 @@ export default class ShowDataLayer {
|
|||
layers: LayerConfig[],
|
||||
options?: Partial<Omit<ShowDataLayerOptions, "features" | "layer">>
|
||||
) {
|
||||
const perLayer =
|
||||
new PerLayerFeatureSourceSplitter(
|
||||
layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)),
|
||||
features,
|
||||
{
|
||||
constructStore: (features, layer) => new SimpleFeatureSource(layer, features),
|
||||
}
|
||||
)
|
||||
const perLayer = new PerLayerFeatureSourceSplitter(
|
||||
layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)),
|
||||
features,
|
||||
{
|
||||
constructStore: (features, layer) => new SimpleFeatureSource(layer, features),
|
||||
}
|
||||
)
|
||||
if (options?.zoomToFeatures) {
|
||||
options.zoomToFeatures = false
|
||||
features.features.addCallbackD((features) => {
|
||||
|
@ -397,17 +396,21 @@ export default class ShowDataLayer {
|
|||
* @param state
|
||||
* @param options
|
||||
*/
|
||||
public static showLayerClustered(mlmap: Store<MlMap>,
|
||||
state: { mapProperties: { zoom: UIEventSource<number> } },
|
||||
options: ShowDataLayerOptions & { layer: LayerConfig }
|
||||
public static showLayerClustered(
|
||||
mlmap: Store<MlMap>,
|
||||
state: { mapProperties: { zoom: UIEventSource<number> } },
|
||||
options: ShowDataLayerOptions & { layer: LayerConfig }
|
||||
) {
|
||||
options.preprocessPoints = feats =>
|
||||
new ClusteringFeatureSource(feats, state.mapProperties.zoom.map(z => z + 2),
|
||||
options.preprocessPoints = (feats) =>
|
||||
new ClusteringFeatureSource(
|
||||
feats,
|
||||
state.mapProperties.zoom.map((z) => z + 2),
|
||||
options.layer.id,
|
||||
{
|
||||
cutoff: 7,
|
||||
showSummaryAt: "tilecenter"
|
||||
})
|
||||
showSummaryAt: "tilecenter",
|
||||
}
|
||||
)
|
||||
new ShowDataLayer(mlmap, options)
|
||||
}
|
||||
|
||||
|
@ -453,14 +456,9 @@ export default class ShowDataLayer {
|
|||
layer,
|
||||
drawLines,
|
||||
drawMarkers,
|
||||
preprocessPoints
|
||||
preprocessPoints,
|
||||
} = this._options
|
||||
let onClick = this._options.onClick
|
||||
if (!onClick && selectedElement && layer.title !== undefined) {
|
||||
onClick = (feature: Feature) => {
|
||||
selectedElement?.setData(feature)
|
||||
}
|
||||
}
|
||||
if (drawLines !== false) {
|
||||
for (let i = 0; i < layer.lineRendering.length; i++) {
|
||||
const lineRenderingConfig = layer.lineRendering[i]
|
||||
|
|
|
@ -32,6 +32,6 @@ export interface ShowDataLayerOptions {
|
|||
onClick?: (feature: Feature) => void
|
||||
metaTags?: Store<Record<string, string>>
|
||||
|
||||
prefix?: string,
|
||||
prefix?: string
|
||||
preprocessPoints?: <T extends Feature<Point>>(fs: FeatureSource<T>) => FeatureSource<T>
|
||||
}
|
||||
|
|
|
@ -571,7 +571,7 @@ changes // => [[36000,61200], ["10:00", "17:00"]]
|
|||
tags: Record<string, string | number> & { _lat: number; _lon: number; _country?: string },
|
||||
textToParse: string,
|
||||
country: string,
|
||||
mode?: mode,
|
||||
mode?: mode
|
||||
) {
|
||||
return new opening_hours(
|
||||
textToParse,
|
||||
|
@ -583,15 +583,13 @@ changes // => [[36000,61200], ["10:00", "17:00"]]
|
|||
state: undefined,
|
||||
},
|
||||
},
|
||||
<optional_conf> {
|
||||
<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
|
||||
|
@ -602,7 +600,7 @@ changes // => [[36000,61200], ["10:00", "17:00"]]
|
|||
*/
|
||||
public static createRangesForApplicableWeek(
|
||||
oh: opening_hours,
|
||||
today?: Date,
|
||||
today?: Date
|
||||
): {
|
||||
ranges: OpeningRange[][]
|
||||
startingMonday: Date
|
||||
|
|
|
@ -15,16 +15,16 @@
|
|||
let mode = new UIEventSource("")
|
||||
|
||||
onDestroy(
|
||||
value
|
||||
.map((ph) => OH.ParsePHRule(ph), onDestroy)
|
||||
.addCallbackAndRunD((parsed) => {
|
||||
if (parsed === null) {
|
||||
return
|
||||
}
|
||||
mode.setData(parsed.mode)
|
||||
startValue.setData(parsed.start)
|
||||
endValue.setData(parsed.end)
|
||||
})
|
||||
value
|
||||
.map((ph) => OH.ParsePHRule(ph), onDestroy)
|
||||
.addCallbackAndRunD((parsed) => {
|
||||
if (parsed === null) {
|
||||
return
|
||||
}
|
||||
mode.setData(parsed.mode)
|
||||
startValue.setData(parsed.start)
|
||||
endValue.setData(parsed.end)
|
||||
})
|
||||
)
|
||||
function updateValue() {
|
||||
if (mode.data === undefined || mode.data === "") {
|
||||
|
@ -72,11 +72,10 @@
|
|||
</label>
|
||||
|
||||
{#if $mode === " "}
|
||||
<div class="flex gap-x-1 items-center">
|
||||
<div class="flex items-center gap-x-1">
|
||||
<Tr t={t.opensAt} />
|
||||
<TimeInput value={startValue} />
|
||||
<Tr t={t.openTill} />
|
||||
<TimeInput value={endValue} />
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
/**
|
||||
* The element showing an "hour" in a bubble, above or below the opening hours table
|
||||
* Dumbly shows one row of what is given.
|
||||
|
|
|
@ -42,10 +42,12 @@
|
|||
let preciseInputIsTapped = false
|
||||
|
||||
const forbiddenKeys = new Set(["id", "timestamp", "user", "changeset", "version", "uid"])
|
||||
let asTags = tags.map((tgs) =>
|
||||
Object.keys(tgs)
|
||||
.filter((k) => !k.startsWith("_") && !forbiddenKeys.has(k))
|
||||
.map((k) => new Tag(k, tgs[k])), onDestroy
|
||||
let asTags = tags.map(
|
||||
(tgs) =>
|
||||
Object.keys(tgs)
|
||||
.filter((k) => !k.startsWith("_") && !forbiddenKeys.has(k))
|
||||
.map((k) => new Tag(k, tgs[k])),
|
||||
onDestroy
|
||||
)
|
||||
let showPopup: UIEventSource<boolean> = new UIEventSource(false)
|
||||
|
||||
|
|
|
@ -66,7 +66,9 @@
|
|||
const feature = state.indexedFeatures.featuresById.data.get(targetFeatureId)
|
||||
const featureTags = state.featureProperties.getStore(targetFeatureId)
|
||||
const rendering = tagRenderingConfig.GetRenderValue(featureTags.data).txt
|
||||
const specialRenderings = Lists.noNull(SpecialVisualizations.constructSpecification(rendering)).filter((v) => typeof v !== "string" && v.func["supportsAutoAction"] === true)
|
||||
const specialRenderings = Lists.noNull(
|
||||
SpecialVisualizations.constructSpecification(rendering)
|
||||
).filter((v) => typeof v !== "string" && v.func["supportsAutoAction"] === true)
|
||||
|
||||
if (specialRenderings.length == 0) {
|
||||
console.warn(
|
||||
|
|
|
@ -2,8 +2,9 @@ import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
|
|||
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
|
||||
import { Changes } from "../../Logic/Osm/Changes"
|
||||
import {
|
||||
SpecialVisualisationArg,
|
||||
SpecialVisualisationParams,
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationState,
|
||||
SpecialVisualizationSvelte,
|
||||
} from "../SpecialVisualization"
|
||||
import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource"
|
||||
|
@ -31,12 +32,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
|
|||
public readonly funcName: string = "auto_apply"
|
||||
public readonly needsUrls = []
|
||||
public readonly group = "data_import"
|
||||
public readonly args: {
|
||||
name: string
|
||||
defaultValue?: string
|
||||
doc: string
|
||||
required?: boolean
|
||||
}[] = [
|
||||
public readonly args: SpecialVisualisationArg[] = [
|
||||
{
|
||||
name: "target_layer",
|
||||
doc: "The layer that the target features will reside in",
|
||||
|
@ -54,6 +50,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
|
|||
},
|
||||
{
|
||||
name: "text",
|
||||
type:"translation",
|
||||
doc: "The text to show on the button",
|
||||
required: true,
|
||||
},
|
||||
|
@ -86,15 +83,11 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
|
|||
4. At last, add this component`
|
||||
}
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[]
|
||||
): SvelteUIElement {
|
||||
const target_layer_id = argument[0]
|
||||
const targetTagRendering = argument[2]
|
||||
const text = argument[3]
|
||||
const icon = argument[4]
|
||||
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const target_layer_id = args[0]
|
||||
const targetTagRendering = args[2]
|
||||
const text = args[3]
|
||||
const icon = args[4]
|
||||
const options = {
|
||||
target_layer_id,
|
||||
targetTagRendering,
|
||||
|
@ -105,7 +98,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
|
|||
const to_parse: UIEventSource<string[]> = new UIEventSource<string[]>(undefined)
|
||||
Stores.chronic(500, () => to_parse.data === undefined)
|
||||
.map(() => {
|
||||
const applicable = <string | string[]>tagSource.data[argument[1]]
|
||||
const applicable = <string | string[]>tags.data[args[1]]
|
||||
if (typeof applicable === "string") {
|
||||
return <string[]>JSON.parse(applicable)
|
||||
} else {
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationState,
|
||||
SpecialVisualizationSvelte,
|
||||
} from "../SpecialVisualization"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { Feature, LineString } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import Translations from "../i18n/Translations"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import ExportFeatureButton from "./ExportFeatureButton.svelte"
|
||||
|
@ -17,13 +11,7 @@ class ExportAsGpxVis extends SpecialVisualizationSvelte {
|
|||
args = []
|
||||
needsUrls = []
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
) {
|
||||
constr({ tags, feature, layer }: SpecialVisualisationParams) {
|
||||
if (feature.geometry.type !== "LineString") {
|
||||
return undefined
|
||||
}
|
||||
|
@ -48,7 +36,7 @@ class ExportAsGeojsonVis extends SpecialVisualizationSvelte {
|
|||
docs = "Exports the selected feature as GeoJson-file"
|
||||
args = []
|
||||
|
||||
constr(state, tags, args, feature, layer) {
|
||||
constr({ tags, feature, layer }: SpecialVisualisationParams) {
|
||||
const t = Translations.t.general.download
|
||||
return new SvelteUIElement(ExportFeatureButton, {
|
||||
tags,
|
||||
|
@ -64,7 +52,7 @@ class ExportAsGeojsonVis extends SpecialVisualizationSvelte {
|
|||
}
|
||||
|
||||
export class DataExportVisualisations {
|
||||
public static initList(): SpecialVisualization[] {
|
||||
public static initList(): SpecialVisualizationSvelte[] {
|
||||
return [new ExportAsGpxVis(), new ExportAsGeojsonVis()]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import {
|
||||
SpecialVisualisationArg,
|
||||
SpecialVisualisationParams,
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationSvelte,
|
||||
} from "../SpecialVisualization"
|
||||
import { HistogramViz } from "./HistogramViz"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Feature } from "geojson"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import DirectionIndicator from "../Base/DirectionIndicator.svelte"
|
||||
|
@ -16,15 +20,18 @@ import NextChangeViz from "../OpeningHours/NextChangeViz.svelte"
|
|||
import { Unit } from "../../Models/Unit"
|
||||
import AllFeaturesStatistics from "../Statistics/AllFeaturesStatistics.svelte"
|
||||
import { LanguageElement } from "./LanguageElement/LanguageElement"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import { And } from "../../Logic/Tags/And"
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import TagRenderingEditable from "./TagRendering/TagRenderingEditable.svelte"
|
||||
import AllTagsPanel from "./AllTagsPanel/AllTagsPanel.svelte"
|
||||
import CollectionTimes from "../CollectionTimes/CollectionTimes.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Combine from "../Base/Combine"
|
||||
import Marker from "../Map/Marker.svelte"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
|
||||
class DirectionIndicatorVis extends SpecialVisualization {
|
||||
class DirectionIndicatorVis extends SpecialVisualizationSvelte {
|
||||
funcName = "direction_indicator"
|
||||
args = []
|
||||
|
||||
|
@ -32,13 +39,8 @@ class DirectionIndicatorVis extends SpecialVisualization {
|
|||
"Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object"
|
||||
group = "data"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
): BaseUIElement {
|
||||
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
||||
constr(params: SpecialVisualisationParams): SvelteUIElement {
|
||||
return new SvelteUIElement(DirectionIndicator, params)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,27 +63,25 @@ class DirectionAbsolute extends SpecialVisualization {
|
|||
]
|
||||
group = "data"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
): BaseUIElement {
|
||||
constr({
|
||||
tags,
|
||||
args,
|
||||
}: SpecialVisualisationParams): BaseUIElement {
|
||||
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
||||
const offset = args[1] === "" ? 0 : Number(args[1])
|
||||
|
||||
return new VariableUiElement(
|
||||
tagSource
|
||||
.map((tags) => {
|
||||
tags.map((tags) => {
|
||||
console.log("Direction value", tags[key], key)
|
||||
return tags[key]
|
||||
})
|
||||
.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]
|
||||
}),
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -113,14 +113,12 @@ class OpeningHoursTableVis extends SpecialVisualizationSvelte {
|
|||
example =
|
||||
"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) {
|
||||
constr({ tags, args }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const [key, prefix, postfix] = args
|
||||
const openingHoursStore: Store<opening_hours | "error" | undefined> =
|
||||
OH.CreateOhObjectStore(tagSource, key, prefix, postfix)
|
||||
OH.CreateOhObjectStore(tags, key, prefix, postfix)
|
||||
return new SvelteUIElement(OpeningHoursWithError, {
|
||||
tags: tagSource,
|
||||
key,
|
||||
opening_hours_obj: openingHoursStore,
|
||||
tags, key, opening_hours_obj: openingHoursStore,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -148,11 +146,7 @@ class OpeningHoursState extends SpecialVisualizationSvelte {
|
|||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
): SvelteUIElement {
|
||||
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const keyToUse = args[0]
|
||||
const prefix = args[1]
|
||||
const postfix = args[2]
|
||||
|
@ -183,25 +177,25 @@ class Canonical extends SpecialVisualization {
|
|||
},
|
||||
]
|
||||
|
||||
constr(state, tagSource, args) {
|
||||
constr({ state, tags, args }: SpecialVisualisationParams) {
|
||||
const key = args[0]
|
||||
return new VariableUiElement(
|
||||
tagSource
|
||||
tags
|
||||
.map((tags) => tags[key])
|
||||
.map((value) => {
|
||||
if (value === undefined) {
|
||||
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) {
|
||||
return value
|
||||
}
|
||||
const getCountry = () => tagSource.data._country
|
||||
const getCountry = () => tags.data._country
|
||||
return unit.asHumanLongValue(value, getCountry)
|
||||
}),
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -213,26 +207,24 @@ class StatisticsVis extends SpecialVisualizationSvelte {
|
|||
"Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer"
|
||||
args = []
|
||||
|
||||
constr(state) {
|
||||
return new SvelteUIElement(AllFeaturesStatistics, { state })
|
||||
constr(params: SpecialVisualisationParams) {
|
||||
return new SvelteUIElement(AllFeaturesStatistics, params)
|
||||
}
|
||||
}
|
||||
|
||||
class PresetDescription extends SpecialVisualization {
|
||||
class PresetDescription extends SpecialVisualizationSvelte {
|
||||
funcName = "preset_description"
|
||||
docs =
|
||||
"Shows the extra description from the presets of the layer, if one matches. It will pick the most specific one (e.g. if preset `A` implies `B`, but `B` does not imply `A`, it'll pick B) or the first one if no ordering can be made. Might be empty"
|
||||
args = []
|
||||
group = "UI"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
): BaseUIElement {
|
||||
const translation = tagSource.map((tags) => {
|
||||
constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const translation = tags.map((tags) => {
|
||||
const layer = state.theme.getMatchingLayer(tags)
|
||||
return layer?.getMostMatchingPreset(tags)?.description
|
||||
})
|
||||
return new VariableUiElement(translation)
|
||||
return new SvelteUIElement(Tr, { t: translation })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,14 +232,9 @@ class PresetTypeSelect extends SpecialVisualizationSvelte {
|
|||
funcName = "preset_type_select"
|
||||
docs = "An editable tag rendering which allows to change the type"
|
||||
args = []
|
||||
group = "ui"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
selectedElement: Feature,
|
||||
layer: LayerConfig,
|
||||
): SvelteUIElement {
|
||||
constr({ state, tags, feature, layer }: SpecialVisualisationParams,): SvelteUIElement {
|
||||
const t = Translations.t.preset_type
|
||||
if (layer._basedOn !== layer.id) {
|
||||
console.warn("Trying to use the _original_ layer")
|
||||
|
@ -273,7 +260,7 @@ class PresetTypeSelect extends SpecialVisualizationSvelte {
|
|||
return new SvelteUIElement(TagRenderingEditable, {
|
||||
config,
|
||||
tags,
|
||||
selectedElement,
|
||||
selectedElement: feature,
|
||||
state,
|
||||
layer,
|
||||
})
|
||||
|
@ -286,8 +273,8 @@ class AllTagsVis extends SpecialVisualizationSvelte {
|
|||
args = []
|
||||
group = "data"
|
||||
|
||||
constr(state, tags: UIEventSource<Record<string, string>>, _, __, layer: LayerConfig) {
|
||||
return new SvelteUIElement(AllTagsPanel, { tags, layer })
|
||||
constr(params: SpecialVisualisationParams) {
|
||||
return new SvelteUIElement(AllTagsPanel, params)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -298,18 +285,75 @@ class PointsInTimeVis extends SpecialVisualization {
|
|||
args = [
|
||||
{
|
||||
name: "key",
|
||||
type:"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 {
|
||||
constr({ tags, args }: SpecialVisualisationParams): 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 }),
|
||||
const points_in_time = tags.map((tags) => tags[key])
|
||||
const times = points_in_time.map(
|
||||
(times) => OH.createOhObject(<any>tags.data, times, tags.data["_country"], 1),
|
||||
[tags]
|
||||
)
|
||||
return new VariableUiElement(
|
||||
times.map((times) => new SvelteUIElement(CollectionTimes, { times }))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class KnownIcons extends SpecialVisualization {
|
||||
docs = "Displays all icons from the specified tagRenderings (if they are known and have an icon) together, e.g. to give a summary of the dietary options"
|
||||
needsUrls = []
|
||||
group = "UI"
|
||||
funcName = "show_icons"
|
||||
args: SpecialVisualisationArg[] = [{
|
||||
name: "labels",
|
||||
doc: "A ';'-separated list of labels and/or ids of tagRenderings",
|
||||
type: "key",
|
||||
required: true,
|
||||
}, {
|
||||
name: "class",
|
||||
doc: "CSS-classes of the container, space-separated",
|
||||
type: "css",
|
||||
required: false,
|
||||
defaultValue: "inline-flex mx-4",
|
||||
}]
|
||||
|
||||
private static readonly emojiHeights = {
|
||||
small: "2rem",
|
||||
medium: "3rem",
|
||||
large: "5rem",
|
||||
}
|
||||
|
||||
constr(options: SpecialVisualisationParams): BaseUIElement {
|
||||
const labels = new Set(options.args[0].split(";").map(s => s.trim()))
|
||||
const matchingTrs = options.layer.tagRenderings.filter(
|
||||
tr => labels.has(tr.id) || tr.labels.some(l => labels.has(l)),
|
||||
)
|
||||
return new VariableUiElement(options.tags.map(tags =>
|
||||
new Combine(matchingTrs.map(tr => {
|
||||
const mapping = tr.GetRenderValueWithImage(tags)
|
||||
if (!mapping?.icon) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return new SvelteUIElement(Marker, {
|
||||
emojiHeight: KnownIcons.emojiHeights[mapping.iconClass] ?? "2rem",
|
||||
clss: `mapping-icon-${mapping.iconClass ?? "small"}`,
|
||||
icons: mapping.icon,
|
||||
size: twJoin(
|
||||
"shrink-0",
|
||||
`mapping-icon-${mapping.iconClass ?? "small"}-height mapping-icon-${
|
||||
mapping.iconClass ?? "small"
|
||||
}-width`),
|
||||
|
||||
|
||||
})
|
||||
})
|
||||
).SetClass(options.args[1] ?? "inline-flex mx-4")
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -329,6 +373,7 @@ export class DataVisualisations {
|
|||
new PresetDescription(),
|
||||
new PresetTypeSelect(),
|
||||
new AllTagsVis(),
|
||||
new KnownIcons(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,16 @@
|
|||
const validator = new FediverseValidator()
|
||||
let userinfo = tags
|
||||
.mapD((t) => t[key], onDestroy)
|
||||
.mapD((fediAccount) => FediverseValidator.extractServer(validator.reformat(fediAccount)), onDestroy)
|
||||
.mapD(
|
||||
(fediAccount) => FediverseValidator.extractServer(validator.reformat(fediAccount)),
|
||||
onDestroy
|
||||
)
|
||||
let homeLocation: Store<string> = state.userRelatedState?.preferencesAsTags
|
||||
.mapD((prefs) => prefs["_mastodon_link"], onDestroy)
|
||||
.mapD((userhandle) => FediverseValidator.extractServer(validator.reformat(userhandle))?.server, onDestroy)
|
||||
.mapD(
|
||||
(userhandle) => FediverseValidator.extractServer(validator.reformat(userhandle))?.server,
|
||||
onDestroy
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="flex w-full flex-col">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { Feature } from "geojson"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import Histogram from "../BigComponents/Histogram.svelte"
|
||||
|
@ -14,10 +14,10 @@ export class HistogramViz extends SpecialVisualization {
|
|||
args = [
|
||||
{
|
||||
name: "key",
|
||||
type:"key",
|
||||
doc: "The key to be read and to generate a histogram from",
|
||||
required: true,
|
||||
}
|
||||
|
||||
},
|
||||
]
|
||||
|
||||
structuredExamples(): { feature: Feature; args: string[] }[] {
|
||||
|
@ -36,23 +36,24 @@ export class HistogramViz extends SpecialVisualization {
|
|||
]
|
||||
}
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
) {
|
||||
const values: Store<string[]> = tagSource.map((tags) => {
|
||||
constr( {tags, args}: SpecialVisualisationParams): SvelteUIElement {
|
||||
const values: Store<string[]> = tags.map((tags) => {
|
||||
const value = tags[args[0]]
|
||||
try {
|
||||
if (value === "" || value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if(Array.isArray(value)){
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
return JSON.parse(value)
|
||||
} catch (e) {
|
||||
console.error("Could not load histogram: parsing of the list failed: ", e,"\nthe data to parse is",value)
|
||||
console.error(
|
||||
"Could not load histogram: parsing of the list failed: ",
|
||||
e,
|
||||
"\nthe data to parse is",
|
||||
value
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import {
|
||||
SpecialVisualisationArg,
|
||||
SpecialVisualisationParams,
|
||||
SpecialVisualizationSvelte,
|
||||
SpecialVisualizationUtils,
|
||||
} from "../../SpecialVisualization"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import { Feature, Geometry, LineString, Polygon } from "geojson"
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
import { ImportFlowArguments, ImportFlowUtils } from "./ImportFlow"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import { Utils } from "../../../Utils"
|
||||
|
@ -14,6 +18,7 @@ import { Changes } from "../../../Logic/Osm/Changes"
|
|||
import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
|
||||
import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
|
||||
import { OsmTags } from "../../../Models/OsmFeature"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
|
||||
export interface ConflateFlowArguments extends ImportFlowArguments {
|
||||
way_to_conflate: string
|
||||
|
@ -22,21 +27,17 @@ export interface ConflateFlowArguments extends ImportFlowArguments {
|
|||
snap_onto_layers?: string
|
||||
}
|
||||
|
||||
export default class ConflateImportButtonViz extends SpecialVisualization implements AutoAction {
|
||||
export default class ConflateImportButtonViz extends SpecialVisualizationSvelte implements AutoAction {
|
||||
supportsAutoAction: boolean = true
|
||||
needsUrls = []
|
||||
group = "data_import"
|
||||
|
||||
public readonly funcName: string = "conflate_button"
|
||||
public readonly args: {
|
||||
name: string
|
||||
defaultValue?: string
|
||||
doc: string
|
||||
required?: boolean
|
||||
}[] = [
|
||||
public readonly args: SpecialVisualisationArg[] = [
|
||||
...ImportFlowUtils.generalArguments,
|
||||
{
|
||||
name: "way_to_conflate",
|
||||
type:"key",
|
||||
doc: "The key, of which the corresponding value is the id of the OSM-way that must be conflated; typically a calculatedTag",
|
||||
},
|
||||
]
|
||||
|
@ -84,32 +85,25 @@ export default class ConflateImportButtonViz extends SpecialVisualization implem
|
|||
await state.changes.applyAction(action)
|
||||
}
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<OsmTags>,
|
||||
argument: string[],
|
||||
feature: Feature
|
||||
): BaseUIElement {
|
||||
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const canBeImported =
|
||||
feature.geometry.type === "LineString" ||
|
||||
(feature.geometry.type === "Polygon" && feature.geometry.coordinates.length === 1)
|
||||
if (!canBeImported) {
|
||||
return Translations.t.general.add.import.wrongTypeToConflate.SetClass("alert")
|
||||
return new SvelteUIElement(Tr, { t: Translations.t.general.add.import.wrongTypeToConflate, cls: "alert" })
|
||||
}
|
||||
const args: ConflateFlowArguments = <any>Utils.ParseVisArgs(this.args, argument)
|
||||
const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, args)
|
||||
const idOfWayToReplaceGeometry = tagSource.data[args.way_to_conflate]
|
||||
const argsParsed: ConflateFlowArguments = <any>SpecialVisualizationUtils.parseArgs(this.args, args)
|
||||
const tagsToApply = ImportFlowUtils.getTagsToApply(<UIEventSource<OsmTags>>tags, argsParsed)
|
||||
const idOfWayToReplaceGeometry = tags.data[argsParsed.way_to_conflate]
|
||||
const importFlow = new ConflateImportFlowState(
|
||||
state,
|
||||
<Feature<LineString | Polygon>>feature,
|
||||
args,
|
||||
argsParsed,
|
||||
tagsToApply,
|
||||
tagSource,
|
||||
tags,
|
||||
idOfWayToReplaceGeometry
|
||||
)
|
||||
return new SvelteUIElement(WayImportFlow, {
|
||||
importFlow,
|
||||
})
|
||||
return new SvelteUIElement(WayImportFlow, { importFlow })
|
||||
}
|
||||
|
||||
getLayerDependencies = (args: string[]) =>
|
||||
|
|
|
@ -66,13 +66,14 @@ ${Utils.special_visualizations_importRequirementDocs}
|
|||
* Given the tagsstore of the point which represents the challenge, creates a new store with tags that should be applied onto the newly created point,
|
||||
*/
|
||||
public static getTagsToApply(
|
||||
originalFeatureTags: UIEventSource<OsmTags>,
|
||||
originalFeatureTags: Store<OsmTags>,
|
||||
args: { tags: string }
|
||||
): Store<Tag[]> {
|
||||
if (originalFeatureTags === undefined) {
|
||||
return undefined
|
||||
}
|
||||
let newTags: Store<Tag[]>
|
||||
// Listing of the keys that should be transferred
|
||||
const tags = args.tags
|
||||
if (
|
||||
tags.indexOf(" ") < 0 &&
|
||||
|
@ -81,12 +82,6 @@ ${Utils.special_visualizations_importRequirementDocs}
|
|||
) {
|
||||
// This is a property to expand...
|
||||
const items: string = originalFeatureTags.data[tags]
|
||||
console.debug(
|
||||
"The import button is using tags from properties[" +
|
||||
tags +
|
||||
"] of this object, namely ",
|
||||
items
|
||||
)
|
||||
|
||||
if (items.startsWith("{")) {
|
||||
// This is probably a JSON
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { Feature, Point } from "geojson"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../../SpecialVisualization"
|
||||
import SvelteUIElement from "../../Base/SvelteUIElement"
|
||||
import PointImportFlow from "./PointImportFlow.svelte"
|
||||
import { PointImportFlowArguments, PointImportFlowState } from "./PointImportFlowState"
|
||||
|
@ -9,12 +7,14 @@ import { Utils } from "../../../Utils"
|
|||
import { ImportFlowUtils } from "./ImportFlow"
|
||||
import Translations from "../../i18n/Translations"
|
||||
import { GeoOperations } from "../../../Logic/GeoOperations"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import { OsmTags } from "../../../Models/OsmFeature"
|
||||
|
||||
/**
|
||||
* The wrapper to make the special visualisation for the PointImportFlow
|
||||
*/
|
||||
export class PointImportButtonViz extends SpecialVisualization {
|
||||
export class PointImportButtonViz extends SpecialVisualizationSvelte {
|
||||
public readonly funcName = "import_button"
|
||||
public readonly docs: string =
|
||||
"This button will copy the point from an external dataset into OpenStreetMap" +
|
||||
|
@ -47,29 +47,24 @@ export class PointImportButtonViz extends SpecialVisualization {
|
|||
public needsUrls = []
|
||||
group = "data_import"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<OsmTags>,
|
||||
argument: string[],
|
||||
feature: Feature
|
||||
): BaseUIElement {
|
||||
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const to_point_index = this.args.findIndex((arg) => arg.name === "to_point")
|
||||
const summarizePointArg = argument[to_point_index].toLowerCase()
|
||||
const summarizePointArg = args[to_point_index].toLowerCase()
|
||||
if (feature.geometry.type !== "Point") {
|
||||
if (summarizePointArg !== "no" && summarizePointArg !== "false") {
|
||||
feature = GeoOperations.centerpoint(feature)
|
||||
} else {
|
||||
return Translations.t.general.add.import.wrongType.SetClass("alert")
|
||||
return new SvelteUIElement(Tr, { t: Translations.t.general.add.import.wrongType.SetClass("alert") })
|
||||
}
|
||||
}
|
||||
const baseArgs: PointImportFlowArguments = <any>Utils.ParseVisArgs(this.args, argument)
|
||||
const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, baseArgs)
|
||||
const baseArgs: PointImportFlowArguments = <any>Utils.ParseVisArgs(this.args, args)
|
||||
const tagsToApply = ImportFlowUtils.getTagsToApply(<UIEventSource<OsmTags>>tags, baseArgs)
|
||||
const importFlow = new PointImportFlowState(
|
||||
state,
|
||||
<Feature<Point>>feature,
|
||||
baseArgs,
|
||||
tagsToApply,
|
||||
tagSource
|
||||
tags
|
||||
)
|
||||
|
||||
return new SvelteUIElement(PointImportFlow, {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import {
|
||||
SpecialVisualisationParams,
|
||||
SpecialVisualizationSvelte,
|
||||
SpecialVisualizationUtils,
|
||||
} from "../../SpecialVisualization"
|
||||
import { AutoAction } from "../AutoApplyButtonVis"
|
||||
import { Feature, LineString, Polygon } from "geojson"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
import { ImportFlowUtils } from "./ImportFlow"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import SvelteUIElement from "../../Base/SvelteUIElement"
|
||||
import WayImportFlow from "./WayImportFlow.svelte"
|
||||
import WayImportFlowState, { WayImportFlowArguments } from "./WayImportFlowState"
|
||||
|
@ -13,12 +15,11 @@ import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
|
|||
import { Changes } from "../../../Logic/Osm/Changes"
|
||||
import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource"
|
||||
import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
|
||||
import { OsmTags } from "../../../Models/OsmFeature"
|
||||
|
||||
/**
|
||||
* Wrapper around 'WayImportFlow' to make it a special visualisation
|
||||
*/
|
||||
export default class WayImportButtonViz extends SpecialVisualization implements AutoAction {
|
||||
export default class WayImportButtonViz extends SpecialVisualizationSvelte implements AutoAction {
|
||||
public readonly funcName: string = "import_way_button"
|
||||
needsUrls = []
|
||||
group = "data_import"
|
||||
|
@ -60,25 +61,20 @@ export default class WayImportButtonViz extends SpecialVisualization implements
|
|||
public readonly supportsAutoAction = true
|
||||
public readonly needsNodeDatabase = true
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<OsmTags>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
_: LayerConfig
|
||||
): BaseUIElement {
|
||||
constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const geometry = feature.geometry
|
||||
if (!(geometry.type == "LineString" || geometry.type === "Polygon")) {
|
||||
throw "Invalid type to import " + geometry.type
|
||||
throw "Invalid type to import, expected linestring of polygon but got " + geometry.type
|
||||
}
|
||||
const args: WayImportFlowArguments = <any>Utils.ParseVisArgs(this.args, argument)
|
||||
const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, args)
|
||||
const parsedArgs: WayImportFlowArguments = <any>SpecialVisualizationUtils.parseArgs(this.args, args)
|
||||
console.log("Parsed args are", parsedArgs)
|
||||
const tagsToApply = ImportFlowUtils.getTagsToApply(tags, parsedArgs)
|
||||
const importFlow = new WayImportFlowState(
|
||||
state,
|
||||
<Feature<LineString | Polygon>>feature,
|
||||
args,
|
||||
parsedArgs,
|
||||
tagsToApply,
|
||||
tagSource
|
||||
tags,
|
||||
)
|
||||
return new SvelteUIElement(WayImportFlow, {
|
||||
importFlow,
|
||||
|
|
|
@ -1,46 +1,49 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import BaseUIElement from "../../BaseUIElement"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../../SpecialVisualization"
|
||||
import SvelteUIElement from "../../Base/SvelteUIElement"
|
||||
import { Feature } from "geojson"
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
|
||||
import { default as LanguageElementSvelte } from "./LanguageElement.svelte"
|
||||
|
||||
export class LanguageElement extends SpecialVisualization {
|
||||
export class LanguageElement extends SpecialVisualizationSvelte {
|
||||
funcName: string = "language_chooser"
|
||||
needsUrls = []
|
||||
|
||||
docs: string | BaseUIElement =
|
||||
docs: string =
|
||||
"The language element allows to show and pick all known (modern) languages. The key can be set"
|
||||
|
||||
args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [
|
||||
args: { name: string; defaultValue?: string; doc: string; required?: boolean; type?: string }[] = [
|
||||
{
|
||||
name: "key",
|
||||
required: true,
|
||||
type:"key",
|
||||
doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `<key>:nl=yes` if _nl_ is picked ",
|
||||
},
|
||||
{
|
||||
name: "question",
|
||||
required: true,
|
||||
type: "translation",
|
||||
doc: "What to ask if no questions are known",
|
||||
},
|
||||
{
|
||||
name: "render_list_item",
|
||||
type: "translation",
|
||||
|
||||
doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).",
|
||||
defaultValue: "{language()}",
|
||||
},
|
||||
{
|
||||
name: "render_single_language",
|
||||
type: "translation",
|
||||
doc: "What will be shown if the feature only supports a single language",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "translation",
|
||||
name: "render_all",
|
||||
doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single",
|
||||
doc: "The full rendering. U0se `{list}` to show where the list of languages must come. Optional if mode=single",
|
||||
defaultValue: "{list()}",
|
||||
},
|
||||
{
|
||||
name: "no_known_languages",
|
||||
type: "translation",
|
||||
doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead",
|
||||
},
|
||||
]
|
||||
|
@ -59,14 +62,16 @@ export class LanguageElement extends SpecialVisualization {
|
|||
`
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
{
|
||||
state,
|
||||
tags,
|
||||
args,
|
||||
feature,
|
||||
layer,
|
||||
}: SpecialVisualisationParams,
|
||||
): SvelteUIElement {
|
||||
let [key, question, item_render, single_render, all_render, on_no_known_languages] =
|
||||
argument
|
||||
args
|
||||
if (item_render === undefined || item_render.trim() === "") {
|
||||
item_render = "{language()}"
|
||||
}
|
||||
|
@ -94,7 +99,7 @@ export class LanguageElement extends SpecialVisualization {
|
|||
|
||||
return new SvelteUIElement(LanguageElementSvelte, {
|
||||
key,
|
||||
tags: tagSource,
|
||||
tags,
|
||||
state,
|
||||
feature,
|
||||
layer,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { Feature } from "geojson"
|
||||
import { ImmutableStore } from "../../Logic/UIEventSource"
|
||||
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import MapillaryLink from "../BigComponents/MapillaryLink.svelte"
|
||||
|
||||
|
@ -20,12 +19,7 @@ export class MapillaryLinkVis extends SpecialVisualizationSvelte {
|
|||
},
|
||||
]
|
||||
|
||||
public constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagsSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature
|
||||
): SvelteUIElement {
|
||||
public constr({ args, feature }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||
let zoom = Number(args[0])
|
||||
if (isNaN(zoom)) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { Feature } from "geojson"
|
||||
import type { Feature, Geometry } from "geojson"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
|
@ -8,22 +8,23 @@
|
|||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
|
||||
import MaplibreMap from "../Map/MaplibreMap.svelte"
|
||||
import DelayedComponent from "../Base/DelayedComponent.svelte"
|
||||
import type { OsmTags } from "../../Models/OsmFeature"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
export let tagSource: UIEventSource<Record<string, string>>
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let defaultzoom: number
|
||||
export let idkeys: string[]
|
||||
export let feature: Feature
|
||||
export let clss : string = "h-40 rounded"
|
||||
const keys =idkeys
|
||||
let featuresToShow: Store<Feature[]> = state.indexedFeatures.featuresById.map(
|
||||
export let clss: string = "h-40 rounded"
|
||||
const keys = idkeys
|
||||
let featuresToShow: Store<Feature<Geometry, OsmTags>[]> = state.indexedFeatures.featuresById.map(
|
||||
(featuresById) => {
|
||||
if (featuresById === undefined) {
|
||||
return []
|
||||
}
|
||||
const properties = tagSource.data
|
||||
const features: Feature[] = []
|
||||
const properties = tags.data
|
||||
const features: Feature<Geometry, OsmTags>[] = []
|
||||
for (const key of keys) {
|
||||
const value = properties[key]
|
||||
if (value === undefined || value === null) {
|
||||
|
@ -44,12 +45,13 @@
|
|||
console.warn("No feature found for id ", id)
|
||||
continue
|
||||
}
|
||||
features.push(feature)
|
||||
features.push(<Feature<Geometry, OsmTags>> feature)
|
||||
}
|
||||
}
|
||||
return features
|
||||
},
|
||||
[tagSource], onDestroy
|
||||
[tags],
|
||||
onDestroy,
|
||||
)
|
||||
|
||||
let mlmap = new UIEventSource(undefined)
|
||||
|
@ -65,12 +67,11 @@
|
|||
mla.location.setData({ lon, lat })
|
||||
mla.zoom.setData(defaultzoom)
|
||||
|
||||
|
||||
ShowDataLayer.showMultipleLayers(
|
||||
mlmap,
|
||||
new StaticFeatureSource(featuresToShow),
|
||||
state.theme.layers,
|
||||
{ zoomToFeatures: true }
|
||||
{ zoomToFeatures: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
|
|
|
@ -62,16 +62,17 @@
|
|||
reason.setData(moveWizardState.reasons[0])
|
||||
}
|
||||
let notAllowed = moveWizardState.moveDisallowedReason
|
||||
let currentMapProperties: Store<Partial<MapProperties> & { location }> = reason.mapD((r) =>
|
||||
initMapProperties(r), onDestroy
|
||||
let currentMapProperties: Store<Partial<MapProperties> & { location }> = reason.mapD(
|
||||
(r) => initMapProperties(r),
|
||||
onDestroy
|
||||
)
|
||||
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||
|
||||
let searchValue = new UIEventSource<string>("")
|
||||
let isSearching = new UIEventSource<boolean>(false)
|
||||
let zoomedInEnough = currentMapProperties.bindD(properties => properties.zoom, onDestroy).mapD(
|
||||
(zoom) => zoom >= Constants.minZoomLevelToAddNewPoint, onDestroy
|
||||
)
|
||||
let zoomedInEnough = currentMapProperties
|
||||
.bindD((properties) => properties.zoom, onDestroy)
|
||||
.mapD((zoom) => zoom >= Constants.minZoomLevelToAddNewPoint, onDestroy)
|
||||
const searcher = new NominatimGeocoding(1)
|
||||
|
||||
async function searchPressed() {
|
||||
|
@ -163,9 +164,7 @@
|
|||
</div>
|
||||
|
||||
<div class="flex flex-wrap">
|
||||
<If
|
||||
condition={zoomedInEnough}
|
||||
>
|
||||
<If condition={zoomedInEnough}>
|
||||
<button
|
||||
class="primary w-full"
|
||||
on:click={() => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { MultiApplyParams } from "./MultiApply"
|
||||
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import MultiApplyButton from "./MultiApplyButton.svelte"
|
||||
|
||||
|
@ -19,7 +19,9 @@ export class MultiApplyViz extends SpecialVisualizationSvelte {
|
|||
doc: "One key (or multiple keys, seperated by ';') of the attribute that should be copied onto the other features.",
|
||||
required: true,
|
||||
},
|
||||
{ name: "text", doc: "The text to show on the button" },
|
||||
{ name: "text",
|
||||
type: "translation",
|
||||
doc: "The text to show on the button" },
|
||||
{
|
||||
name: "autoapply",
|
||||
doc: "A boolean indicating wether this tagging should be applied automatically if the relevant tags on this object are changed. A visual element indicating the multi_apply is still shown",
|
||||
|
@ -34,17 +36,13 @@ export class MultiApplyViz extends SpecialVisualizationSvelte {
|
|||
example =
|
||||
"{multi_apply(_features_with_the_same_name_within_100m, name:etymology:wikidata;name:etymology, Apply etymology information on all nearby objects with the same name)}"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagsSource: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
): SvelteUIElement {
|
||||
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const featureIdsKey = args[0]
|
||||
const keysToApply = args[1].split(";")
|
||||
const text = args[2]
|
||||
const autoapply = args[3]?.toLowerCase() === "true"
|
||||
const overwrite = args[4]?.toLowerCase() === "true"
|
||||
const featureIds: Store<string[]> = tagsSource.map((tags) => {
|
||||
const featureIds: Store<string[]> = tags.map((tags) => {
|
||||
const ids = tags[featureIdsKey]
|
||||
try {
|
||||
if (ids === undefined) {
|
||||
|
@ -69,7 +67,7 @@ export class MultiApplyViz extends SpecialVisualizationSvelte {
|
|||
text,
|
||||
autoapply,
|
||||
overwrite,
|
||||
tagsSource,
|
||||
tagsSource: tags,
|
||||
state,
|
||||
}
|
||||
return new SvelteUIElement(MultiApplyButton, { params })
|
||||
|
|
|
@ -25,10 +25,12 @@
|
|||
const t = Translations.t.notes
|
||||
|
||||
// Info about the user who made the comment
|
||||
let userinfo = UIEventSource.fromPromise(Utils.downloadJsonCached<{ user: { img: { href: string } } }>(
|
||||
"https://api.openstreetmap.org/api/0.6/user/" + comment.uid,
|
||||
24 * 60 * 60 * 1000
|
||||
))
|
||||
let userinfo = UIEventSource.fromPromise(
|
||||
Utils.downloadJsonCached<{ user: { img: { href: string } } }>(
|
||||
"https://api.openstreetmap.org/api/0.6/user/" + comment.uid,
|
||||
24 * 60 * 60 * 1000
|
||||
)
|
||||
)
|
||||
|
||||
const htmlElement = document.createElement("div")
|
||||
htmlElement.innerHTML = Utils.purify(comment.html)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||
import Wikidata from "../../Logic/Web/Wikidata"
|
||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
||||
import { And } from "../../Logic/Tags/And"
|
||||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
|
||||
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import PlantNet from "../PlantNet/PlantNet.svelte"
|
||||
import { default as PlantNetCode } from "../../Logic/Web/PlantNet"
|
||||
|
@ -31,16 +31,13 @@ export class PlantNetDetectionViz extends SpecialVisualizationSvelte {
|
|||
args = [
|
||||
{
|
||||
name: "image_key",
|
||||
type:"key",
|
||||
defaultValue: AllImageProviders.defaultKeys.join(","),
|
||||
doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated ",
|
||||
},
|
||||
]
|
||||
|
||||
public constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
): SvelteUIElement {
|
||||
public constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
|
||||
let imagePrefixes: string[] = undefined
|
||||
if (args.length > 0) {
|
||||
imagePrefixes = [].concat(...args.map((a) => a.split(",")))
|
||||
|
|
|
@ -48,17 +48,13 @@
|
|||
return ""
|
||||
}
|
||||
})
|
||||
.map(
|
||||
(id) => {
|
||||
let host = `${window.location.protocol}//${window.location.host}`
|
||||
if(AndroidPolyfill.inAndroid.data){
|
||||
host = "https://mapcomplete.org"
|
||||
}
|
||||
return `${host}${
|
||||
window.location.pathname
|
||||
}?${params.join("&")}${id}`
|
||||
.map((id) => {
|
||||
let host = `${window.location.protocol}//${window.location.host}`
|
||||
if (AndroidPolyfill.inAndroid.data) {
|
||||
host = "https://mapcomplete.org"
|
||||
}
|
||||
)
|
||||
return `${host}${window.location.pathname}?${params.join("&")}${id}`
|
||||
})
|
||||
|
||||
function toggleSize() {
|
||||
if (size.data !== bigSize) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import ShareButton from "../Base/ShareButton.svelte"
|
||||
|
||||
|
@ -17,25 +16,25 @@ export class ShareLinkViz extends SpecialVisualizationSvelte {
|
|||
},
|
||||
{
|
||||
name: "text",
|
||||
type:"translation",
|
||||
doc: "The text to show on the button. If none is given, will act as a titleIcon",
|
||||
},
|
||||
]
|
||||
needsUrls = []
|
||||
|
||||
public constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
public constr({
|
||||
state,
|
||||
tags,
|
||||
args}:SpecialVisualisationParams
|
||||
) {
|
||||
const text = args[1]
|
||||
|
||||
const generateShareData = () => {
|
||||
const title = state?.theme?.title?.txt ?? "MapComplete"
|
||||
|
||||
const matchingLayer: LayerConfig = state?.theme?.getMatchingLayer(tagSource?.data)
|
||||
const matchingLayer: LayerConfig = state?.theme?.getMatchingLayer(tags?.data)
|
||||
let name =
|
||||
matchingLayer?.title?.GetRenderValue(tagSource.data)?.Subs(tagSource.data)?.txt ??
|
||||
tagSource.data?.name ??
|
||||
matchingLayer?.title?.GetRenderValue(tags.data)?.Subs(tags.data)?.txt ??
|
||||
tags.data?.name ??
|
||||
"POI"
|
||||
if (name) {
|
||||
name = `${name} (${title})`
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
onDestroy(
|
||||
value.addCallbackD(() => {
|
||||
dispatch("selected")
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
function getCountry() {
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
{
|
||||
try {
|
||||
return specpart.func
|
||||
.constr(state, tags, specpart.args, feature, layer)
|
||||
.constr({state, tags, args : specpart.args, feature, layer})
|
||||
?.SetClass(specpart.style)
|
||||
} catch (e) {
|
||||
console.error(
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
import { onDestroy } from "svelte"
|
||||
import { Lists } from "../../../Utils/Lists"
|
||||
|
||||
|
||||
export let tags: UIEventSource<Record<string, string> | undefined>
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
|
@ -27,8 +26,9 @@
|
|||
console.error("TagRenderingAnswer: Config is undefined")
|
||||
throw "Config is undefined in tagRenderingAnswer"
|
||||
}
|
||||
let trs: Store<{ then: Translation; icon?: string; iconClass?: string }[]> = tags.mapD((tags) =>
|
||||
Lists.noNull(config?.GetRenderValues(tags)),onDestroy
|
||||
let trs: Store<{ then: Translation; icon?: string; iconClass?: string }[]> = tags.mapD(
|
||||
(tags) => Lists.noNull(config?.GetRenderValues(tags)),
|
||||
onDestroy
|
||||
)
|
||||
</script>
|
||||
|
||||
|
|
|
@ -67,8 +67,9 @@
|
|||
/**
|
||||
* The tags to apply to mark this answer as "unknown"
|
||||
*/
|
||||
let onMarkUnknown: Store<UploadableTag[] | undefined> = tags.mapD((tags) =>
|
||||
config.markUnknown(layer, tags), onDestroy
|
||||
let onMarkUnknown: Store<UploadableTag[] | undefined> = tags.mapD(
|
||||
(tags) => config.markUnknown(layer, tags),
|
||||
onDestroy
|
||||
)
|
||||
let unknownModal = new UIEventSource(false)
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import {
|
||||
SpecialVisualisationParams,
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationState,
|
||||
SpecialVisualizationSvelte,
|
||||
} from "../SpecialVisualization"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import Constants from "../../Models/Constants"
|
||||
|
@ -9,18 +14,14 @@ import { ServerSourceInfo } from "../../Models/SourceOverview"
|
|||
/**
|
||||
* Wrapper around 'UploadTraceToOsmUI'
|
||||
*/
|
||||
export class UploadToOsmViz extends SpecialVisualization {
|
||||
export class UploadToOsmViz extends SpecialVisualizationSvelte {
|
||||
funcName = "upload_to_osm"
|
||||
docs =
|
||||
"Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored."
|
||||
args = []
|
||||
needsUrls: ServerSourceInfo[] = [Constants.osmAuthConfig]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
_: UIEventSource<Record<string, string>>,
|
||||
__: string[]
|
||||
) {
|
||||
constr({ state }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const locations = state.historicalUserLocations.features.data
|
||||
return new SvelteUIElement(UploadTraceToOsmUI, {
|
||||
state,
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
export let state: ThemeViewState
|
||||
export let tags: UIEventSource<Record<string, string>> = undefined
|
||||
export let feature: Feature = undefined
|
||||
export let layer: LayerConfig =undefined
|
||||
export let layer: LayerConfig = undefined
|
||||
let average = reviews.average
|
||||
let allReviews: Store<
|
||||
(Review & {
|
||||
kid: string,
|
||||
signature: string,
|
||||
kid: string
|
||||
signature: string
|
||||
madeByLoggedInUser: Store<boolean>
|
||||
})[]
|
||||
> = reviews.reviews.map((r) => Lists.noNull(r))
|
||||
|
@ -35,27 +35,27 @@
|
|||
</script>
|
||||
|
||||
<ReviewPrivacyShield loadingAllowed={reviews.loadingAllowed} guistate={state.guistate}>
|
||||
<div class="border-2 border-dashed border-gray-300 p-2 flex flex-col gap-y-2">
|
||||
<div class="flex flex-col gap-y-2 border-2 border-dashed border-gray-300 p-2">
|
||||
{#if $allReviews?.length > 1}
|
||||
<StarsBar score={$average} />
|
||||
{/if}
|
||||
{#if !$allReviews}
|
||||
<div class="flex justify-center">
|
||||
<Loading/>
|
||||
<Loading />
|
||||
</div>
|
||||
{:else if $allReviews.length > 0}
|
||||
{#each $allReviews as review}
|
||||
<SingleReview {review} {state} {tags} {feature} {layer} {reviews} showMenu={false}/>
|
||||
<SingleReview {review} {state} {tags} {feature} {layer} {reviews} showMenu={false} />
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="subtle m-2 italic flex justify-center">
|
||||
<div class="subtle m-2 flex justify-center italic">
|
||||
<Tr t={Translations.t.reviews.no_reviews_yet} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex justify-end">
|
||||
<a
|
||||
href={"https://mangrove.reviews" +
|
||||
($allReviews?.length > 0 ?"/search?sub=" + encodeURIComponent($subject):"")}
|
||||
($allReviews?.length > 0 ? "/search?sub=" + encodeURIComponent($subject) : "")}
|
||||
target="_blank"
|
||||
class="subtle text-sm"
|
||||
>
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
*/
|
||||
export let reviews: ReviewCollection
|
||||
export let editReview: Review & { signature: string } = undefined
|
||||
export let subject: Store<string> = editReview !== undefined ? new ImmutableStore(editReview.sub) : reviews.subjectUri
|
||||
export let subject: Store<string> =
|
||||
editReview !== undefined ? new ImmutableStore(editReview.sub) : reviews.subjectUri
|
||||
let score = editReview?.rating ?? 0
|
||||
let confirmedScore = editReview?.rating
|
||||
let isAffiliated = new UIEventSource(editReview?.metadata?.is_affiliated ?? false)
|
||||
|
@ -103,10 +104,13 @@
|
|||
|
||||
_state = "done"
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<ReviewPrivacyShield hiddenIfNotAllowed loadingAllowed={reviews.loadingAllowed} guistate={state.guistate}>
|
||||
<ReviewPrivacyShield
|
||||
hiddenIfNotAllowed
|
||||
loadingAllowed={reviews.loadingAllowed}
|
||||
guistate={state.guistate}
|
||||
>
|
||||
{#if uploadFailed}
|
||||
<div class="alert flex">
|
||||
<ExclamationTriangle class="h-6 w-6" />
|
||||
|
@ -186,7 +190,7 @@
|
|||
<Tr slot="else" t={t.reviewing_as_anonymous} />
|
||||
</If>
|
||||
<button class="primary" class:disabled={$hasError !== undefined} on:click={save}>
|
||||
<Tr t={ editReview === undefined ? t.save : t.edit} />
|
||||
<Tr t={editReview === undefined ? t.save : t.edit} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,25 +8,29 @@
|
|||
import Tr from "../Base/Tr.svelte"
|
||||
|
||||
export let guistate: MenuState
|
||||
export let loadingAllowed : UIEventSource<boolean>
|
||||
export let loadingAllowed: UIEventSource<boolean>
|
||||
export let hiddenIfNotAllowed: boolean = false
|
||||
let t = Translations.t.reviews
|
||||
|
||||
</script>
|
||||
|
||||
{#if $loadingAllowed}
|
||||
<slot />
|
||||
{:else if !hiddenIfNotAllowed && $loadingAllowed !== null}
|
||||
<div class="low-interaction mx-1 flex flex-col rounded">
|
||||
<Tr t={t.disabledForPrivacy}/>
|
||||
<button on:click={() => {loadingAllowed.set(true)}} class="primary">
|
||||
<Tr t={t.loadOnce}/>
|
||||
<Tr t={t.disabledForPrivacy} />
|
||||
<button
|
||||
on:click={() => {
|
||||
loadingAllowed.set(true)
|
||||
}}
|
||||
class="primary"
|
||||
>
|
||||
<Tr t={t.loadOnce} />
|
||||
</button>
|
||||
<button
|
||||
class="as-link self-end"
|
||||
on:click={() => guistate.openUsersettings("mangrove-reviews-allowed")}
|
||||
>
|
||||
<Tr t={t.editPrivacySettings}/>
|
||||
<Tr t={t.editPrivacySettings} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -13,7 +13,10 @@
|
|||
/**
|
||||
* A panel showing all the reviews by the logged-in user
|
||||
*/
|
||||
export let state: ThemeViewState & {osmConnection: OsmConnection, userRelatedState: { mangroveIdentity: MangroveIdentity }}
|
||||
export let state: ThemeViewState & {
|
||||
osmConnection: OsmConnection
|
||||
userRelatedState: { mangroveIdentity: MangroveIdentity }
|
||||
}
|
||||
let allReviews = state.userRelatedState.mangroveIdentity.getAllReviews()
|
||||
let reviews = state.userRelatedState.mangroveIdentity.getGeoReviews()
|
||||
let kid = state.userRelatedState.mangroveIdentity.getKeyId()
|
||||
|
|
|
@ -25,9 +25,8 @@
|
|||
import ValidatedInput from "../InputElement/ValidatedInput.svelte"
|
||||
import type { MapProperties } from "../../Models/MapProperties"
|
||||
|
||||
|
||||
export let state: ThemeViewState & {
|
||||
mapProperties?: MapProperties,
|
||||
mapProperties?: MapProperties
|
||||
userRelatedState?: { mangroveIdentity: MangroveIdentity }
|
||||
} = undefined
|
||||
export let tags: UIEventSource<Record<string, string>> = undefined
|
||||
|
@ -61,7 +60,6 @@
|
|||
state?.guistate?.closeAll()
|
||||
}
|
||||
|
||||
|
||||
let isTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false)
|
||||
|
||||
async function report() {
|
||||
|
@ -71,7 +69,6 @@
|
|||
}
|
||||
const identity: CryptoKeyPair = await state?.userRelatedState?.mangroveIdentity.getKeypair()
|
||||
if (!isTesting.data) {
|
||||
|
||||
try {
|
||||
await MangroveReviews.reportAbuseReview(identity, review, reason)
|
||||
} catch (e) {
|
||||
|
@ -91,7 +88,6 @@
|
|||
const identity: CryptoKeyPair = await state?.userRelatedState?.mangroveIdentity.getKeypair()
|
||||
console.log("Deleting review...", identity)
|
||||
if (!isTesting.data) {
|
||||
|
||||
try {
|
||||
await reviews.deleteReview(review)
|
||||
} catch (e) {
|
||||
|
@ -118,9 +114,9 @@
|
|||
<Tr t={t.deleteTitle} />
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="flex gap-x-4 items-center m-8">
|
||||
<div class="m-8 flex items-center gap-x-4">
|
||||
<Delete_icon class="w-16" />
|
||||
<div class="p-4 low-interaction border-interactive">
|
||||
<div class="low-interaction border-interactive p-4">
|
||||
<StarsBar starSize="w-4 h-4 md:w-6 md:h-6" readonly={true} score={review.rating} />
|
||||
<div class="disable-links">
|
||||
<Markdown src={review.opinion} />
|
||||
|
@ -136,15 +132,14 @@
|
|||
</NextButton>
|
||||
</Popup>
|
||||
|
||||
|
||||
<Popup shown={showReport}>
|
||||
<svelte:fragment slot="header">
|
||||
<Tr t={t.reportReviewTitle} />
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="flex gap-x-4 items-center m-8">
|
||||
<div class="m-8 flex items-center gap-x-4">
|
||||
<ShieldExclamation class="w-16" />
|
||||
<div class="p-4 low-interaction border-interactive">
|
||||
<div class="low-interaction border-interactive p-4">
|
||||
<StarsBar starSize="w-4 h-4 md:w-6 md:h-6" readonly={true} score={review.rating} />
|
||||
<div class="disable-links">
|
||||
<Markdown src={review.opinion} />
|
||||
|
@ -156,13 +151,17 @@
|
|||
<Tr t={t.reportReason} />
|
||||
</h3>
|
||||
<div class="w-full">
|
||||
|
||||
<ValidatedInput type="text" value={reportReason} />
|
||||
</div>
|
||||
|
||||
<Tr t={t.deleteText} />
|
||||
|
||||
<NextButton clss="primary float-right" on:click={() => {report()}}>
|
||||
<NextButton
|
||||
clss="primary float-right"
|
||||
on:click={() => {
|
||||
report()
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="w-12 text-red-600" />
|
||||
<Tr t={t.reportReview} />
|
||||
</NextButton>
|
||||
|
@ -174,7 +173,6 @@
|
|||
</svelte:fragment>
|
||||
|
||||
<ReviewForm {tags} {feature} {layer} {state} editReview={review} {reviews} />
|
||||
|
||||
</Popup>
|
||||
|
||||
<div
|
||||
|
@ -248,21 +246,19 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
<div class="flex gap-x-2 overflow-x-auto items-center">
|
||||
|
||||
{#each review.images ?? [] as image}
|
||||
<div class="w-32 min-w-32">
|
||||
<AttributedImage image={{url: image.src, id: image.src}}></AttributedImage>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex items-center gap-x-2 overflow-x-auto">
|
||||
{#each review.images ?? [] as image}
|
||||
<div class="w-32 min-w-32">
|
||||
<AttributedImage image={{ url: image.src, id: image.src }} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if review.metadata.is_affiliated}
|
||||
<Tr t={Translations.t.reviews.affiliated_reviewer_warning} />
|
||||
{/if}
|
||||
{#if $isDebugging}
|
||||
<div class="subtle">
|
||||
Maresi: {(review.signature)}
|
||||
</div>
|
||||
{/if}
|
||||
{#if $isDebugging}
|
||||
<div class="subtle">
|
||||
Maresi: {review.signature}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -34,7 +34,8 @@
|
|||
}
|
||||
return layers
|
||||
},
|
||||
[activeLayers], onDestroy
|
||||
[activeLayers],
|
||||
onDestroy
|
||||
)
|
||||
let filterResultsClipped: Store<{
|
||||
clipped: (FilterSearchResult[] | LayerConfig)[]
|
||||
|
@ -48,7 +49,8 @@
|
|||
}
|
||||
return { clipped: ls.slice(0, 4), rest: ls.slice(4) }
|
||||
},
|
||||
[layerResults, activeLayers, Locale.language], onDestroy
|
||||
[layerResults, activeLayers, Locale.language],
|
||||
onDestroy
|
||||
)
|
||||
</script>
|
||||
|
||||
|
|
|
@ -33,14 +33,20 @@
|
|||
descriptionTr = layer?.tagRenderings?.find((tr) => tr.labels.indexOf("description") >= 0)
|
||||
}
|
||||
|
||||
let distance = state.mapProperties.location.mapD((l) =>
|
||||
GeoOperations.distanceBetween([l.lon, l.lat], [entry.lon, entry.lat]), onDestroy
|
||||
let distance = state.mapProperties.location.mapD(
|
||||
(l) => GeoOperations.distanceBetween([l.lon, l.lat], [entry.lon, entry.lat]),
|
||||
onDestroy
|
||||
)
|
||||
let bearing = state.mapProperties.location.mapD(
|
||||
(l) => GeoOperations.bearing([l.lon, l.lat], [entry.lon, entry.lat]),
|
||||
onDestroy
|
||||
)
|
||||
let bearing = state.mapProperties.location.mapD((l) =>
|
||||
GeoOperations.bearing([l.lon, l.lat], [entry.lon, entry.lat]), onDestroy)
|
||||
|
||||
let mapRotation = state.mapProperties.rotation
|
||||
let inView = state.mapProperties.bounds.mapD((bounds) => bounds.contains([entry.lon, entry.lat]), onDestroy)
|
||||
let inView = state.mapProperties.bounds.mapD(
|
||||
(bounds) => bounds.contains([entry.lon, entry.lat]),
|
||||
onDestroy
|
||||
)
|
||||
|
||||
let dispatch = createEventDispatcher<{ select: GeocodeResult }>()
|
||||
function select() {
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
suggestions: Store<GeocodeResult[]>
|
||||
suggestionsSearchRunning: Store<boolean>
|
||||
runningEngines: Store<string[]>
|
||||
failedEngines: Store<{
|
||||
source: GeocodingProvider;
|
||||
error: any
|
||||
}[]>
|
||||
failedEngines: Store<
|
||||
{
|
||||
source: GeocodingProvider
|
||||
error: any
|
||||
}[]
|
||||
>
|
||||
}
|
||||
featureSwitchIsTesting?: Store<boolean>
|
||||
userRelatedState?: { showTagsB: Store<boolean> }
|
||||
|
@ -35,7 +37,6 @@
|
|||
let runningEngines = state.searchState.runningEngines ?? new ImmutableStore([])
|
||||
let failedEngines = state.searchState.failedEngines
|
||||
|
||||
|
||||
let isTesting = state.featureSwitchIsTesting ?? new ImmutableStore(false)
|
||||
let showTags = state.userRelatedState?.showTagsB ?? new ImmutableStore(false)
|
||||
const t = Translations.t.general.search
|
||||
|
@ -59,7 +60,7 @@
|
|||
<Tr t={t.searching} />
|
||||
{#if $isTesting || $showTags}
|
||||
<div class="subtle">
|
||||
Querying {$runningEngines?.map(provider => provider.name)?.join(", ")}
|
||||
Querying {$runningEngines?.map((provider) => provider.name)?.join(", ")}
|
||||
</div>
|
||||
{/if}
|
||||
</Loading>
|
||||
|
@ -73,7 +74,7 @@
|
|||
{/if}
|
||||
{#if $failedEngines?.length > 0}
|
||||
{#each $failedEngines as failed}
|
||||
<div class="warning flex flex-col items-center py-4 overflow-hidden">
|
||||
<div class="warning flex flex-col items-center overflow-hidden py-4">
|
||||
<ExclamationTriangle class="w-4" />
|
||||
Search using {failed.source.name} failed
|
||||
<div class="subtle text-sm">
|
||||
|
@ -82,7 +83,6 @@
|
|||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
</SidebarUnit>
|
||||
{:else}
|
||||
<slot name="if-no-results" />
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
{#if !$isOnline}
|
||||
<div class="alert flex items-center">
|
||||
<CrossedOut size="w-8 h-8">
|
||||
<Wifi class="w-8 h-8" />
|
||||
<Wifi class="h-8 w-8" />
|
||||
</CrossedOut>
|
||||
Your device is currently offline. This impacts search results
|
||||
</div>
|
||||
|
|
|
@ -16,9 +16,10 @@
|
|||
|
||||
export let state: WithSearchState
|
||||
let searchTerm = state.searchState.searchTerm
|
||||
let visitedThemes = state.userRelatedState.recentlyVisitedThemes.value
|
||||
let visitedThemes = state.userRelatedState.recentlyVisitedThemes.value
|
||||
let recentThemes: Store<string[]> = visitedThemes.map((themes) =>
|
||||
Lists.dedup(themes.filter((th) => th !== state.theme.id)).slice(0, 6))
|
||||
Lists.dedup(themes.filter((th) => th !== state.theme.id)).slice(0, 6)
|
||||
)
|
||||
let themeResults = state.searchState.themeSuggestions
|
||||
|
||||
const t = Translations.t.general.search
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import {
|
||||
SpecialVisualisationParams,
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationState,
|
||||
SpecialVisualizationSvelte,
|
||||
} from "../SpecialVisualization"
|
||||
import Maproulette from "../../Logic/Maproulette"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import { PointImportButtonViz } from "../Popup/ImportButtons/PointImportButtonViz"
|
||||
|
@ -43,6 +48,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
|
|||
args = [
|
||||
{
|
||||
name: "message",
|
||||
type: "translation",
|
||||
doc: "A message to show to the user",
|
||||
},
|
||||
{
|
||||
|
@ -52,6 +58,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
|
|||
},
|
||||
{
|
||||
name: "message_confirm",
|
||||
type: "translation",
|
||||
doc: "What to show when the task is closed, either by the user or was already closed.",
|
||||
},
|
||||
{
|
||||
|
@ -61,17 +68,19 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
|
|||
},
|
||||
{
|
||||
name: "maproulette_id",
|
||||
type:"key",
|
||||
doc: "The property name containing the maproulette id",
|
||||
defaultValue: "mr_taskId",
|
||||
},
|
||||
{
|
||||
name: "ask_feedback",
|
||||
type: "translation",
|
||||
doc: "If not an empty string, this will be used as question to ask some additional feedback. A text field will be added",
|
||||
defaultValue: "",
|
||||
},
|
||||
]
|
||||
|
||||
constr(state, tagsSource, args) {
|
||||
constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement {
|
||||
let [message, image, message_closed, statusToSet, maproulette_id_key, askFeedback] = args
|
||||
if (image === "") {
|
||||
image = "confirm"
|
||||
|
@ -82,7 +91,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
|
|||
statusToSet = statusToSet ?? "1"
|
||||
return new SvelteUIElement(MaprouletteSetStatus, {
|
||||
state,
|
||||
tags: tagsSource,
|
||||
tags,
|
||||
message,
|
||||
image,
|
||||
message_closed,
|
||||
|
@ -102,6 +111,7 @@ class LinkedDataFromWebsite extends SpecialVisualization {
|
|||
{
|
||||
name: "key",
|
||||
defaultValue: "website",
|
||||
type:"key",
|
||||
doc: "Attempt to load ld+json from the specified URL. This can be in an embedded <script type='ld+json'>",
|
||||
},
|
||||
{
|
||||
|
@ -140,21 +150,15 @@ class LinkedDataFromWebsite extends SpecialVisualization {
|
|||
},
|
||||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
if(state.theme.enableMorePrivacy){
|
||||
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement {
|
||||
if (state.theme.enableMorePrivacy) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const key = argument[0] ?? "website"
|
||||
const useProxy = argument[1] !== "no"
|
||||
const readonly = argument[3] === "readonly"
|
||||
const isClosed = (argument[4] ?? "yes") === "yes"
|
||||
const key = args[0] ?? "website"
|
||||
const useProxy = args[1] !== "no"
|
||||
const readonly = args[3] === "readonly"
|
||||
const isClosed = (args[4] ?? "yes") === "yes"
|
||||
const downloadInformation = new UIEventSource(false)
|
||||
const countryStore: Store<string | undefined> = tags.mapD((tags) => tags._country)
|
||||
const sourceUrl: Store<string | undefined> = tags.mapD((tags) => {
|
||||
|
@ -165,51 +169,55 @@ class LinkedDataFromWebsite extends SpecialVisualization {
|
|||
})
|
||||
const externalData: Store<{ success: GeoJsonProperties } | { error }> = sourceUrl.bindD(
|
||||
(url) => {
|
||||
if(!downloadInformation.data){
|
||||
if (!downloadInformation.data) {
|
||||
return undefined
|
||||
}
|
||||
const country = countryStore.data
|
||||
if (url.startsWith("https://data.velopark.be/")) {
|
||||
return UIEventSource.fromPromiseWithErr((async () => {
|
||||
try {
|
||||
const loadAll = layer.id.toLowerCase().indexOf("maproulette") >= 0 // Dirty hack
|
||||
const features = await LinkedDataLoader.fetchVeloparkEntry(
|
||||
url,
|
||||
loadAll
|
||||
)
|
||||
const feature =
|
||||
features.find((f) => f.properties["ref:velopark"] === url) ??
|
||||
features[0]
|
||||
const properties = feature.properties
|
||||
properties["ref:velopark"] = url
|
||||
console.log("Got properties from velopark:", properties)
|
||||
return properties
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
})())
|
||||
return UIEventSource.fromPromiseWithErr(
|
||||
(async () => {
|
||||
try {
|
||||
const loadAll = layer.id.toLowerCase().indexOf("maproulette") >= 0 // Dirty hack
|
||||
const features = await LinkedDataLoader.fetchVeloparkEntry(
|
||||
url,
|
||||
loadAll
|
||||
)
|
||||
const feature =
|
||||
features.find((f) => f.properties["ref:velopark"] === url) ??
|
||||
features[0]
|
||||
const properties = feature.properties
|
||||
properties["ref:velopark"] = url
|
||||
console.log("Got properties from velopark:", properties)
|
||||
return properties
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
})()
|
||||
)
|
||||
}
|
||||
if (country === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return UIEventSource.fromPromiseWithErr((async () => {
|
||||
try {
|
||||
return await LinkedDataLoader.fetchJsonLd(
|
||||
url,
|
||||
{ country },
|
||||
useProxy ? "proxy" : "fetch-lod"
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(
|
||||
"Could not get with proxy/download LOD, attempting to download directly. Error for ",
|
||||
url,
|
||||
"is",
|
||||
e
|
||||
)
|
||||
return await LinkedDataLoader.fetchJsonLd(url, { country }, "fetch-raw")
|
||||
}
|
||||
})())
|
||||
return UIEventSource.fromPromiseWithErr(
|
||||
(async () => {
|
||||
try {
|
||||
return await LinkedDataLoader.fetchJsonLd(
|
||||
url,
|
||||
{ country },
|
||||
useProxy ? "proxy" : "fetch-lod"
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(
|
||||
"Could not get with proxy/download LOD, attempting to download directly. Error for ",
|
||||
url,
|
||||
"is",
|
||||
e
|
||||
)
|
||||
return await LinkedDataLoader.fetchJsonLd(url, { country }, "fetch-raw")
|
||||
}
|
||||
})()
|
||||
)
|
||||
},
|
||||
[countryStore, downloadInformation]
|
||||
)
|
||||
|
@ -218,23 +226,21 @@ class LinkedDataFromWebsite extends SpecialVisualization {
|
|||
console.log("linked_data_from_website received the following data:", lod)
|
||||
)
|
||||
|
||||
|
||||
|
||||
return new SvelteUIElement(ComparisonTool, {
|
||||
feature,
|
||||
state,
|
||||
tags,
|
||||
layer,
|
||||
externalData,
|
||||
sourceUrl,
|
||||
readonly,
|
||||
feature,
|
||||
state,
|
||||
tags,
|
||||
layer,
|
||||
externalData,
|
||||
sourceUrl,
|
||||
readonly,
|
||||
downloadInformation,
|
||||
collapsed: isClosed,
|
||||
collapsed: isClosed,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class CompareData extends SpecialVisualization {
|
||||
class CompareData extends SpecialVisualizationSvelte {
|
||||
funcName = "compare_data"
|
||||
group = "data_import"
|
||||
needsUrls = (args) => args[1].split(";")
|
||||
|
@ -242,6 +248,7 @@ class CompareData extends SpecialVisualization {
|
|||
{
|
||||
name: "url",
|
||||
required: true,
|
||||
type:"key",
|
||||
doc: "The attribute containing the url where to fetch more data",
|
||||
},
|
||||
{
|
||||
|
@ -258,20 +265,14 @@ class CompareData extends SpecialVisualization {
|
|||
docs =
|
||||
"Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement {
|
||||
const url = args[0]
|
||||
const readonly = args[3] === "yes"
|
||||
const externalData = UIEventSource.fromPromiseWithErr(Utils.downloadJson(url))
|
||||
return new SvelteUIElement(ComparisonTool, {
|
||||
url,
|
||||
state,
|
||||
tags: tagSource,
|
||||
tags,
|
||||
layer,
|
||||
feature,
|
||||
readonly,
|
||||
|
@ -280,7 +281,7 @@ class CompareData extends SpecialVisualization {
|
|||
}
|
||||
}
|
||||
export class DataImportSpecialVisualisations {
|
||||
public static initList(): (SpecialVisualization & { group })[] {
|
||||
public static initList(): SpecialVisualizationSvelte[] {
|
||||
return [
|
||||
new TagApplyViz(),
|
||||
new PointImportButtonViz(),
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Feature } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import MarkAsFavourite from "../Popup/MarkAsFavourite.svelte"
|
||||
import MarkAsFavouriteMini from "../Popup/MarkAsFavouriteMini.svelte"
|
||||
|
@ -14,19 +11,8 @@ class FavouriteStatus extends SpecialVisualizationSvelte {
|
|||
args = []
|
||||
group = "favourites"
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(MarkAsFavourite, {
|
||||
tags: tagSource,
|
||||
state,
|
||||
layer,
|
||||
feature,
|
||||
})
|
||||
constr(params: SpecialVisualisationParams): SvelteUIElement {
|
||||
return new SvelteUIElement(MarkAsFavourite, params)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,19 +23,8 @@ class FavouriteIcon extends SpecialVisualizationSvelte {
|
|||
"A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon"
|
||||
args = []
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): SvelteUIElement {
|
||||
return new SvelteUIElement(MarkAsFavouriteMini, {
|
||||
tags: tagSource,
|
||||
state,
|
||||
layer,
|
||||
feature,
|
||||
})
|
||||
constr(params: SpecialVisualisationParams): SvelteUIElement {
|
||||
return new SvelteUIElement(MarkAsFavouriteMini, params)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue