Chore: run prettier

This commit is contained in:
Pieter Vander Vennet 2023-06-15 16:12:46 +02:00
parent 147c3db957
commit 9661ade80c
19 changed files with 910 additions and 897 deletions

View file

@ -14,7 +14,8 @@
<SubtleButton
on:click={() => dispatch("click")}
options={{ extraClasses: twMerge("flex items-center", clss) }}>
options={{ extraClasses: twMerge("flex items-center", clss) }}
>
<ChevronLeftIcon class="h-12 w-12" slot="image" />
<slot slot="message" />
</SubtleButton>

View file

@ -15,7 +15,7 @@
if (hideSignal) {
onDestroy(
hideSignal.addCallbackD(() => {
if(initTime + 1000 > Date.now()){
if (initTime + 1000 > Date.now()) {
console.log("Ignoring hide signal")
return
}
@ -32,7 +32,7 @@
}
</script>
<div bind:this={mainElem} class="absolute bottom-0 right-0 h-full w-full pointer-events-none">
<div bind:this={mainElem} class="pointer-events-none absolute bottom-0 right-0 h-full w-full">
<div id="hand-container">
<img src="./assets/svg/hand.svg" />
</div>

View file

@ -11,6 +11,7 @@
<button
on:click={(e) => dispatch("click", e)}
class={twJoin("pointer-events-auto m-0.5 h-fit w-fit rounded-full p-0.5 sm:p-1 md:m-1", cls)}>
class={twJoin("pointer-events-auto m-0.5 h-fit w-fit rounded-full p-0.5 sm:p-1 md:m-1", cls)}
>
<slot />
</button>

View file

@ -15,7 +15,8 @@
<SubtleButton
on:click={() => dispatch("click")}
options={{ extraClasses: twMerge("flex items-center", clss) }}>
options={{ extraClasses: twMerge("flex items-center", clss) }}
>
<slot name="image" slot="image" />
<div class="flex w-full items-center justify-between" slot="message">
<slot />

View file

@ -17,7 +17,8 @@
<button
class={twMerge(options.extraClasses, "secondary no-image-background")}
on:click={(e) => dispatch("click", e)}>
on:click={(e) => dispatch("click", e)}
>
<slot name="image">
{#if imageUrl !== undefined}
{#if typeof imageUrl === "string"}

View file

@ -17,7 +17,7 @@
}
</script>
<div class="tabbedgroup h-full flex w-full">
<div class="tabbedgroup flex h-full w-full">
<TabGroup
class="flex h-full w-full flex-col"
defaultIndex={1}
@ -67,7 +67,7 @@
</TabList>
<slot name="post-tablist" />
</div>
<div class="normal-background overflow-y-auto h-full">
<div class="normal-background h-full overflow-y-auto">
<TabPanels class="tabpanels" defaultIndex={$tab}>
<TabPanel class="tabpanel">
<slot name="content0">
@ -96,16 +96,15 @@
max-height: 100vh;
height: 100%;
}
:global(.tabpanel) {
height:100%;
height: 100%;
}
:global(.tabpanels) {
height: calc( 100% - 2rem );
height: calc(100% - 2rem);
}
:global(.tab) {
margin: 0.25rem;
padding: 0.25rem;

View file

@ -15,7 +15,7 @@
import FeatureSourceMerger from "../../Logic/FeatureSource/Sources/FeatureSourceMerger"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { Utils } from "../../Utils"
import {createEventDispatcher} from "svelte";
import { createEventDispatcher } from "svelte"
/**
* An advanced location input, which has support to:
@ -44,8 +44,8 @@
lon: number
lat: number
}>(undefined)
const dispatch = createEventDispatcher<{click: {lon: number, lat: number}}>()
const dispatch = createEventDispatcher<{ click: { lon: number; lat: number } }>()
const xyz = Tiles.embedded_tile(coordinate.lat, coordinate.lon, 16)
const map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
@ -109,7 +109,7 @@
<LocationInput
{map}
on:click={data => dispatch("click", data)}
on:click={(data) => dispatch("click", data)}
mapProperties={initialMapProperties}
value={preciseLocation}
initialCoordinate={coordinate}

View file

@ -3,21 +3,19 @@
* A mapcontrol button which allows the user to select a different background.
* Even though the componenet is very small, it gets it's own class as it is often reused
*/
import {Square3Stack3dIcon} from "@babeard/svelte-heroicons/solid";
import type {SpecialVisualizationState} from "../SpecialVisualization";
import Translations from "../i18n/Translations";
import MapControlButton from "../Base/MapControlButton.svelte";
import Tr from "../Base/Tr.svelte";
import { Square3Stack3dIcon } from "@babeard/svelte-heroicons/solid"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import Translations from "../i18n/Translations"
import MapControlButton from "../Base/MapControlButton.svelte"
import Tr from "../Base/Tr.svelte"
export let state: SpecialVisualizationState
export let hideTooltip = false
</script>
<MapControlButton
on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}
>
<Square3Stack3dIcon class="h-6 w-6"/>
<MapControlButton on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}>
<Square3Stack3dIcon class="h-6 w-6" />
{#if !hideTooltip}
<Tr cls="mx-2" t={Translations.t.general.backgroundSwitch}/>
<Tr cls="mx-2" t={Translations.t.general.backgroundSwitch} />
{/if}
</MapControlButton>

View file

@ -1,40 +1,41 @@
<script lang="ts">
import ThemeViewState from "../../Models/ThemeViewState";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import Loading from "../Base/Loading.svelte";
import ThemeViewState from "../../Models/ThemeViewState"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import Loading from "../Base/Loading.svelte"
export let state: ThemeViewState
/**
* Gives the contributor some feedback based on the current state:
* - is data loading?
* - Is all data hidden due to filters?
* - Is no data in view?
*/
export let state: ThemeViewState
/**
* Gives the contributor some feedback based on the current state:
* - is data loading?
* - Is all data hidden due to filters?
* - Is no data in view?
*/
let dataIsLoading = state.dataIsLoading
let currentState = state.hasDataInView
currentState.data === ""
const t = Translations.t.centerMessage
let dataIsLoading = state.dataIsLoading
let currentState = state.hasDataInView
currentState.data === ""
const t = Translations.t.centerMessage
</script>
{#if $currentState === "has-visible-features"}
<!-- don't show anything -->
<!-- don't show anything -->
{:else if $currentState === "zoom-to-low"}
<div class="w-fit p-4 alert">
<Tr t={t.zoomIn}/>
</div>
<div class="alert w-fit p-4">
<Tr t={t.zoomIn} />
</div>
{:else if $currentState === "all-filtered-away"}
<div class="w-fit p-4 alert">
<Tr t={t.allFilteredAway}/>
</div>
<div class="alert w-fit p-4">
<Tr t={t.allFilteredAway} />
</div>
{:else if $dataIsLoading}
<div class="w-fit p-4 alert">
<Loading>
<Tr t={Translations.t.centerMessage.loadingData}/>
</Loading>
</div>
<div class="alert w-fit p-4">
<Loading>
<Tr t={Translations.t.centerMessage.loadingData} />
</Loading>
</div>
{:else if $currentState === "no-data"}
<div class="w-fit p-4 alert">
<Tr t={t.noData}/>
</div>
<div class="alert w-fit p-4">
<Tr t={t.noData} />
</div>
{/if}

View file

@ -1,104 +1,102 @@
<script lang="ts">
import Translations from "../i18n/Translations"
import Svg from "../../Svg"
import Tr from "../Base/Tr.svelte"
import NextButton from "../Base/NextButton.svelte"
import Geosearch from "./Geosearch.svelte"
import IfNot from "../Base/IfNot.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import ThemeViewState from "../../Models/ThemeViewState"
import If from "../Base/If.svelte"
import {UIEventSource} from "../../Logic/UIEventSource"
import {SearchIcon} from "@rgossiaux/svelte-heroicons/solid"
import {twJoin} from "tailwind-merge"
import {Utils} from "../../Utils";
import Translations from "../i18n/Translations"
import Svg from "../../Svg"
import Tr from "../Base/Tr.svelte"
import NextButton from "../Base/NextButton.svelte"
import Geosearch from "./Geosearch.svelte"
import IfNot from "../Base/IfNot.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import ThemeViewState from "../../Models/ThemeViewState"
import If from "../Base/If.svelte"
import { UIEventSource } from "../../Logic/UIEventSource"
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
import { twJoin } from "tailwind-merge"
import { Utils } from "../../Utils"
/**
* The theme introduction panel
*/
export let state: ThemeViewState
let layout = state.layout
let selectedElement = state.selectedElement
let selectedLayer = state.selectedLayer
/**
* The theme introduction panel
*/
export let state: ThemeViewState
let layout = state.layout
let selectedElement = state.selectedElement
let selectedLayer = state.selectedLayer
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
let searchEnabled = false
let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
let searchEnabled = false
function jumpToCurrentLocation() {
const glstate = state.geolocation.geolocationState
if (glstate.currentGPSLocation.data !== undefined) {
const c: GeolocationCoordinates = glstate.currentGPSLocation.data
state.guistate.themeIsOpened.setData(false)
const coor = {lon: c.longitude, lat: c.latitude}
state.mapProperties.location.setData(coor)
}
if (glstate.permission.data !== "granted") {
glstate.requestPermission()
return
}
function jumpToCurrentLocation() {
const glstate = state.geolocation.geolocationState
if (glstate.currentGPSLocation.data !== undefined) {
const c: GeolocationCoordinates = glstate.currentGPSLocation.data
state.guistate.themeIsOpened.setData(false)
const coor = { lon: c.longitude, lat: c.latitude }
state.mapProperties.location.setData(coor)
}
if (glstate.permission.data !== "granted") {
glstate.requestPermission()
return
}
}
</script>
<div class="flex flex-col justify-between h-full">
<div class="flex h-full flex-col justify-between">
<div>
<!-- Intro, description, ... -->
<Tr t={layout.description} />
<Tr t={Translations.t.general.welcomeExplanation.general} />
{#if layout.layers.some((l) => l.presets?.length > 0)}
<If condition={state.featureSwitches.featureSwitchAddNew}>
<Tr t={Translations.t.general.welcomeExplanation.addNew} />
</If>
{/if}
<div>
<Tr t={layout.descriptionTail} />
<!-- Intro, description, ... -->
<Tr t={layout.description}/>
<Tr t={Translations.t.general.welcomeExplanation.general}/>
{#if layout.layers.some((l) => l.presets?.length > 0)}
<If condition={state.featureSwitches.featureSwitchAddNew}>
<Tr t={Translations.t.general.welcomeExplanation.addNew}/>
</If>
{/if}
<!-- Buttons: open map, go to location, search -->
<NextButton clss="primary w-full" on:click={() => state.guistate.themeIsOpened.setData(false)}>
<div class="flex w-full justify-center text-2xl">
<Tr t={Translations.t.general.openTheMap} />
</div>
</NextButton>
<Tr t={layout.descriptionTail}/>
<!-- Buttons: open map, go to location, search -->
<NextButton clss="primary w-full" on:click={() => state.guistate.themeIsOpened.setData(false)}>
<div class="flex w-full justify-center text-2xl">
<Tr t={Translations.t.general.openTheMap}/>
</div>
</NextButton>
<div class="flex w-full flex-wrap sm:flex-nowrap">
<IfNot condition={state.geolocation.geolocationState.permission.map((p) => p === "denied")}>
<button class="flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")} />
<Tr t={Translations.t.general.openTheMapAtGeolocation} />
</button>
</IfNot>
<div class="flex w-full flex-wrap sm:flex-nowrap">
<IfNot condition={state.geolocation.geolocationState.permission.map((p) => p === "denied")}>
<button class="flex w-full items-center gap-x-2" on:click={jumpToCurrentLocation}>
<ToSvelte construct={Svg.crosshair_svg().SetClass("w-8 h-8")}/>
<Tr t={Translations.t.general.openTheMapAtGeolocation}/>
</button>
</IfNot>
<div class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2">
<div class="w-full">
<Geosearch
bounds={state.mapProperties.bounds}
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
on:searchIsValid={(isValid) => {
searchEnabled = isValid
}}
perLayer={state.perLayer}
{selectedElement}
{selectedLayer}
{triggerSearch}
/>
</div>
<button
class={twJoin("flex items-center justify-between gap-x-2", !searchEnabled && "disabled")}
on:click={() => triggerSearch.ping()}>
<Tr t={Translations.t.general.search.searchShort}/>
<SearchIcon class="h-6 w-6"/>
</button>
</div>
<div class=".button low-interaction m-1 flex w-full items-center gap-x-2 rounded border p-2">
<div class="w-full">
<Geosearch
bounds={state.mapProperties.bounds}
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
on:searchIsValid={(isValid) => {
searchEnabled = isValid
}}
perLayer={state.perLayer}
{selectedElement}
{selectedLayer}
{triggerSearch}
/>
</div>
<button
class={twJoin("flex items-center justify-between gap-x-2", !searchEnabled && "disabled")}
on:click={() => triggerSearch.ping()}
>
<Tr t={Translations.t.general.search.searchShort} />
<SearchIcon class="h-6 w-6" />
</button>
</div>
</div>
<div class="links-as-button links-w-full m-2 flex flex-col gap-y-1">
</div>
<div class="links-as-button links-w-full m-2 flex flex-col gap-y-1">
<!-- bottom buttons, a bit hidden away: switch layout -->
<a class="flex" href={Utils.HomepageLink()}>
<img class="h-6 w-6" src="./assets/svg/add.svg"/>
<Tr t={Translations.t.general.backToIndex}/>
<img class="h-6 w-6" src="./assets/svg/add.svg" />
<Tr t={Translations.t.general.backToIndex} />
</a>
</div>
</div>

View file

@ -127,7 +127,8 @@
draggable="false"
on:mousedown={click}
src="./assets/svg/elevator.svg"
style={`top: ${top}px;`} />
style={`top: ${top}px;`}
/>
</div>
</div>

View file

@ -1,96 +1,96 @@
<script lang="ts">
import {Store, UIEventSource} from "../../../Logic/UIEventSource"
import type {MapProperties} from "../../../Models/MapProperties"
import {Map as MlMap} from "maplibre-gl"
import {MapLibreAdaptor} from "../../Map/MapLibreAdaptor"
import MaplibreMap from "../../Map/MaplibreMap.svelte"
import DragInvitation from "../../Base/DragInvitation.svelte"
import {GeoOperations} from "../../../Logic/GeoOperations"
import ShowDataLayer from "../../Map/ShowDataLayer"
import * as boundsdisplay from "../../../assets/layers/range/range.json"
import StaticFeatureSource from "../../../Logic/FeatureSource/Sources/StaticFeatureSource"
import * as turf from "@turf/turf"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import {createEventDispatcher, onDestroy} from "svelte"
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
import type { MapProperties } from "../../../Models/MapProperties"
import { Map as MlMap } from "maplibre-gl"
import { MapLibreAdaptor } from "../../Map/MapLibreAdaptor"
import MaplibreMap from "../../Map/MaplibreMap.svelte"
import DragInvitation from "../../Base/DragInvitation.svelte"
import { GeoOperations } from "../../../Logic/GeoOperations"
import ShowDataLayer from "../../Map/ShowDataLayer"
import * as boundsdisplay from "../../../assets/layers/range/range.json"
import StaticFeatureSource from "../../../Logic/FeatureSource/Sources/StaticFeatureSource"
import * as turf from "@turf/turf"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { createEventDispatcher, onDestroy } from "svelte"
/**
* A visualisation to pick a location on a map background
*/
export let value: UIEventSource<{ lon: number; lat: number }>
export let initialCoordinate: { lon: number; lat: number }
initialCoordinate = initialCoordinate ?? value.data
export let maxDistanceInMeters: number = undefined
export let mapProperties: Partial<MapProperties> & {
readonly location: UIEventSource<{ lon: number; lat: number }>
} = undefined
/**
* Called when setup is done, can be used to add more layers to the map
*/
export let onCreated: (
value: Store<{
lon: number
lat: number
}>,
map: Store<MlMap>,
mapProperties: MapProperties
) => void = undefined
/**
* A visualisation to pick a location on a map background
*/
export let value: UIEventSource<{ lon: number; lat: number }>
export let initialCoordinate: { lon: number; lat: number }
initialCoordinate = initialCoordinate ?? value.data
export let maxDistanceInMeters: number = undefined
export let mapProperties: Partial<MapProperties> & {
readonly location: UIEventSource<{ lon: number; lat: number }>
} = undefined
/**
* Called when setup is done, can be used to add more layers to the map
*/
export let onCreated: (
value: Store<{
lon: number
lat: number
}>,
map: Store<MlMap>,
mapProperties: MapProperties
) => void = undefined
const dispatch = createEventDispatcher<{ click: { lon: number, lat: number } }>()
const dispatch = createEventDispatcher<{ click: { lon: number; lat: number } }>()
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let mla = new MapLibreAdaptor(map, mapProperties)
mla.lastClickLocation.addCallbackAndRunD(lastClick => {
dispatch("click", lastClick)
})
mapProperties.location.syncWith(value)
if (onCreated) {
onCreated(value, map, mla)
}
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let mla = new MapLibreAdaptor(map, mapProperties)
mla.lastClickLocation.addCallbackAndRunD((lastClick) => {
dispatch("click", lastClick)
})
mapProperties.location.syncWith(value)
if (onCreated) {
onCreated(value, map, mla)
}
let rangeIsShown = false
if (maxDistanceInMeters) {
onDestroy(
mla.location.addCallbackD((newLocation) => {
const l = [newLocation.lon, newLocation.lat]
const c: [number, number] = [initialCoordinate.lon, initialCoordinate.lat]
const d = GeoOperations.distanceBetween(l, c)
console.log("distance is", d, l, c)
if (d <= maxDistanceInMeters) {
return
}
// This is too far away - let's move back
const correctLocation = GeoOperations.along(c, l, maxDistanceInMeters - 10)
window.setTimeout(() => {
mla.location.setData({lon: correctLocation[0], lat: correctLocation[1]})
}, 25)
let rangeIsShown = false
if (maxDistanceInMeters) {
onDestroy(
mla.location.addCallbackD((newLocation) => {
const l = [newLocation.lon, newLocation.lat]
const c: [number, number] = [initialCoordinate.lon, initialCoordinate.lat]
const d = GeoOperations.distanceBetween(l, c)
console.log("distance is", d, l, c)
if (d <= maxDistanceInMeters) {
return
}
// This is too far away - let's move back
const correctLocation = GeoOperations.along(c, l, maxDistanceInMeters - 10)
window.setTimeout(() => {
mla.location.setData({ lon: correctLocation[0], lat: correctLocation[1] })
}, 25)
if (!rangeIsShown) {
new ShowDataLayer(map, {
layer: new LayerConfig(boundsdisplay),
features: new StaticFeatureSource([
turf.circle(c, maxDistanceInMeters, {
units: "meters",
properties: {range: "yes", id: "0"},
}),
]),
})
rangeIsShown = true
}
})
)
}
if (!rangeIsShown) {
new ShowDataLayer(map, {
layer: new LayerConfig(boundsdisplay),
features: new StaticFeatureSource([
turf.circle(c, maxDistanceInMeters, {
units: "meters",
properties: { range: "yes", id: "0" },
}),
]),
})
rangeIsShown = true
}
})
)
}
</script>
<div class="min-h-32 relative h-full cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 h-full w-full cursor-pointer">
<MaplibreMap {map}/>
</div>
<div class="absolute top-0 left-0 h-full w-full cursor-pointer">
<MaplibreMap {map} />
</div>
<div
class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center p-8 opacity-50"
>
<img class="h-full max-h-24" src="./assets/svg/move-arrows.svg"/>
</div>
<div
class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center p-8 opacity-50"
>
<img class="h-full max-h-24" src="./assets/svg/move-arrows.svg" />
</div>
<DragInvitation hideSignal={mla.location}/>
<DragInvitation hideSignal={mla.location} />
</div>

View file

@ -1,349 +1,356 @@
<script lang="ts">
/**
* This component ties together all the steps that are needed to create a new point.
* There are many subcomponents which help with that
*/
import type {SpecialVisualizationState} from "../../SpecialVisualization"
import PresetList from "./PresetList.svelte"
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import Tr from "../../Base/Tr.svelte"
import SubtleButton from "../../Base/SubtleButton.svelte"
import FromHtml from "../../Base/FromHtml.svelte"
import Translations from "../../i18n/Translations.js"
import TagHint from "../TagHint.svelte"
import {And} from "../../../Logic/Tags/And.js"
import LoginToggle from "../../Base/LoginToggle.svelte"
import Constants from "../../../Models/Constants.js"
import FilteredLayer from "../../../Models/FilteredLayer"
import {Store, UIEventSource} from "../../../Logic/UIEventSource"
import {EyeIcon, EyeOffIcon} from "@rgossiaux/svelte-heroicons/solid"
import LoginButton from "../../Base/LoginButton.svelte"
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"
import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"
import {OsmWay} from "../../../Logic/Osm/OsmObject"
import {Tag} from "../../../Logic/Tags/Tag"
import type {WayId} from "../../../Models/OsmFeature"
import Loading from "../../Base/Loading.svelte"
import type {GlobalFilter} from "../../../Models/GlobalFilter"
import {onDestroy} from "svelte"
import NextButton from "../../Base/NextButton.svelte"
import BackButton from "../../Base/BackButton.svelte"
import ToSvelte from "../../Base/ToSvelte.svelte"
import Svg from "../../../Svg"
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte";
import { twJoin } from "tailwind-merge"
/**
* This component ties together all the steps that are needed to create a new point.
* There are many subcomponents which help with that
*/
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import PresetList from "./PresetList.svelte"
import type PresetConfig from "../../../Models/ThemeConfig/PresetConfig"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import Tr from "../../Base/Tr.svelte"
import SubtleButton from "../../Base/SubtleButton.svelte"
import FromHtml from "../../Base/FromHtml.svelte"
import Translations from "../../i18n/Translations.js"
import TagHint from "../TagHint.svelte"
import { And } from "../../../Logic/Tags/And.js"
import LoginToggle from "../../Base/LoginToggle.svelte"
import Constants from "../../../Models/Constants.js"
import FilteredLayer from "../../../Models/FilteredLayer"
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
import { EyeIcon, EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid"
import LoginButton from "../../Base/LoginButton.svelte"
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"
import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"
import { OsmWay } from "../../../Logic/Osm/OsmObject"
import { Tag } from "../../../Logic/Tags/Tag"
import type { WayId } from "../../../Models/OsmFeature"
import Loading from "../../Base/Loading.svelte"
import type { GlobalFilter } from "../../../Models/GlobalFilter"
import { onDestroy } from "svelte"
import NextButton from "../../Base/NextButton.svelte"
import BackButton from "../../Base/BackButton.svelte"
import ToSvelte from "../../Base/ToSvelte.svelte"
import Svg from "../../../Svg"
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"
import { twJoin } from "tailwind-merge"
export let coordinate: { lon: number; lat: number }
export let state: SpecialVisualizationState
export let coordinate: { lon: number; lat: number }
export let state: SpecialVisualizationState
let selectedPreset: {
preset: PresetConfig
layer: LayerConfig
icon: string
tags: Record<string, string>
} = undefined
let checkedOfGlobalFilters: number = 0
let confirmedCategory = false
$: if (selectedPreset === undefined) {
confirmedCategory = false
creating = false
checkedOfGlobalFilters = 0
}
let selectedPreset: {
preset: PresetConfig
layer: LayerConfig
icon: string
tags: Record<string, string>
} = undefined
let checkedOfGlobalFilters: number = 0
let confirmedCategory = false
$: if (selectedPreset === undefined) {
confirmedCategory = false
creating = false
checkedOfGlobalFilters = 0
}
let flayer: FilteredLayer = undefined
let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined
let layerHasFilters: Store<boolean> | undefined = undefined
let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters
let _globalFilter: GlobalFilter[] = []
onDestroy(
globalFilter.addCallbackAndRun((globalFilter) => {
console.log("Global filters are", globalFilter)
_globalFilter = globalFilter ?? []
})
let flayer: FilteredLayer = undefined
let layerIsDisplayed: UIEventSource<boolean> | undefined = undefined
let layerHasFilters: Store<boolean> | undefined = undefined
let globalFilter: UIEventSource<GlobalFilter[]> = state.layerState.globalFilters
let _globalFilter: GlobalFilter[] = []
onDestroy(
globalFilter.addCallbackAndRun((globalFilter) => {
console.log("Global filters are", globalFilter)
_globalFilter = globalFilter ?? []
})
)
$: {
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id)
layerIsDisplayed = flayer?.isDisplayed
layerHasFilters = flayer?.hasFilter
}
const t = Translations.t.general.add
const zoom = state.mapProperties.zoom
const isLoading = state.dataIsLoading
let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined)
// Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map
let preciseInputIsTapped = false
let creating = false
/**
* Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters.
* Will delete the lastclick-location
*/
function abort() {
state.selectedElement.setData(undefined)
// When aborted, we force the contributors to place the pin _again_
// This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map
state.lastClickObject.features.setData([])
preciseInputIsTapped = false
}
async function confirm() {
creating = true
const location: { lon: number; lat: number } = preciseCoordinate.data
const snapTo: WayId | undefined = <WayId>snappedToObject.data
const tags: Tag[] = selectedPreset.preset.tags.concat(
..._globalFilter.map((f) => f?.onNewPoint?.tags ?? [])
)
$: {
flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id)
layerIsDisplayed = flayer?.isDisplayed
layerHasFilters = flayer?.hasFilter
}
const t = Translations.t.general.add
console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags)
const zoom = state.mapProperties.zoom
const isLoading = state.dataIsLoading
let preciseCoordinate: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
let snappedToObject: UIEventSource<string> = new UIEventSource<string>(undefined)
// Small helper variable: if the map is tapped, we should let the 'Next'-button grab some attention as users have to click _that_ to continue, not the map
let preciseInputIsTapped = false
let creating = false
/**
* Call when the user should restart the flow by clicking on the map, e.g. because they disabled filters.
* Will delete the lastclick-location
*/
function abort() {
state.selectedElement.setData(undefined)
// When aborted, we force the contributors to place the pin _again_
// This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map
state.lastClickObject.features.setData([])
preciseInputIsTapped = false
let snapToWay: undefined | OsmWay = undefined
if (snapTo !== undefined) {
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0)
if (downloaded !== "deleted") {
snapToWay = downloaded
}
}
async function confirm() {
creating = true
const location: { lon: number; lat: number } = preciseCoordinate.data
const snapTo: WayId | undefined = <WayId>snappedToObject.data
const tags: Tag[] = selectedPreset.preset.tags.concat(
..._globalFilter.map((f) => f?.onNewPoint?.tags ?? [])
)
console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags)
let snapToWay: undefined | OsmWay = undefined
if (snapTo !== undefined) {
const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0)
if (downloaded !== "deleted") {
snapToWay = downloaded
}
}
const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, {
theme: state.layout?.id ?? "unkown",
changeType: "create",
snapOnto: snapToWay,
})
await state.changes.applyAction(newElementAction)
state.newFeatures.features.ping()
// The 'changes' should have created a new point, which added this into the 'featureProperties'
const newId = newElementAction.newElementId
console.log("Applied pending changes, fetching store for", newId)
const tagsStore = state.featureProperties.getStore(newId)
{
// Set some metainfo
const properties = tagsStore.data
if (snapTo) {
// metatags (starting with underscore) are not uploaded, so we can safely mark this
delete properties["_referencing_ways"]
properties["_referencing_ways"] = `["${snapTo}"]`
}
properties["_backend"] = state.osmConnection.Backend()
properties["_last_edit:timestamp"] = new Date().toISOString()
const userdetails = state.osmConnection.userDetails.data
properties["_last_edit:contributor"] = userdetails.name
properties["_last_edit:uid"] = "" + userdetails.uid
tagsStore.ping()
}
const feature = state.indexedFeatures.featuresById.data.get(newId)
abort()
state.selectedLayer.setData(selectedPreset.layer)
state.selectedElement.setData(feature)
tagsStore.ping()
const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, {
theme: state.layout?.id ?? "unkown",
changeType: "create",
snapOnto: snapToWay,
})
await state.changes.applyAction(newElementAction)
state.newFeatures.features.ping()
// The 'changes' should have created a new point, which added this into the 'featureProperties'
const newId = newElementAction.newElementId
console.log("Applied pending changes, fetching store for", newId)
const tagsStore = state.featureProperties.getStore(newId)
{
// Set some metainfo
const properties = tagsStore.data
if (snapTo) {
// metatags (starting with underscore) are not uploaded, so we can safely mark this
delete properties["_referencing_ways"]
properties["_referencing_ways"] = `["${snapTo}"]`
}
properties["_backend"] = state.osmConnection.Backend()
properties["_last_edit:timestamp"] = new Date().toISOString()
const userdetails = state.osmConnection.userDetails.data
properties["_last_edit:contributor"] = userdetails.name
properties["_last_edit:uid"] = "" + userdetails.uid
tagsStore.ping()
}
const feature = state.indexedFeatures.featuresById.data.get(newId)
abort()
state.selectedLayer.setData(selectedPreset.layer)
state.selectedElement.setData(feature)
tagsStore.ping()
}
</script>
<LoginToggle ignoreLoading={true} {state}>
<!-- This component is basically one big if/then/else flow checking for many conditions and edge cases that (in some cases) have to be handled;
<!-- This component is basically one big if/then/else flow checking for many conditions and edge cases that (in some cases) have to be handled;
1. the first (and outermost) is of course: are we logged in?
2. What do we want to add?
3. Are all elements of this category visible? (i.e. there are no filters possibly hiding this, is the data still loading, ...) -->
<LoginButton osmConnection={state.osmConnection} slot="not-logged-in">
<Tr slot="message" t={Translations.t.general.add.pleaseLogin}/>
</LoginButton>
{#if $isLoading}
<div class="alert">
<Loading>
<Tr t={Translations.t.general.add.stillLoading}/>
</Loading>
</div>
{:else if $zoom < Constants.minZoomLevelToAddNewPoint}
<div class="alert">
<Tr t={Translations.t.general.add.zoomInFurther}/>
</div>
{:else if selectedPreset === undefined}
<!-- First, select the correct preset -->
<PresetList
{state}
on:select={(event) => {
<LoginButton osmConnection={state.osmConnection} slot="not-logged-in">
<Tr slot="message" t={Translations.t.general.add.pleaseLogin} />
</LoginButton>
{#if $isLoading}
<div class="alert">
<Loading>
<Tr t={Translations.t.general.add.stillLoading} />
</Loading>
</div>
{:else if $zoom < Constants.minZoomLevelToAddNewPoint}
<div class="alert">
<Tr t={Translations.t.general.add.zoomInFurther} />
</div>
{:else if selectedPreset === undefined}
<!-- First, select the correct preset -->
<PresetList
{state}
on:select={(event) => {
selectedPreset = event.detail
}}
/>
{:else if !$layerIsDisplayed}
<!-- Check that the layer is enabled, so that we don't add a duplicate -->
<div class="alert flex items-center justify-center">
<EyeOffIcon class="w-8"/>
<Tr
t={Translations.t.general.add.layerNotEnabled.Subs({ layer: selectedPreset.layer.name })}
/>
</div>
/>
{:else if !$layerIsDisplayed}
<!-- Check that the layer is enabled, so that we don't add a duplicate -->
<div class="alert flex items-center justify-center">
<EyeOffIcon class="w-8" />
<Tr
t={Translations.t.general.add.layerNotEnabled.Subs({ layer: selectedPreset.layer.name })}
/>
</div>
<div class="flex flex-wrap-reverse md:flex-nowrap">
<button
class="flex w-full gap-x-1"
on:click={() => {
<div class="flex flex-wrap-reverse md:flex-nowrap">
<button
class="flex w-full gap-x-1"
on:click={() => {
abort()
state.guistate.openFilterView(selectedPreset.layer)
}}
>
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")}/>
<Tr t={Translations.t.general.add.openLayerControl}/>
</button>
>
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
<Tr t={Translations.t.general.add.openLayerControl} />
</button>
<button
class="primary flex w-full gap-x-1"
on:click={() => {
<button
class="primary flex w-full gap-x-1"
on:click={() => {
layerIsDisplayed.setData(true)
abort()
}}
>
<EyeIcon class="w-12"/>
<Tr t={Translations.t.general.add.enableLayer.Subs({ name: selectedPreset.layer.name })}/>
</button>
</div>
{:else if $layerHasFilters}
<!-- Some filters are enabled. The feature to add might already be mapped, but hidden -->
<div class="alert flex items-center justify-center">
<EyeOffIcon class="w-8"/>
<Tr t={Translations.t.general.add.disableFiltersExplanation}/>
</div>
<div class="flex flex-wrap-reverse md:flex-nowrap">
<button
class="primary flex w-full gap-x-1"
on:click={() => {
>
<EyeIcon class="w-12" />
<Tr t={Translations.t.general.add.enableLayer.Subs({ name: selectedPreset.layer.name })} />
</button>
</div>
{:else if $layerHasFilters}
<!-- Some filters are enabled. The feature to add might already be mapped, but hidden -->
<div class="alert flex items-center justify-center">
<EyeOffIcon class="w-8" />
<Tr t={Translations.t.general.add.disableFiltersExplanation} />
</div>
<div class="flex flex-wrap-reverse md:flex-nowrap">
<button
class="primary flex w-full gap-x-1"
on:click={() => {
abort()
state.layerState.filteredLayers.get(selectedPreset.layer.id).disableAllFilters()
}}
>
<EyeOffIcon class="w-12"/>
<Tr t={Translations.t.general.add.disableFilters}/>
</button>
<button
class="flex w-full gap-x-1"
on:click={() => {
>
<EyeOffIcon class="w-12" />
<Tr t={Translations.t.general.add.disableFilters} />
</button>
<button
class="flex w-full gap-x-1"
on:click={() => {
abort()
state.guistate.openFilterView(selectedPreset.layer)
}}
>
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")}/>
<Tr t={Translations.t.general.add.openLayerControl}/>
</button>
</div>
{:else if !confirmedCategory}
<!-- Second, confirm the category -->
<h2 class="mr-12">
<Tr
t={Translations.t.general.add.confirmTitle.Subs({ title: selectedPreset.preset.title })}
/>
</h2>
>
<ToSvelte construct={Svg.layers_svg().SetClass("w-12")} />
<Tr t={Translations.t.general.add.openLayerControl} />
</button>
</div>
{:else if !confirmedCategory}
<!-- Second, confirm the category -->
<h2 class="mr-12">
<Tr
t={Translations.t.general.add.confirmTitle.Subs({ title: selectedPreset.preset.title })}
/>
</h2>
<Tr t={Translations.t.general.add.confirmIntro}/>
<Tr t={Translations.t.general.add.confirmIntro} />
{#if selectedPreset.preset.description}
<Tr t={selectedPreset.preset.description}/>
{#if selectedPreset.preset.description}
<Tr t={selectedPreset.preset.description} />
{/if}
{#if selectedPreset.preset.exampleImages}
<h3>
{#if selectedPreset.preset.exampleImages.length === 1}
<Tr t={Translations.t.general.example} />
{:else}
<Tr t={Translations.t.general.examples} />
{/if}
{#if selectedPreset.preset.exampleImages}
<h3>
{#if selectedPreset.preset.exampleImages.length === 1}
<Tr t={Translations.t.general.example}/>
{:else}
<Tr t={Translations.t.general.examples}/>
{/if}
</h3>
<span class="flex flex-wrap items-stretch">
</h3>
<span class="flex flex-wrap items-stretch">
{#each selectedPreset.preset.exampleImages as src}
<img {src} class="m-1 h-64 w-auto rounded-lg"/>
<img {src} class="m-1 h-64 w-auto rounded-lg" />
{/each}
</span>
{/if}
<TagHint
embedIn={(tags) => t.presetInfo.Subs({ tags })}
{state}
tags={new And(selectedPreset.preset.tags)}
/>
{/if}
<TagHint
embedIn={(tags) => t.presetInfo.Subs({ tags })}
{state}
tags={new And(selectedPreset.preset.tags)}
/>
<div class="flex w-full flex-wrap-reverse md:flex-nowrap">
<BackButton on:click={() => (selectedPreset = undefined)} clss="w-full">
<Tr t={t.backToSelect}/>
</BackButton>
<div class="flex w-full flex-wrap-reverse md:flex-nowrap">
<BackButton on:click={() => (selectedPreset = undefined)} clss="w-full">
<Tr t={t.backToSelect} />
</BackButton>
<NextButton on:click={() => (confirmedCategory = true)} clss="primary w-full">
<div slot="image" class="relative">
<FromHtml src={selectedPreset.icon}/>
<img class="absolute bottom-0 right-0 h-4 w-4" src="./assets/svg/confirm.svg"/>
</div>
<div class="w-full">
<Tr t={selectedPreset.text}/>
</div>
</NextButton>
<NextButton on:click={() => (confirmedCategory = true)} clss="primary w-full">
<div slot="image" class="relative">
<FromHtml src={selectedPreset.icon} />
<img class="absolute bottom-0 right-0 h-4 w-4" src="./assets/svg/confirm.svg" />
</div>
{:else if _globalFilter?.length > 0 && _globalFilter?.length > checkedOfGlobalFilters}
<Tr t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.safetyCheck} cls="mx-12"/>
<SubtleButton
on:click={() => {
<div class="w-full">
<Tr t={selectedPreset.text} />
</div>
</NextButton>
</div>
{:else if _globalFilter?.length > 0 && _globalFilter?.length > checkedOfGlobalFilters}
<Tr t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.safetyCheck} cls="mx-12" />
<SubtleButton
on:click={() => {
checkedOfGlobalFilters = checkedOfGlobalFilters + 1
}}
>
<img
slot="image"
src={_globalFilter[checkedOfGlobalFilters].onNewPoint?.icon ?? "./assets/svg/confirm.svg"}
class="h-12 w-12"
/>
<Tr
slot="message"
t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.confirmAddNew.Subs({
>
<img
slot="image"
src={_globalFilter[checkedOfGlobalFilters].onNewPoint?.icon ?? "./assets/svg/confirm.svg"}
class="h-12 w-12"
/>
<Tr
slot="message"
t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.confirmAddNew.Subs({
preset: selectedPreset.preset,
})}
/>
</SubtleButton>
<SubtleButton
on:click={() => {
/>
</SubtleButton>
<SubtleButton
on:click={() => {
globalFilter.setData([])
abort()
}}
>
<img slot="image" src="./assets/svg/close.svg" class="h-8 w-8"/>
<Tr slot="message" t={Translations.t.general.cancel}/>
</SubtleButton>
{:else if !creating}
<div class="relative w-full p-1">
<div class="h-96 max-h-screen w-full overflow-hidden rounded-xl">
<NewPointLocationInput
on:click={() => {preciseInputIsTapped = true}}
value={preciseCoordinate}
snappedTo={snappedToObject}
{state}
{coordinate}
targetLayer={selectedPreset.layer}
snapToLayers={selectedPreset.preset.preciseInput.snapToLayers}
/>
</div>
>
<img slot="image" src="./assets/svg/close.svg" class="h-8 w-8" />
<Tr slot="message" t={Translations.t.general.cancel} />
</SubtleButton>
{:else if !creating}
<div class="relative w-full p-1">
<div class="h-96 max-h-screen w-full overflow-hidden rounded-xl">
<NewPointLocationInput
on:click={() => {
preciseInputIsTapped = true
}}
value={preciseCoordinate}
snappedTo={snappedToObject}
{state}
{coordinate}
targetLayer={selectedPreset.layer}
snapToLayers={selectedPreset.preset.preciseInput.snapToLayers}
/>
</div>
<div class={twJoin(!preciseInputIsTapped && "hidden", "absolute top-0 p-12 flex justify-center w-full")}>
<NextButton on:click={confirm} clss="primary w-fit">
<div class="flex w-full justify-end gap-x-2">
<Tr t={Translations.t.general.add.confirmLocation}/>
</div>
</NextButton>
</div>
<div
class={twJoin(
!preciseInputIsTapped && "hidden",
"absolute top-0 flex w-full justify-center p-12"
)}
>
<NextButton on:click={confirm} clss="primary w-fit">
<div class="flex w-full justify-end gap-x-2">
<Tr t={Translations.t.general.add.confirmLocation} />
</div>
</NextButton>
</div>
<div class="absolute bottom-0 left-0 p-4">
<OpenBackgroundSelectorButton {state}/>
</div>
<div class="absolute bottom-0 left-0 p-4">
<OpenBackgroundSelectorButton {state} />
</div>
</div>
<div class="flex flex-wrap-reverse md:flex-nowrap">
<BackButton on:click={() => (selectedPreset = undefined)} clss="w-full">
<Tr t={t.backToSelect} />
</BackButton>
<NextButton on:click={confirm} clss={"primary w-full"}>
<div class="flex w-full justify-end gap-x-2">
<Tr t={Translations.t.general.add.confirmLocation} />
</div>
<div class="flex flex-wrap-reverse md:flex-nowrap">
<BackButton on:click={() => (selectedPreset = undefined)} clss="w-full">
<Tr t={t.backToSelect}/>
</BackButton>
<NextButton on:click={confirm} clss={"primary w-full"}>
<div class="flex w-full justify-end gap-x-2">
<Tr t={Translations.t.general.add.confirmLocation}/>
</div>
</NextButton>
</div>
{:else}
<Loading>Creating point...</Loading>
{/if}
</NextButton>
</div>
{:else}
<Loading>Creating point...</Loading>
{/if}
</LoginToggle>

View file

@ -18,7 +18,7 @@
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
import Loading from "../../Base/Loading.svelte"
import { DeleteFlowState } from "./DeleteFlowState"
import { twJoin } from "tailwind-merge"
import { twJoin } from "tailwind-merge"
export let state: SpecialVisualizationState
export let deleteConfig: DeleteConfig
@ -112,10 +112,13 @@
<button
slot="save-button"
on:click={onDelete}
class={twJoin((selectedTags === undefined && "disabled"), "primary flex bg-red-600")}
class={twJoin(selectedTags === undefined && "disabled", "primary flex bg-red-600")}
>
<TrashIcon
class={twJoin("ml-1 mr-2 h-6 w-6 rounded-full p-1", selectedTags !== undefined && "bg-red-600")}
class={twJoin(
"ml-1 mr-2 h-6 w-6 rounded-full p-1",
selectedTags !== undefined && "bg-red-600"
)}
/>
<Tr t={t.delete} />
</button>

View file

@ -1,10 +1,10 @@
<script lang="ts">
import ImportFlow from "./ImportFlow.svelte"
import {PointImportFlowState} from "./PointImportFlowState"
import { PointImportFlowState } from "./PointImportFlowState"
import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import {UIEventSource} from "../../../Logic/UIEventSource"
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte";
import { UIEventSource } from "../../../Logic/UIEventSource"
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"
export let importFlow: PointImportFlowState
@ -52,7 +52,7 @@
/>
</div>
<div class="absolute bottom-0">
<OpenBackgroundSelectorButton {state}/>
<OpenBackgroundSelectorButton {state} />
</div>
</div>
</ImportFlow>

View file

@ -4,16 +4,16 @@
*/
import WayImportFlowState from "./WayImportFlowState"
import ImportFlow from "./ImportFlow.svelte"
import {UIEventSource} from "../../../Logic/UIEventSource"
import {Map as MlMap} from "maplibre-gl"
import {MapLibreAdaptor} from "../../Map/MapLibreAdaptor"
import { UIEventSource } from "../../../Logic/UIEventSource"
import { Map as MlMap } from "maplibre-gl"
import { MapLibreAdaptor } from "../../Map/MapLibreAdaptor"
import MaplibreMap from "../../Map/MaplibreMap.svelte"
import ShowDataLayer from "../../Map/ShowDataLayer"
import StaticFeatureSource from "../../../Logic/FeatureSource/Sources/StaticFeatureSource"
import {ImportFlowUtils} from "./ImportFlow"
import {GeoOperations} from "../../../Logic/GeoOperations"
import { ImportFlowUtils } from "./ImportFlow"
import { GeoOperations } from "../../../Logic/GeoOperations"
import ConflateImportFlowState from "./ConflateImportFlowState"
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte";
import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte"
export let importFlow: WayImportFlowState | ConflateImportFlowState
@ -51,7 +51,7 @@
<MaplibreMap {map} />
</div>
<div class="absolute bottom-0">
<OpenBackgroundSelectorButton/>
<OpenBackgroundSelectorButton />
</div>
</div>
</ImportFlow>

View file

@ -32,7 +32,8 @@
class={twJoin(`mapping-icon-${mapping.iconClass}`, "mr-1")}
src={mapping.icon}
aria-hidden="true"
alt="" />
alt=""
/>
<SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement} />
</div>
{:else if mapping.then !== undefined}

View file

@ -277,7 +277,8 @@
<slot name="save-button" {selectedTags}>
<button
on:click={onSave}
class={twJoin(selectedTags === undefined ? "disabled" : "button-shadow", "primary")}>
class={twJoin(selectedTags === undefined ? "disabled" : "button-shadow", "primary")}
>
<Tr t={Translations.t.general.save} />
</button>
</slot>

View file

@ -1,440 +1,440 @@
<script lang="ts">
import {Store, UIEventSource} from "../Logic/UIEventSource"
import {Map as MlMap} from "maplibre-gl"
import MaplibreMap from "./Map/MaplibreMap.svelte"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import MapControlButton from "./Base/MapControlButton.svelte"
import ToSvelte from "./Base/ToSvelte.svelte"
import If from "./Base/If.svelte"
import {GeolocationControl} from "./BigComponents/GeolocationControl"
import type {Feature} from "geojson"
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import Filterview from "./BigComponents/Filterview.svelte"
import ThemeViewState from "../Models/ThemeViewState"
import type {MapProperties} from "../Models/MapProperties"
import Geosearch from "./BigComponents/Geosearch.svelte"
import Translations from "./i18n/Translations"
import {CogIcon, EyeIcon, MenuIcon, XCircleIcon} from "@rgossiaux/svelte-heroicons/solid"
import { Store, UIEventSource } from "../Logic/UIEventSource"
import { Map as MlMap } from "maplibre-gl"
import MaplibreMap from "./Map/MaplibreMap.svelte"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import MapControlButton from "./Base/MapControlButton.svelte"
import ToSvelte from "./Base/ToSvelte.svelte"
import If from "./Base/If.svelte"
import { GeolocationControl } from "./BigComponents/GeolocationControl"
import type { Feature } from "geojson"
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import Filterview from "./BigComponents/Filterview.svelte"
import ThemeViewState from "../Models/ThemeViewState"
import type { MapProperties } from "../Models/MapProperties"
import Geosearch from "./BigComponents/Geosearch.svelte"
import Translations from "./i18n/Translations"
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
import FloatOver from "./Base/FloatOver.svelte"
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
import Constants from "../Models/Constants"
import TabbedGroup from "./Base/TabbedGroup.svelte"
import UserRelatedState from "../Logic/State/UserRelatedState"
import LoginToggle from "./Base/LoginToggle.svelte"
import LoginButton from "./Base/LoginButton.svelte"
import CopyrightPanel from "./BigComponents/CopyrightPanel"
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
import ModalRight from "./Base/ModalRight.svelte"
import {Utils} from "../Utils"
import Hotkeys from "./Base/Hotkeys"
import {VariableUiElement} from "./Base/VariableUIElement"
import SvelteUIElement from "./Base/SvelteUIElement"
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
import LevelSelector from "./BigComponents/LevelSelector.svelte"
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
import Svg from "../Svg"
import {ShareScreen} from "./BigComponents/ShareScreen"
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
import type {RasterLayerPolygon} from "../Models/RasterLayers"
import {AvailableRasterLayers} from "../Models/RasterLayers"
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
import IfHidden from "./Base/IfHidden.svelte"
import {onDestroy} from "svelte"
import {OpenJosm} from "./BigComponents/OpenJosm"
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
import Loading from "./Base/Loading.svelte";
import StateIndicator from "./BigComponents/StateIndicator.svelte";
import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
import FloatOver from "./Base/FloatOver.svelte"
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
import Constants from "../Models/Constants"
import TabbedGroup from "./Base/TabbedGroup.svelte"
import UserRelatedState from "../Logic/State/UserRelatedState"
import LoginToggle from "./Base/LoginToggle.svelte"
import LoginButton from "./Base/LoginButton.svelte"
import CopyrightPanel from "./BigComponents/CopyrightPanel"
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
import ModalRight from "./Base/ModalRight.svelte"
import { Utils } from "../Utils"
import Hotkeys from "./Base/Hotkeys"
import { VariableUiElement } from "./Base/VariableUIElement"
import SvelteUIElement from "./Base/SvelteUIElement"
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
import LevelSelector from "./BigComponents/LevelSelector.svelte"
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
import Svg from "../Svg"
import { ShareScreen } from "./BigComponents/ShareScreen"
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
import type { RasterLayerPolygon } from "../Models/RasterLayers"
import { AvailableRasterLayers } from "../Models/RasterLayers"
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
import IfHidden from "./Base/IfHidden.svelte"
import { onDestroy } from "svelte"
import { OpenJosm } from "./BigComponents/OpenJosm"
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
import Loading from "./Base/Loading.svelte"
import StateIndicator from "./BigComponents/StateIndicator.svelte"
export let state: ThemeViewState
let layout = state.layout
export let state: ThemeViewState
let layout = state.layout
let maplibremap: UIEventSource<MlMap> = state.map
let selectedElement: UIEventSource<Feature> = state.selectedElement
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
let maplibremap: UIEventSource<MlMap> = state.map
let selectedElement: UIEventSource<Feature> = state.selectedElement
let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer
const selectedElementView = selectedElement.map(
(selectedElement) => {
// Svelte doesn't properly reload some of the legacy UI-elements
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
const layer = selectedLayer.data
if (selectedElement === undefined || layer === undefined) {
return undefined
}
const selectedElementView = selectedElement.map(
(selectedElement) => {
// Svelte doesn't properly reload some of the legacy UI-elements
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
const layer = selectedLayer.data
if (selectedElement === undefined || layer === undefined) {
return undefined
}
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
return undefined
}
if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) {
return undefined
}
const tags = state.featureProperties.getStore(selectedElement.properties.id)
return new SvelteUIElement(SelectedElementView, {state, layer, selectedElement, tags})
},
[selectedLayer]
)
const tags = state.featureProperties.getStore(selectedElement.properties.id)
return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags })
},
[selectedLayer]
)
const selectedElementTitle = selectedElement.map(
(selectedElement) => {
// Svelte doesn't properly reload some of the legacy UI-elements
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
const layer = selectedLayer.data
if (selectedElement === undefined || layer === undefined) {
return undefined
}
const selectedElementTitle = selectedElement.map(
(selectedElement) => {
// Svelte doesn't properly reload some of the legacy UI-elements
// As such, we _reconstruct_ the selectedElementView every time a new feature is selected
// This is a bit wasteful, but until everything is a svelte-component, this should do the trick
const layer = selectedLayer.data
if (selectedElement === undefined || layer === undefined) {
return undefined
}
const tags = state.featureProperties.getStore(selectedElement.properties.id)
return new SvelteUIElement(SelectedElementTitle, {state, layer, selectedElement, tags})
},
[selectedLayer]
)
const tags = state.featureProperties.getStore(selectedElement.properties.id)
return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags })
},
[selectedLayer]
)
let mapproperties: MapProperties = state.mapProperties
let featureSwitches: FeatureSwitchState = state.featureSwitches
let availableLayers = state.availableLayers
let userdetails = state.osmConnection.userDetails
let currentViewLayer = layout.layers.find((l) => l.id === "current_view")
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
let rasterLayerName =
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maplibre.properties.name
onDestroy(
rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name
})
)
let mapproperties: MapProperties = state.mapProperties
let featureSwitches: FeatureSwitchState = state.featureSwitches
let availableLayers = state.availableLayers
let userdetails = state.osmConnection.userDetails
let currentViewLayer = layout.layers.find((l) => l.id === "current_view")
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
let rasterLayerName =
rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maplibre.properties.name
onDestroy(
rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name
})
)
</script>
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
<MaplibreMap map={maplibremap}/>
<MaplibreMap map={maplibremap} />
</div>
<div class="pointer-events-none absolute top-0 left-0 w-full">
<!-- Top components -->
<If condition={state.featureSwitches.featureSwitchSearch}>
<div class="pointer-events-auto float-right mt-1 px-1 max-[480px]:w-full sm:m-2">
<Geosearch
bounds={state.mapProperties.bounds}
perLayer={state.perLayer}
{selectedElement}
{selectedLayer}
/>
</div>
</If>
<div class="float-left m-1 flex flex-col sm:mt-2">
<MapControlButton on:click={() => state.guistate.themeIsOpened.setData(true)}>
<div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2">
<img class="mr-0.5 block h-6 w-6 sm:mr-1 md:mr-2 md:h-8 md:w-8" src={layout.icon}/>
<b class="mr-1">
<Tr t={layout.title}/>
</b>
</div>
</MapControlButton>
<MapControlButton on:click={() => state.guistate.menuIsOpened.setData(true)}>
<MenuIcon class="h-8 w-8 cursor-pointer"/>
</MapControlButton>
{#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()}
<MapControlButton
on:click={() => {
<!-- Top components -->
<If condition={state.featureSwitches.featureSwitchSearch}>
<div class="pointer-events-auto float-right mt-1 px-1 max-[480px]:w-full sm:m-2">
<Geosearch
bounds={state.mapProperties.bounds}
perLayer={state.perLayer}
{selectedElement}
{selectedLayer}
/>
</div>
</If>
<div class="float-left m-1 flex flex-col sm:mt-2">
<MapControlButton on:click={() => state.guistate.themeIsOpened.setData(true)}>
<div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2">
<img class="mr-0.5 block h-6 w-6 sm:mr-1 md:mr-2 md:h-8 md:w-8" src={layout.icon} />
<b class="mr-1">
<Tr t={layout.title} />
</b>
</div>
</MapControlButton>
<MapControlButton on:click={() => state.guistate.menuIsOpened.setData(true)}>
<MenuIcon class="h-8 w-8 cursor-pointer" />
</MapControlButton>
{#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()}
<MapControlButton
on:click={() => {
selectedLayer.setData(currentViewLayer)
selectedElement.setData(state.currentView.features?.data?.[0])
}}
>
<ToSvelte
construct={() => currentViewLayer.defaultIcon().SetClass("w-8 h-8 cursor-pointer")}
/>
</MapControlButton>
{/if}
>
<ToSvelte
construct={() => new ExtraLinkButton(state, layout.extraLink).SetClass("pointer-events-auto")}
construct={() => currentViewLayer.defaultIcon().SetClass("w-8 h-8 cursor-pointer")}
/>
<If condition={state.featureSwitchIsTesting}>
<div class="alert w-fit">Testmode</div>
</If>
</div>
<div class="flex justify-center w-full"> <!-- Flex and w-full are needed for the positioning -->
<!-- Centermessage -->
<StateIndicator {state}/>
</div>
</MapControlButton>
{/if}
<ToSvelte
construct={() => new ExtraLinkButton(state, layout.extraLink).SetClass("pointer-events-auto")}
/>
<If condition={state.featureSwitchIsTesting}>
<div class="alert w-fit">Testmode</div>
</If>
</div>
<div class="flex w-full justify-center">
<!-- Flex and w-full are needed for the positioning -->
<!-- Centermessage -->
<StateIndicator {state} />
</div>
</div>
<div class="pointer-events-none absolute bottom-0 left-0 mb-4 w-screen">
<!-- bottom controls -->
<div class="flex w-full items-end justify-between px-4">
<div class="flex">
<!-- bottom left elements -->
<OpenBackgroundSelectorButton hideTooltip={true} {state}/>
<a
class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100"
on:click={() => {
<!-- bottom controls -->
<div class="flex w-full items-end justify-between px-4">
<div class="flex">
<!-- bottom left elements -->
<OpenBackgroundSelectorButton hideTooltip={true} {state} />
<a
class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100"
on:click={() => {
state.guistate.themeViewTab.setData("copyright")
state.guistate.themeIsOpened.setData(true)
}}
>
© OpenStreetMap, <span class="w-24">{rasterLayerName}</span>
</a>
</div>
>
© OpenStreetMap, <span class="w-24">{rasterLayerName}</span>
</a>
</div>
<div class="flex flex-col items-end">
<!-- bottom right elements -->
<If condition={state.floors.map((f) => f.length > 1)}>
<div class="pointer-events-auto mr-0.5">
<LevelSelector
floors={state.floors}
layerState={state.layerState}
zoom={state.mapProperties.zoom}
/>
</div>
</If>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)}>
<ToSvelte construct={Svg.plus_svg().SetClass("w-8 h-8")}/>
</MapControlButton>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)}>
<ToSvelte construct={Svg.min_svg().SetClass("w-8 h-8")}/>
</MapControlButton>
<If condition={featureSwitches.featureSwitchGeolocation}>
<MapControlButton>
<ToSvelte
construct={new GeolocationControl(state.geolocation, mapproperties).SetClass(
<div class="flex flex-col items-end">
<!-- bottom right elements -->
<If condition={state.floors.map((f) => f.length > 1)}>
<div class="pointer-events-auto mr-0.5">
<LevelSelector
floors={state.floors}
layerState={state.layerState}
zoom={state.mapProperties.zoom}
/>
</div>
</If>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)}>
<ToSvelte construct={Svg.plus_svg().SetClass("w-8 h-8")} />
</MapControlButton>
<MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)}>
<ToSvelte construct={Svg.min_svg().SetClass("w-8 h-8")} />
</MapControlButton>
<If condition={featureSwitches.featureSwitchGeolocation}>
<MapControlButton>
<ToSvelte
construct={new GeolocationControl(state.geolocation, mapproperties).SetClass(
"block w-8 h-8"
)}
/>
</MapControlButton>
</If>
</div>
/>
</MapControlButton>
</If>
</div>
</div>
</div>
<If
condition={selectedElementView.map(
condition={selectedElementView.map(
(v) =>
v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover,
[selectedLayer]
)}
>
<!-- right modal with the selected element view -->
<ModalRight
on:close={() => {
<!-- right modal with the selected element view -->
<ModalRight
on:close={() => {
selectedElement.setData(undefined)
}}
>
<div class="normal-background absolute flex h-full w-full flex-col">
<ToSvelte construct={new VariableUiElement(selectedElementTitle)}>
<!-- Title -->
</ToSvelte>
<ToSvelte construct={new VariableUiElement(selectedElementView).SetClass("overflow-auto")}>
<!-- Main view -->
</ToSvelte>
</div>
</ModalRight>
>
<div class="normal-background absolute flex h-full w-full flex-col">
<ToSvelte construct={new VariableUiElement(selectedElementTitle)}>
<!-- Title -->
</ToSvelte>
<ToSvelte construct={new VariableUiElement(selectedElementView).SetClass("overflow-auto")}>
<!-- Main view -->
</ToSvelte>
</div>
</ModalRight>
</If>
<If
condition={selectedElementView.map(
condition={selectedElementView.map(
(v) =>
v !== undefined && selectedLayer.data !== undefined && selectedLayer.data.popupInFloatover,
[selectedLayer]
)}
>
<!-- Floatover with the selected element, if applicable -->
<FloatOver
on:close={() => {
<!-- Floatover with the selected element, if applicable -->
<FloatOver
on:close={() => {
selectedElement.setData(undefined)
}}
>
<ToSvelte construct={new VariableUiElement(selectedElementView)}/>
</FloatOver>
>
<ToSvelte construct={new VariableUiElement(selectedElementView)} />
</FloatOver>
</If>
<If condition={state.guistate.themeIsOpened}>
<!-- Theme menu -->
<FloatOver>
<span slot="close-button"><!-- Disable the close button --></span>
<TabbedGroup tab={state.guistate.themeViewTabIndex}>
<div slot="post-tablist">
<XCircleIcon
class="mr-2 h-8 w-8"
on:click={() => state.guistate.themeIsOpened.setData(false)}
/>
</div>
<!-- Theme menu -->
<FloatOver>
<span slot="close-button"><!-- Disable the close button --></span>
<TabbedGroup tab={state.guistate.themeViewTabIndex}>
<div slot="post-tablist">
<XCircleIcon
class="mr-2 h-8 w-8"
on:click={() => state.guistate.themeIsOpened.setData(false)}
/>
</div>
<div class="flex" slot="title0">
<img class="block h-4 w-4" src={layout.icon}/>
<Tr t={layout.title}/>
</div>
<div class="flex" slot="title0">
<img class="block h-4 w-4" src={layout.icon} />
<Tr t={layout.title} />
</div>
<div class="m-4 h-full" slot="content0">
<ThemeIntroPanel {state}/>
</div>
<div class="m-4 h-full" slot="content0">
<ThemeIntroPanel {state} />
</div>
<div class="flex" slot="title1">
<If condition={state.featureSwitches.featureSwitchFilter}>
<ToSvelte construct={Svg.filter_svg().SetClass("w-4 h-4")}/>
<Tr t={Translations.t.general.menu.filter}/>
</If>
</div>
<div class="flex" slot="title1">
<If condition={state.featureSwitches.featureSwitchFilter}>
<ToSvelte construct={Svg.filter_svg().SetClass("w-4 h-4")} />
<Tr t={Translations.t.general.menu.filter} />
</If>
</div>
<div class="m-2 flex flex-col" slot="content1">
{#each layout.layers as layer}
<Filterview
zoomlevel={state.mapProperties.zoom}
filteredLayer={state.layerState.filteredLayers.get(layer.id)}
highlightedLayer={state.guistate.highlightedLayerInFilters}
/>
{/each}
{#each layout.tileLayerSources as tilesource}
<OverlayToggle
layerproperties={tilesource}
state={state.overlayLayerStates.get(tilesource.id)}
highlightedLayer={state.guistate.highlightedLayerInFilters}
zoomlevel={state.mapProperties.zoom}
/>
{/each}
</div>
<div class="flex" slot="title2">
<If condition={state.featureSwitches.featureSwitchEnableExport}>
<ToSvelte construct={Svg.download_svg().SetClass("w-4 h-4")}/>
<Tr t={Translations.t.general.download.title}/>
</If>
</div>
<div class="m-4" slot="content2">
<DownloadPanel {state}/>
</div>
<div class="m-2 flex flex-col" slot="content1">
{#each layout.layers as layer}
<Filterview
zoomlevel={state.mapProperties.zoom}
filteredLayer={state.layerState.filteredLayers.get(layer.id)}
highlightedLayer={state.guistate.highlightedLayerInFilters}
/>
{/each}
{#each layout.tileLayerSources as tilesource}
<OverlayToggle
layerproperties={tilesource}
state={state.overlayLayerStates.get(tilesource.id)}
highlightedLayer={state.guistate.highlightedLayerInFilters}
zoomlevel={state.mapProperties.zoom}
/>
{/each}
</div>
<div class="flex" slot="title2">
<If condition={state.featureSwitches.featureSwitchEnableExport}>
<ToSvelte construct={Svg.download_svg().SetClass("w-4 h-4")} />
<Tr t={Translations.t.general.download.title} />
</If>
</div>
<div class="m-4" slot="content2">
<DownloadPanel {state} />
</div>
<div slot="title3">
<Tr t={Translations.t.general.attribution.title}/>
</div>
<div slot="title3">
<Tr t={Translations.t.general.attribution.title} />
</div>
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3"/>
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" />
<div slot="title4">
<Tr t={Translations.t.general.sharescreen.title}/>
</div>
<div class="m-2" slot="content4">
<ToSvelte construct={() => new ShareScreen(state)}/>
</div>
</TabbedGroup>
</FloatOver>
<div slot="title4">
<Tr t={Translations.t.general.sharescreen.title} />
</div>
<div class="m-2" slot="content4">
<ToSvelte construct={() => new ShareScreen(state)} />
</div>
</TabbedGroup>
</FloatOver>
</If>
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
<!-- background layer selector -->
<FloatOver on:close={() => state.guistate.backgroundLayerSelectionIsOpened.setData(false)}>
<div class="h-full p-2">
<RasterLayerOverview
{availableLayers}
map={state.map}
mapproperties={state.mapProperties}
userstate={state.userRelatedState}
visible={state.guistate.backgroundLayerSelectionIsOpened}
/>
</div>
</FloatOver>
<!-- background layer selector -->
<FloatOver on:close={() => state.guistate.backgroundLayerSelectionIsOpened.setData(false)}>
<div class="h-full p-2">
<RasterLayerOverview
{availableLayers}
map={state.map}
mapproperties={state.mapProperties}
userstate={state.userRelatedState}
visible={state.guistate.backgroundLayerSelectionIsOpened}
/>
</div>
</FloatOver>
</IfHidden>
<If condition={state.guistate.menuIsOpened}>
<!-- Menu page -->
<FloatOver>
<span slot="close-button"><!-- Hide the default close button --></span>
<TabbedGroup tab={state.guistate.menuViewTabIndex}>
<div slot="post-tablist">
<XCircleIcon
class="mr-2 h-8 w-8"
on:click={() => state.guistate.menuIsOpened.setData(false)}
/>
</div>
<div class="flex" slot="title0">
<Tr t={Translations.t.general.menu.aboutMapComplete}/>
</div>
<!-- Menu page -->
<FloatOver>
<span slot="close-button"><!-- Hide the default close button --></span>
<TabbedGroup tab={state.guistate.menuViewTabIndex}>
<div slot="post-tablist">
<XCircleIcon
class="mr-2 h-8 w-8"
on:click={() => state.guistate.menuIsOpened.setData(false)}
/>
</div>
<div class="flex" slot="title0">
<Tr t={Translations.t.general.menu.aboutMapComplete} />
</div>
<div class="links-as-button links-w-full m-2 flex flex-col gap-y-1" slot="content0">
<Tr t={Translations.t.general.aboutMapComplete.intro}/>
<div class="links-as-button links-w-full m-2 flex flex-col gap-y-1" slot="content0">
<Tr t={Translations.t.general.aboutMapComplete.intro} />
<a class="flex" href={Utils.HomepageLink()}>
<img class="h-6 w-6" src="./assets/svg/add.svg"/>
<Tr t={Translations.t.general.backToIndex}/>
</a>
<a class="flex" href={Utils.HomepageLink()}>
<img class="h-6 w-6" src="./assets/svg/add.svg" />
<Tr t={Translations.t.general.backToIndex} />
</a>
<a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank">
<img class="h-6 w-6" src="./assets/svg/bug.svg"/>
<Tr t={Translations.t.general.attribution.openIssueTracker}/>
</a>
<a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank">
<img class="h-6 w-6" src="./assets/svg/bug.svg" />
<Tr t={Translations.t.general.attribution.openIssueTracker} />
</a>
<a class="flex" href="https://en.osm.town/@MapComplete" target="_blank">
<img class="h-6 w-6" src="./assets/svg/mastodon.svg"/>
<Tr t={Translations.t.general.attribution.followOnMastodon}/>
</a>
<a class="flex" href="https://en.osm.town/@MapComplete" target="_blank">
<img class="h-6 w-6" src="./assets/svg/mastodon.svg" />
<Tr t={Translations.t.general.attribution.followOnMastodon} />
</a>
<a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank">
<img class="h-6 w-6" src="./assets/svg/liberapay.svg"/>
<Tr t={Translations.t.general.attribution.donate}/>
</a>
<a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank">
<img class="h-6 w-6" src="./assets/svg/liberapay.svg" />
<Tr t={Translations.t.general.attribution.donate} />
</a>
<a class="flex" href={Utils.OsmChaLinkFor(7)} target="_blank">
<img class="h-6 w-6" src="./assets/svg/statistics.svg"/>
<Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: "MapComplete" })}/>
</a>
{Constants.vNumber}
</div>
<a class="flex" href={Utils.OsmChaLinkFor(7)} target="_blank">
<img class="h-6 w-6" src="./assets/svg/statistics.svg" />
<Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: "MapComplete" })} />
</a>
{Constants.vNumber}
</div>
<div class="flex" slot="title1">
<CogIcon class="h-6 w-6"/>
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})}/>
</div>
<div class="flex" slot="title1">
<CogIcon class="h-6 w-6" />
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})} />
</div>
<div class="links-as-button" slot="content1">
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
<LoginToggle {state}>
<div class="flex flex-col" slot="not-logged-in">
<Tr class="alert" t={Translations.t.userinfo.notLoggedIn}/>
<LoginButton clss="primary" osmConnection={state.osmConnection}/>
</div>
<SelectedElementView
highlightedRendering={state.guistate.highlightedUserSetting}
layer={UserRelatedState.usersettingsConfig}
selectedElement={{
<div class="links-as-button" slot="content1">
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
<LoginToggle {state}>
<div class="flex flex-col" slot="not-logged-in">
<Tr class="alert" t={Translations.t.userinfo.notLoggedIn} />
<LoginButton clss="primary" osmConnection={state.osmConnection} />
</div>
<SelectedElementView
highlightedRendering={state.guistate.highlightedUserSetting}
layer={UserRelatedState.usersettingsConfig}
selectedElement={{
type: "Feature",
properties: {},
geometry: { type: "Point", coordinates: [0, 0] },
}}
{state}
tags={state.userRelatedState.preferencesAsTags}
/>
</LoginToggle>
</div>
{state}
tags={state.userRelatedState.preferencesAsTags}
/>
</LoginToggle>
</div>
<div class="flex" slot="title2">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/>
Get in touch with others
</div>
<div class="m-2" slot="content2">
<CommunityIndexView location={state.mapProperties.location}/>
</div>
<div class="flex" slot="title2">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />
Get in touch with others
</div>
<div class="m-2" slot="content2">
<CommunityIndexView location={state.mapProperties.location} />
</div>
<div class="flex" slot="title3">
<EyeIcon class="w-6"/>
<Tr t={Translations.t.privacy.title}/>
</div>
<div class="m-2" slot="content3">
<ToSvelte construct={() => new PrivacyPolicy()}/>
</div>
<div class="flex" slot="title3">
<EyeIcon class="w-6" />
<Tr t={Translations.t.privacy.title} />
</div>
<div class="m-2" slot="content3">
<ToSvelte construct={() => new PrivacyPolicy()} />
</div>
<Tr slot="title4" t={Translations.t.advanced.title}/>
<div class="m-2 flex flex-col" slot="content4">
<OpenIdEditor mapProperties={state.mapProperties}/>
<ToSvelte
construct={() =>
<Tr slot="title4" t={Translations.t.advanced.title} />
<div class="m-2 flex flex-col" slot="content4">
<OpenIdEditor mapProperties={state.mapProperties} />
<ToSvelte
construct={() =>
new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")}
/>
<MapillaryLink mapProperties={state.mapProperties}/>
<ToSvelte construct={Hotkeys.generateDocumentationDynamic}/>
</div>
</TabbedGroup>
</FloatOver>
/>
<MapillaryLink mapProperties={state.mapProperties} />
<ToSvelte construct={Hotkeys.generateDocumentationDynamic} />
</div>
</TabbedGroup>
</FloatOver>
</If>