diff --git a/src/UI/Popup/MoveWizard.svelte b/src/UI/Popup/MoveWizard.svelte
new file mode 100644
index 000000000..e6bebf286
--- /dev/null
+++ b/src/UI/Popup/MoveWizard.svelte
@@ -0,0 +1,144 @@
+
+{#if moveWizardState.reasons.length > 0}
+
+ {#if $notAllowed}
+
+ {:else if currentStep === "start"}
+ {#if moveWizardState.reasons.length === 1}
+
+ {:else}
+
+ {/if}
+ {:else if currentStep === "reason"}
+
+
+
+ {#each moveWizardState.reasons as reasonSpec}
+
+ {/each}
+
+
+ {:else if currentStep === "pick_location"}
+
+
|
+
+
+
+
+ {#if $reason.includeSearch}
+
+ {/if}
+
+
+
+
zoom >= Constants.minZoomLevelToAddNewPoint)}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {:else if currentStep === "moved"}
+
+
+
+
+
+
+ {/if}
+{/if}
diff --git a/src/UI/Popup/MoveWizard.ts b/src/UI/Popup/MoveWizard.ts
deleted file mode 100644
index 6d1de64eb..000000000
--- a/src/UI/Popup/MoveWizard.ts
+++ /dev/null
@@ -1,281 +0,0 @@
-import { SubtleButton } from "../Base/SubtleButton"
-import Combine from "../Base/Combine"
-import Svg from "../../Svg"
-import Toggle from "../Input/Toggle"
-import { UIEventSource } from "../../Logic/UIEventSource"
-import Translations from "../i18n/Translations"
-import { VariableUiElement } from "../Base/VariableUIElement"
-import { Translation } from "../i18n/Translation"
-import BaseUIElement from "../BaseUIElement"
-import { GeoOperations } from "../../Logic/GeoOperations"
-import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction"
-import MoveConfig from "../../Models/ThemeConfig/MoveConfig"
-import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
-import { And } from "../../Logic/Tags/And"
-import { Tag } from "../../Logic/Tags/Tag"
-import { LoginToggle } from "./LoginButton"
-import { SpecialVisualizationState } from "../SpecialVisualization"
-import { Feature, Point } from "geojson"
-import { OsmTags } from "../../Models/OsmFeature"
-import SvelteUIElement from "../Base/SvelteUIElement"
-import { MapProperties } from "../../Models/MapProperties"
-import LocationInput from "../InputElement/Helpers/LocationInput.svelte"
-import Geosearch from "../BigComponents/Geosearch.svelte"
-import Constants from "../../Models/Constants"
-import OpenBackgroundSelectorButton from "../BigComponents/OpenBackgroundSelectorButton.svelte"
-
-interface MoveReason {
- text: Translation | string
- invitingText: Translation | string
- icon: BaseUIElement
- changesetCommentValue: string
- lockBounds: true | boolean
- includeSearch: false | boolean
- background: undefined | "map" | "photo" | string | string[]
- startZoom: number
- minZoom: number
- eraseAddressFields: false | boolean
-}
-
-export default class MoveWizard extends Toggle {
- /**
- * The UI-element which helps moving a point
- */
- constructor(
- featureToMove: Feature,
- tags: UIEventSource,
- state: SpecialVisualizationState,
- options: MoveConfig
- ) {
- const t = Translations.t.move
-
- const reasons: MoveReason[] = []
- if (options.enableRelocation) {
- reasons.push({
- text: t.reasons.reasonRelocation,
- invitingText: t.inviteToMove.reasonRelocation,
- icon: Svg.relocation_svg(),
- changesetCommentValue: "relocated",
- lockBounds: false,
- background: undefined,
- includeSearch: true,
- startZoom: 12,
- minZoom: 6,
- eraseAddressFields: true,
- })
- }
- if (options.enableImproveAccuracy) {
- reasons.push({
- text: t.reasons.reasonInaccurate,
- invitingText: t.inviteToMove.reasonInaccurate,
- icon: Svg.crosshair_svg(),
- changesetCommentValue: "improve_accuracy",
- lockBounds: true,
- includeSearch: false,
- background: "photo",
- startZoom: 18,
- minZoom: 16,
- eraseAddressFields: false,
- })
- }
-
- const currentStep = new UIEventSource<"start" | "reason" | "pick_location" | "moved">(
- "start"
- )
- const moveReason = new UIEventSource(undefined)
- let moveButton: BaseUIElement
- if (reasons.length === 1) {
- const reason = reasons[0]
- moveReason.setData(reason)
- moveButton = new SubtleButton(
- reason.icon.SetStyle("height: 1.5rem; width: 1.5rem;"),
- Translations.T(reason.invitingText)
- ).onClick(() => {
- currentStep.setData("pick_location")
- })
- } else {
- moveButton = new SubtleButton(
- Svg.move_svg().SetStyle("width: 1.5rem; height: 1.5rem"),
- t.inviteToMove.generic
- ).onClick(() => {
- currentStep.setData("reason")
- })
- }
-
- const moveAgainButton = new SubtleButton(Svg.move_svg(), t.inviteToMoveAgain).onClick(
- () => {
- currentStep.setData("reason")
- }
- )
-
- const selectReason = new Combine(
- reasons.map((r) =>
- new SubtleButton(r.icon, r.text).onClick(() => {
- moveReason.setData(r)
- currentStep.setData("pick_location")
- })
- )
- )
-
- const cancelButton = new SubtleButton(Svg.close_svg(), t.cancel).onClick(() =>
- currentStep.setData("start")
- )
-
- const [lon, lat] = GeoOperations.centerpointCoordinates(featureToMove)
- const locationInput = moveReason.map((reason) => {
- if (reason === undefined) {
- return undefined
- }
-
- const mapProperties: Partial = {
- minzoom: new UIEventSource(reason.minZoom),
- zoom: new UIEventSource(reason?.startZoom ?? 16),
- location: new UIEventSource({ lon, lat }),
- bounds: new UIEventSource(undefined),
- rasterLayer: state.mapProperties.rasterLayer,
- }
- const value = new UIEventSource<{ lon: number; lat: number }>(undefined)
- const locationInput = new Combine([
- new SvelteUIElement(LocationInput, {
- mapProperties,
- value,
- initialCoordinate: { lon, lat },
- }).SetClass("w-full h-full"),
- new SvelteUIElement(OpenBackgroundSelectorButton, { state }).SetClass(
- "absolute bottom-0 left-0"
- ),
- ]).SetClass("relative w-full h-full")
-
- let searchPanel: BaseUIElement = undefined
- if (reason.includeSearch) {
- searchPanel = new SvelteUIElement(Geosearch, {
- bounds: mapProperties.bounds,
- clearAfterView: false,
- })
- }
-
- locationInput.SetStyle("height: 17.5rem")
-
- const confirmMove = new SubtleButton(Svg.move_confirm_svg(), t.confirmMove)
- confirmMove.onClick(async () => {
- const loc = value.data
- await state.changes.applyAction(
- new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], {
- reason: reason.changesetCommentValue,
- theme: state.layout.id,
- })
- )
- featureToMove.properties._lat = loc.lat
- featureToMove.properties._lon = loc.lon
- featureToMove.geometry.coordinates = [loc.lon, loc.lat]
- if (reason.eraseAddressFields) {
- await state.changes.applyAction(
- new ChangeTagAction(
- featureToMove.properties.id,
- new And([
- new Tag("addr:housenumber", ""),
- new Tag("addr:street", ""),
- new Tag("addr:city", ""),
- new Tag("addr:postcode", ""),
- ]),
- featureToMove.properties,
- {
- changeType: "relocated",
- theme: state.layout.id,
- }
- )
- )
- }
-
- state.featureProperties.getStore(id).ping()
- currentStep.setData("moved")
- state.mapProperties.location.setData(loc)
- })
- const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6")
- return new Combine([
- searchPanel,
- locationInput,
- new Toggle(
- confirmMove,
- zoomInFurhter,
- mapProperties.zoom.map((zoom) => zoom >= Constants.minZoomLevelToAddNewPoint)
- ),
- ]).SetClass("flex flex-col")
- })
-
- const dialogClasses = "p-2 md:p-4 m-2 border border-gray-400 rounded-xl flex flex-col"
-
- const moveFlow = new LoginToggle(
- new VariableUiElement(
- currentStep.map((currentStep) => {
- switch (currentStep) {
- case "start":
- return moveButton
- case "reason":
- return new Combine([
- t.whyMove.SetClass("text-lg font-bold"),
- selectReason,
- cancelButton,
- ]).SetClass(dialogClasses)
- case "pick_location":
- return new Combine([
- t.moveTitle.SetClass("text-lg font-bold"),
- new VariableUiElement(locationInput),
- cancelButton,
- ]).SetClass(dialogClasses)
- case "moved":
- return new Combine([
- t.pointIsMoved.SetClass("thanks"),
- moveAgainButton,
- ]).SetClass("flex flex-col")
- }
- })
- ),
- undefined,
- state
- )
- let id = featureToMove.properties.id
- const backend = state.osmConnection._oauth_config.url
- if (id.startsWith(backend)) {
- id = id.substring(backend.length)
- }
-
- const moveDisallowedReason = new UIEventSource(undefined)
- if (id.startsWith("way")) {
- moveDisallowedReason.setData(t.isWay)
- } else if (id.startsWith("relation")) {
- moveDisallowedReason.setData(t.isRelation)
- } else if (id.indexOf("-") < 0) {
- state.osmObjectDownloader.DownloadReferencingWays(id).then((referencing) => {
- if (referencing.length > 0) {
- console.log("Got a referencing way, move not allowed")
- moveDisallowedReason.setData(t.partOfAWay)
- }
- })
- state.osmObjectDownloader.DownloadReferencingRelations(id).then((partOf) => {
- if (partOf.length > 0) {
- moveDisallowedReason.setData(t.partOfRelation)
- }
- })
- }
- super(
- moveFlow,
- new Combine([
- Svg.move_not_allowed_svg().SetStyle("height: 2rem").SetClass("m-2"),
- new Combine([
- t.cannotBeMoved,
- new VariableUiElement(moveDisallowedReason).SetClass("subtle"),
- ]).SetClass("flex flex-col"),
- ]).SetClass("flex m-2 p-2 rounded-lg bg-gray-200"),
- moveDisallowedReason.map((r) => r === undefined)
- )
-
- const self = this
- currentStep.addCallback((state) => {
- if (state === "start") {
- return
- }
- self.ScrollIntoView()
- })
- }
-}
diff --git a/src/UI/Popup/MoveWizardState.ts b/src/UI/Popup/MoveWizardState.ts
new file mode 100644
index 000000000..5ffdf689b
--- /dev/null
+++ b/src/UI/Popup/MoveWizardState.ts
@@ -0,0 +1,136 @@
+import Svg from "../../Svg"
+import { UIEventSource } from "../../Logic/UIEventSource"
+import Translations from "../i18n/Translations"
+import { Translation } from "../i18n/Translation"
+import BaseUIElement from "../BaseUIElement"
+import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction"
+import MoveConfig from "../../Models/ThemeConfig/MoveConfig"
+import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
+import { And } from "../../Logic/Tags/And"
+import { Tag } from "../../Logic/Tags/Tag"
+import { SpecialVisualizationState } from "../SpecialVisualization"
+import { Feature, Point } from "geojson"
+
+export interface MoveReason {
+ text: Translation | string
+ invitingText: Translation | string
+ icon: BaseUIElement
+ changesetCommentValue: string
+ lockBounds: true | boolean
+ includeSearch: false | boolean
+ background: undefined | "map" | "photo" | string | string[]
+ startZoom: number
+ minZoom: number
+ eraseAddressFields: false | boolean
+}
+
+export class MoveWizardState {
+ public readonly reasons: ReadonlyArray
+
+ public readonly moveDisallowedReason = new UIEventSource(undefined)
+ private readonly _state: SpecialVisualizationState
+
+ constructor(id: string, options: MoveConfig, state: SpecialVisualizationState) {
+ this._state = state
+ const t = Translations.t.move
+
+ this.reasons = MoveWizardState.initReasons(options)
+
+ if (this.reasons.length > 0) {
+ this.checkIsAllowed(id)
+ }
+ }
+
+ private static initReasons(options: MoveConfig): MoveReason[] {
+ const t = Translations.t.move
+
+ const reasons: MoveReason[] = []
+ if (options.enableRelocation) {
+ reasons.push({
+ text: t.reasons.reasonRelocation,
+ invitingText: t.inviteToMove.reasonRelocation,
+ icon: Svg.relocation_svg(),
+ changesetCommentValue: "relocated",
+ lockBounds: false,
+ background: undefined,
+ includeSearch: true,
+ startZoom: 12,
+ minZoom: 6,
+ eraseAddressFields: true,
+ })
+ }
+ if (options.enableImproveAccuracy) {
+ reasons.push({
+ text: t.reasons.reasonInaccurate,
+ invitingText: t.inviteToMove.reasonInaccurate,
+ icon: Svg.crosshair_svg(),
+ changesetCommentValue: "improve_accuracy",
+ lockBounds: true,
+ includeSearch: false,
+ background: "photo",
+ startZoom: 18,
+ minZoom: 16,
+ eraseAddressFields: false,
+ })
+ }
+ return reasons
+ }
+
+ public async moveFeature(
+ loc: { lon: number; lat: number },
+ reason: MoveReason,
+ featureToMove: Feature
+ ) {
+ const state = this._state
+ await state.changes.applyAction(
+ new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], {
+ reason: reason.changesetCommentValue,
+ theme: state.layout.id,
+ })
+ )
+ featureToMove.properties._lat = loc.lat
+ featureToMove.properties._lon = loc.lon
+ featureToMove.geometry.coordinates = [loc.lon, loc.lat]
+ if (reason.eraseAddressFields) {
+ await state.changes.applyAction(
+ new ChangeTagAction(
+ featureToMove.properties.id,
+ new And([
+ new Tag("addr:housenumber", ""),
+ new Tag("addr:street", ""),
+ new Tag("addr:city", ""),
+ new Tag("addr:postcode", ""),
+ ]),
+ featureToMove.properties,
+ {
+ changeType: "relocated",
+ theme: state.layout.id,
+ }
+ )
+ )
+ }
+
+ state.featureProperties.getStore(featureToMove.properties.id)?.ping()
+ state.mapProperties.location.setData(loc)
+ }
+
+ private checkIsAllowed(id: string) {
+ const t = Translations.t.move
+ if (id.startsWith("way")) {
+ this.moveDisallowedReason.setData(t.isWay)
+ } else if (id.startsWith("relation")) {
+ this.moveDisallowedReason.setData(t.isRelation)
+ } else if (id.indexOf("-") < 0) {
+ this._state.osmObjectDownloader.DownloadReferencingWays(id).then((referencing) => {
+ if (referencing.length > 0) {
+ this.moveDisallowedReason.setData(t.partOfAWay)
+ }
+ })
+ this._state.osmObjectDownloader.DownloadReferencingRelations(id).then((partOf) => {
+ if (partOf.length > 0) {
+ this.moveDisallowedReason.setData(t.partOfRelation)
+ }
+ })
+ }
+ }
+}