forked from MapComplete/MapComplete
213 lines
7.9 KiB
Svelte
213 lines
7.9 KiB
Svelte
<script lang="ts">
|
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
|
import type { MoveReason } from "./MoveWizardState"
|
|
import { MoveWizardState } from "./MoveWizardState"
|
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
|
import Tr from "../Base/Tr.svelte"
|
|
import Translations from "../i18n/Translations"
|
|
import Move from "../../assets/svg/Move.svelte"
|
|
import Move_not_allowed from "../../assets/svg/Move_not_allowed.svelte"
|
|
import type { MapProperties } from "../../Models/MapProperties"
|
|
import type { Feature, Point } from "geojson"
|
|
import { GeoOperations } from "../../Logic/GeoOperations"
|
|
import OpenBackgroundSelectorButton from "../BigComponents/OpenBackgroundSelectorButton.svelte"
|
|
import If from "../Base/If.svelte"
|
|
import Constants from "../../Models/Constants"
|
|
import LoginToggle from "../Base/LoginToggle.svelte"
|
|
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
|
|
|
|
|
import ChevronLeft from "@babeard/svelte-heroicons/solid/ChevronLeft"
|
|
import ThemeViewState from "../../Models/ThemeViewState"
|
|
import Icon from "../Map/Icon.svelte"
|
|
import NewPointLocationInput from "../BigComponents/NewPointLocationInput.svelte"
|
|
import type { WayId } from "../../Models/OsmFeature"
|
|
import Searchbar from "../Base/Searchbar.svelte"
|
|
import { NominatimGeocoding } from "../../Logic/Search/NominatimGeocoding"
|
|
import Loading from "../Base/Loading.svelte"
|
|
import { Map as MlMap } from "maplibre-gl"
|
|
import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider"
|
|
|
|
export let state: ThemeViewState
|
|
|
|
export let layer: LayerConfig
|
|
export let featureToMove: Feature<Point>
|
|
|
|
let id: string = featureToMove.properties.id
|
|
let currentStep: "reason" | "pick_location" | "moved" = "reason"
|
|
const t = Translations.t.move
|
|
let reason = new UIEventSource<MoveReason>(undefined)
|
|
|
|
let [lon, lat] = GeoOperations.centerpointCoordinates(featureToMove)
|
|
|
|
let newLocation = new UIEventSource<{ lon: number; lat: number }>(undefined)
|
|
|
|
let snappedTo = new UIEventSource<WayId | undefined>(undefined)
|
|
|
|
function initMapProperties(reason: MoveReason): Partial<MapProperties> & { location } {
|
|
return {
|
|
allowMoving: new UIEventSource(true),
|
|
allowRotating: new UIEventSource(false),
|
|
allowZooming: new UIEventSource(true),
|
|
bounds: new UIEventSource(undefined),
|
|
location: new UIEventSource({ lon, lat }),
|
|
minzoom: new UIEventSource(reason.minZoom),
|
|
rasterLayer: state.mapProperties.rasterLayer,
|
|
zoom: new UIEventSource(reason?.startZoom ?? 16),
|
|
}
|
|
}
|
|
|
|
let moveWizardState = new MoveWizardState(id, layer.allowMove, layer, state)
|
|
if (moveWizardState.reasons.length === 1) {
|
|
reason.setData(moveWizardState.reasons[0])
|
|
}
|
|
let notAllowed = moveWizardState.moveDisallowedReason
|
|
let currentMapProperties: Store<Partial<MapProperties> & { location }> = reason.mapD(r => initMapProperties(r))
|
|
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
|
|
|
|
|
let searchValue = new UIEventSource<string>("")
|
|
let isSearching = new UIEventSource<boolean>(false)
|
|
const searcher = new NominatimGeocoding(1)
|
|
|
|
|
|
async function searchPressed() {
|
|
const v = searchValue.data
|
|
console.log("Search is pressed", v)
|
|
isSearching.set(true)
|
|
const result: GeocodeResult[] = await searcher.search(v)
|
|
isSearching.set(false)
|
|
if (result.length == 0 || currentMapProperties.data === undefined) {
|
|
return
|
|
}
|
|
|
|
const loc = result[0]
|
|
console.log("Found", result, "flying")
|
|
map.data.flyTo({ zoom: 18, center: [loc.lon, loc.lat] })
|
|
}
|
|
|
|
</script>
|
|
|
|
{#if moveWizardState.reasons.length > 0}
|
|
<LoginToggle {state}>
|
|
{#if $notAllowed}
|
|
<div class="m-2 flex rounded-lg bg-gray-200 p-2">
|
|
<Move_not_allowed class="m-2 h-8 w-8" />
|
|
<div class="flex flex-col">
|
|
<Tr t={t.cannotBeMoved} />
|
|
<Tr t={$notAllowed} />
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<AccordionSingle>
|
|
<span slot="header" class="flex">
|
|
{#if moveWizardState.reasons.length === 1}
|
|
<Icon icon={moveWizardState.reasons[0].icon} clss="w-6 h-6" />
|
|
<Tr t={Translations.T(moveWizardState.reasons[0].invitingText)} />
|
|
{:else}
|
|
<Move class="h-6 w-6" />
|
|
<Tr t={t.inviteToMove.generic} />
|
|
{/if}
|
|
</span>
|
|
<span class="flex flex-col p-2">
|
|
{#if currentStep === "reason" && moveWizardState.reasons.length > 1}
|
|
{#each moveWizardState.reasons as reasonSpec}
|
|
<button
|
|
class="flex justify-start"
|
|
on:click={() => {
|
|
reason.setData(reasonSpec)
|
|
currentStep = "pick_location"
|
|
}}
|
|
>
|
|
<Icon icon={reasonSpec.icon} clss="w-12 h-12" />
|
|
<Tr t={Translations.T(reasonSpec.text)} />
|
|
</button>
|
|
{/each}
|
|
{:else if currentStep === "pick_location" || currentStep === "reason"}
|
|
{#if $reason.includeSearch}
|
|
<Searchbar value={searchValue} on:search={() => searchPressed()}>
|
|
<svelte:fragment slot="button-right">
|
|
{#if $isSearching}
|
|
<Loading />
|
|
{:else}
|
|
<button class="primary" class:disabled={$searchValue.length === 0} on:click={() => searchPressed()}>
|
|
<Tr t={Translations.t.general.search.searchShort} />
|
|
</button>
|
|
{/if}
|
|
</svelte:fragment>
|
|
</Searchbar>
|
|
{/if}
|
|
<div class="relative h-64 w-full">
|
|
<NewPointLocationInput
|
|
{map}
|
|
mapProperties={$currentMapProperties}
|
|
value={newLocation}
|
|
{state}
|
|
coordinate={{ lon, lat }}
|
|
{snappedTo}
|
|
maxSnapDistance={$reason.maxSnapDistance ?? 5}
|
|
snapToLayers={$reason.snapTo}
|
|
targetLayer={layer}
|
|
dontShow={[id]}
|
|
maxDistanceInMeters={$reason.maxSnapDistance ?? Infinity}
|
|
/>
|
|
<div class="absolute bottom-0 left-0">
|
|
<OpenBackgroundSelectorButton {state} />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-wrap">
|
|
<If
|
|
condition={$currentMapProperties.zoom.mapD(
|
|
(zoom) => zoom >= Constants.minZoomLevelToAddNewPoint
|
|
)}
|
|
>
|
|
<button
|
|
class="primary w-full"
|
|
on:click={() => {
|
|
moveWizardState.moveFeature(
|
|
newLocation.data,
|
|
snappedTo.data,
|
|
reason.data,
|
|
featureToMove
|
|
)
|
|
currentStep = "moved"
|
|
}}
|
|
>
|
|
<Tr t={t.confirmMove} />
|
|
</button>
|
|
|
|
<div slot="else" class="alert w-full">
|
|
<Tr t={t.zoomInFurther} />
|
|
</div>
|
|
</If>
|
|
{#if moveWizardState.reasons.length > 1}
|
|
<button
|
|
class="w-full"
|
|
on:click={() => {
|
|
currentStep = "reason"
|
|
}}
|
|
>
|
|
<ChevronLeft class="h-6 w-6" />
|
|
<Tr t={t.cancel} />
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
{:else if currentStep === "moved"}
|
|
<div class="flex flex-col">
|
|
<Tr cls="thanks" t={t.pointIsMoved} />
|
|
<button
|
|
on:click={() => {
|
|
currentStep = "reason"
|
|
}}
|
|
>
|
|
<Move class="h-6 w-6 pr-2" />
|
|
<Tr t={t.inviteToMoveAgain} />
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
</span>
|
|
</AccordionSingle>
|
|
{/if}
|
|
</LoginToggle>
|
|
{/if}
|