mapcomplete/UI/Popup/MoveWizard.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

282 lines
11 KiB
TypeScript
Raw Normal View History

import { SubtleButton } from "../Base/SubtleButton"
2021-10-11 00:54:35 +02:00
import Combine from "../Base/Combine"
import Svg from "../../Svg"
import Toggle from "../Input/Toggle"
import { UIEventSource } from "../../Logic/UIEventSource"
2021-10-11 00:54:35 +02:00
import Translations from "../i18n/Translations"
import { VariableUiElement } from "../Base/VariableUIElement"
import { Translation } from "../i18n/Translation"
2021-10-11 00:54:35 +02:00
import BaseUIElement from "../BaseUIElement"
import { GeoOperations } from "../../Logic/GeoOperations"
2021-10-13 03:09:37 +02:00
import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction"
import MoveConfig from "../../Models/ThemeConfig/MoveConfig"
2022-12-16 13:45:07 +01:00
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"
2021-10-11 00:54:35 +02:00
2021-10-13 03:09:37 +02:00
interface MoveReason {
text: Translation | string
invitingText: Translation | string
icon: BaseUIElement
2021-10-11 00:54:35 +02:00
changesetCommentValue: string
lockBounds: true | boolean
2022-12-15 20:27:20 +01:00
includeSearch: false | boolean
background: undefined | "map" | "photo" | string | string[]
2021-10-13 03:09:37 +02:00
startZoom: number
minZoom: number
2022-12-15 20:27:20 +01:00
eraseAddressFields: false | boolean
2021-10-13 03:09:37 +02:00
}
2021-10-11 00:54:35 +02:00
export default class MoveWizard extends Toggle {
/**
* The UI-element which helps moving a point
*/
constructor(
featureToMove: Feature<Point>,
tags: UIEventSource<OsmTags>,
state: SpecialVisualizationState,
2021-11-07 16:34:51 +01:00
options: MoveConfig
) {
2021-10-13 03:09:37 +02:00
const t = Translations.t.move
const reasons: MoveReason[] = []
if (options.enableRelocation) {
2021-10-11 00:54:35 +02:00
reasons.push({
text: t.reasons.reasonRelocation,
invitingText: t.inviteToMove.reasonRelocation,
2021-10-11 00:54:35 +02:00
icon: Svg.relocation_svg(),
changesetCommentValue: "relocated",
lockBounds: false,
background: undefined,
2022-12-15 20:27:20 +01:00
includeSearch: true,
2021-10-13 03:09:37 +02:00
startZoom: 12,
minZoom: 6,
2022-12-16 13:45:07 +01:00
eraseAddressFields: true,
2021-10-11 00:54:35 +02:00
})
}
2021-11-07 16:34:51 +01:00
if (options.enableImproveAccuracy) {
2021-10-11 00:54:35 +02:00
reasons.push({
text: t.reasons.reasonInaccurate,
invitingText: t.inviteToMove.reasonInaccurate,
2021-10-11 00:54:35 +02:00
icon: Svg.crosshair_svg(),
changesetCommentValue: "improve_accuracy",
lockBounds: true,
2022-12-15 20:27:20 +01:00
includeSearch: false,
2021-10-11 00:54:35 +02:00
background: "photo",
startZoom: 18,
2021-10-13 03:09:37 +02:00
minZoom: 16,
2022-12-16 13:45:07 +01:00
eraseAddressFields: false,
2021-10-11 00:54:35 +02:00
})
}
const currentStep = new UIEventSource<"start" | "reason" | "pick_location" | "moved">(
"start"
)
const moveReason = new UIEventSource<MoveReason>(undefined)
2021-11-07 16:34:51 +01:00
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")
})
2021-11-07 16:34:51 +01:00
} else {
moveButton = new SubtleButton(
Svg.move_svg().SetStyle("width: 1.5rem; height: 1.5rem"),
t.inviteToMove.generic
).onClick(() => {
currentStep.setData("reason")
2021-10-11 00:54:35 +02:00
})
}
2021-11-07 16:34:51 +01:00
const moveAgainButton = new SubtleButton(Svg.move_svg(), t.inviteToMoveAgain).onClick(
() => {
currentStep.setData("reason")
}
)
2021-10-11 00:54:35 +02:00
const selectReason = new Combine(
reasons.map((r) =>
new SubtleButton(r.icon, r.text).onClick(() => {
moveReason.setData(r)
currentStep.setData("pick_location")
2022-09-08 21:40:48 +02:00
})
)
)
2021-10-13 03:09:37 +02:00
2021-10-11 00:54:35 +02:00
const cancelButton = new SubtleButton(Svg.close_svg(), t.cancel).onClick(() =>
currentStep.setData("start")
2022-09-08 21:40:48 +02:00
)
2021-10-13 03:09:37 +02:00
2021-10-11 00:54:35 +02:00
const [lon, lat] = GeoOperations.centerpointCoordinates(featureToMove)
2021-10-13 03:09:37 +02:00
const locationInput = moveReason.map((reason) => {
if (reason === undefined) {
return undefined
}
const mapProperties: Partial<MapProperties> = {
minzoom: new UIEventSource(reason.minZoom),
zoom: new UIEventSource(reason?.startZoom ?? 16),
location: new UIEventSource({ lon, lat }),
bounds: new UIEventSource(undefined),
rasterLayer: state.mapProperties.rasterLayer,
2021-10-14 18:59:21 +02:00
}
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")
2021-10-13 03:09:37 +02:00
2022-12-15 20:27:20 +01:00
let searchPanel: BaseUIElement = undefined
if (reason.includeSearch) {
searchPanel = new SvelteUIElement(Geosearch, {
bounds: mapProperties.bounds,
clearAfterView: false,
})
2022-12-15 20:27:20 +01:00
}
2021-10-13 03:09:37 +02:00
locationInput.SetStyle("height: 17.5rem")
const confirmMove = new SubtleButton(Svg.move_confirm_svg(), t.confirmMove)
2022-12-15 20:27:20 +01:00
confirmMove.onClick(async () => {
const loc = value.data
2022-12-15 20:27:20 +01:00
await state.changes.applyAction(
new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], {
2021-10-25 20:48:06 +02:00
reason: reason.changesetCommentValue,
theme: state.layout.id,
2021-10-13 03:09:37 +02:00
})
2022-09-08 21:40:48 +02:00
)
featureToMove.properties._lat = loc.lat
featureToMove.properties._lon = loc.lon
featureToMove.geometry.coordinates = [loc.lon, loc.lat]
2022-12-15 20:27:20 +01:00
if (reason.eraseAddressFields) {
await state.changes.applyAction(
2022-12-16 13:45:07 +01:00
new ChangeTagAction(
featureToMove.properties.id,
new And([
new Tag("addr:housenumber", ""),
2022-12-15 20:27:20 +01:00
new Tag("addr:street", ""),
new Tag("addr:city", ""),
2022-12-16 13:45:07 +01:00
new Tag("addr:postcode", ""),
]),
2022-12-15 20:27:20 +01:00
featureToMove.properties,
{
changeType: "relocated",
theme: state.layout.id,
2022-12-15 20:27:20 +01:00
}
)
)
}
state.featureProperties.getStore(id).ping()
2021-10-13 03:09:37 +02:00
currentStep.setData("moved")
state.mapProperties.location.setData(loc)
2021-10-13 03:09:37 +02:00
})
const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6")
2021-10-13 03:09:37 +02:00
return new Combine([
2022-12-15 20:27:20 +01:00
searchPanel,
2021-10-13 03:09:37 +02:00
locationInput,
new Toggle(
confirmMove,
zoomInFurhter,
mapProperties.zoom.map((zoom) => zoom >= Constants.minZoomLevelToAddNewPoint)
2022-09-08 21:40:48 +02:00
),
2021-10-13 03:09:37 +02:00
]).SetClass("flex flex-col")
})
const dialogClasses = "p-2 md:p-4 m-2 border border-gray-400 rounded-xl flex flex-col"
2023-01-06 03:37:22 +01:00
const moveFlow = new LoginToggle(
2021-10-11 00:54:35 +02:00
new VariableUiElement(
currentStep.map((currentStep) => {
2021-10-13 03:09:37 +02:00
switch (currentStep) {
2021-10-11 00:54:35 +02:00
case "start":
return moveButton
case "reason":
return new Combine([
t.whyMove.SetClass("text-lg font-bold"),
selectReason,
cancelButton,
]).SetClass(dialogClasses)
2021-10-11 00:54:35 +02:00
case "pick_location":
return new Combine([
t.moveTitle.SetClass("text-lg font-bold"),
new VariableUiElement(locationInput),
cancelButton,
]).SetClass(dialogClasses)
2021-10-13 03:09:37 +02:00
case "moved":
return new Combine([
t.pointIsMoved.SetClass("thanks"),
moveAgainButton,
]).SetClass("flex flex-col")
2021-10-11 00:54:35 +02:00
}
})
),
2023-01-06 03:37:22 +01:00
undefined,
state
2021-10-11 00:54:35 +02:00
)
2021-10-13 03:09:37 +02:00
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<BaseUIElement>(undefined)
if (id.startsWith("way")) {
moveDisallowedReason.setData(t.isWay)
2021-10-13 03:09:37 +02:00
} else if (id.startsWith("relation")) {
moveDisallowedReason.setData(t.isRelation)
2021-11-07 16:34:51 +01:00
} else if (id.indexOf("-") < 0) {
state.osmObjectDownloader.DownloadReferencingWays(id).then((referencing) => {
2021-10-13 03:09:37 +02:00
if (referencing.length > 0) {
console.log("Got a referencing way, move not allowed")
moveDisallowedReason.setData(t.partOfAWay)
2021-10-13 03:09:37 +02:00
}
})
state.osmObjectDownloader.DownloadReferencingRelations(id).then((partOf) => {
2021-11-07 16:34:51 +01:00
if (partOf.length > 0) {
moveDisallowedReason.setData(t.partOfRelation)
2021-10-13 03:09:37 +02:00
}
})
}
super(
moveFlow,
new Combine([
Svg.move_not_allowed_svg().SetStyle("height: 2rem").SetClass("m-2"),
new Combine([
t.cannotBeMoved,
2021-10-13 03:09:37 +02:00
new VariableUiElement(moveDisallowedReason).SetClass("subtle"),
]).SetClass("flex flex-col"),
]).SetClass("flex m-2 p-2 rounded-lg bg-gray-200"),
2021-10-13 03:09:37 +02:00
moveDisallowedReason.map((r) => r === undefined)
)
const self = this
currentStep.addCallback((state) => {
if (state === "start") {
return
}
self.ScrollIntoView()
})
2021-10-11 00:54:35 +02:00
}
}