forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			248 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			248 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { SubtleButton } from "../Base/SubtleButton"
 | |
| import Combine from "../Base/Combine"
 | |
| import Svg from "../../Svg"
 | |
| import { OsmConnection } from "../../Logic/Osm/OsmConnection"
 | |
| 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 LocationInput from "../Input/LocationInput"
 | |
| import Loc from "../../Models/Loc"
 | |
| import { GeoOperations } from "../../Logic/GeoOperations"
 | |
| import { OsmObject } from "../../Logic/Osm/OsmObject"
 | |
| import { Changes } from "../../Logic/Osm/Changes"
 | |
| import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction"
 | |
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | |
| import MoveConfig from "../../Models/ThemeConfig/MoveConfig"
 | |
| import { ElementStorage } from "../../Logic/ElementStorage"
 | |
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"
 | |
| import BaseLayer from "../../Models/BaseLayer"
 | |
| 
 | |
| interface MoveReason {
 | |
|     text: Translation | string
 | |
|     invitingText: Translation | string
 | |
|     icon: BaseUIElement
 | |
|     changesetCommentValue: string
 | |
|     lockBounds: true | boolean
 | |
|     background: undefined | "map" | "photo" | string | string[]
 | |
|     startZoom: number
 | |
|     minZoom: number
 | |
| }
 | |
| 
 | |
| export default class MoveWizard extends Toggle {
 | |
|     /**
 | |
|      * The UI-element which helps moving a point
 | |
|      */
 | |
|     constructor(
 | |
|         featureToMove: any,
 | |
|         state: {
 | |
|             osmConnection: OsmConnection
 | |
|             featureSwitchUserbadge: UIEventSource<boolean>
 | |
|             changes: Changes
 | |
|             layoutToUse: LayoutConfig
 | |
|             allElements: ElementStorage
 | |
|         },
 | |
|         options: MoveConfig
 | |
|     ) {
 | |
|         const t = Translations.t.move
 | |
|         const loginButton = new Toggle(
 | |
|             t.loginToMove.SetClass("btn").onClick(() => state.osmConnection.AttemptLogin()),
 | |
|             undefined,
 | |
|             state.featureSwitchUserbadge
 | |
|         )
 | |
| 
 | |
|         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,
 | |
|                 startZoom: 12,
 | |
|                 minZoom: 6,
 | |
|             })
 | |
|         }
 | |
|         if (options.enableImproveAccuracy) {
 | |
|             reasons.push({
 | |
|                 text: t.reasons.reasonInaccurate,
 | |
|                 invitingText: t.inviteToMove.reasonInaccurate,
 | |
|                 icon: Svg.crosshair_svg(),
 | |
|                 changesetCommentValue: "improve_accuracy",
 | |
|                 lockBounds: true,
 | |
|                 background: "photo",
 | |
|                 startZoom: 17,
 | |
|                 minZoom: 16,
 | |
|             })
 | |
|         }
 | |
| 
 | |
|         const currentStep = new UIEventSource<"start" | "reason" | "pick_location" | "moved">(
 | |
|             "start"
 | |
|         )
 | |
|         const moveReason = new UIEventSource<MoveReason>(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_ui().SetStyle("width: 1.5rem; height: 1.5rem"),
 | |
|                 t.inviteToMove.generic
 | |
|             ).onClick(() => {
 | |
|                 currentStep.setData("reason")
 | |
|             })
 | |
|         }
 | |
| 
 | |
|         const moveAgainButton = new SubtleButton(Svg.move_ui(), 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 loc = new UIEventSource<Loc>({
 | |
|                 lon: lon,
 | |
|                 lat: lat,
 | |
|                 zoom: reason?.startZoom ?? 16,
 | |
|             })
 | |
| 
 | |
|             let background: string[]
 | |
|             if (typeof reason.background == "string") {
 | |
|                 background = [reason.background]
 | |
|             } else {
 | |
|                 background = reason.background
 | |
|             }
 | |
| 
 | |
|             const preferredBackground = AvailableBaseLayers.SelectBestLayerAccordingTo(
 | |
|                 loc,
 | |
|                 new UIEventSource(background)
 | |
|             ).data
 | |
|             const locationInput = new LocationInput({
 | |
|                 minZoom: reason.minZoom,
 | |
|                 centerLocation: loc,
 | |
|                 mapBackground: new UIEventSource<BaseLayer>(preferredBackground), // We detach the layer
 | |
|             })
 | |
| 
 | |
|             if (reason.lockBounds) {
 | |
|                 locationInput.installBounds(0.05, true)
 | |
|             }
 | |
| 
 | |
|             locationInput.SetStyle("height: 17.5rem")
 | |
| 
 | |
|             const confirmMove = new SubtleButton(Svg.move_confirm_svg(), t.confirmMove)
 | |
|             confirmMove.onClick(() => {
 | |
|                 const loc = locationInput.GetValue().data
 | |
|                 state.changes.applyAction(
 | |
|                     new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], {
 | |
|                         reason: reason.changesetCommentValue,
 | |
|                         theme: state.layoutToUse.id,
 | |
|                     })
 | |
|                 )
 | |
|                 featureToMove.properties._lat = loc.lat
 | |
|                 featureToMove.properties._lon = loc.lon
 | |
|                 state.allElements.getEventSourceById(id).ping()
 | |
|                 currentStep.setData("moved")
 | |
|             })
 | |
|             const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6")
 | |
|             return new Combine([
 | |
|                 locationInput,
 | |
|                 new Toggle(
 | |
|                     confirmMove,
 | |
|                     zoomInFurhter,
 | |
|                     locationInput.GetValue().map((l) => l.zoom >= 19)
 | |
|                 ),
 | |
|             ]).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 Toggle(
 | |
|             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")
 | |
|                     }
 | |
|                 })
 | |
|             ),
 | |
|             loginButton,
 | |
|             state.osmConnection.isLoggedIn
 | |
|         )
 | |
|         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)
 | |
|         } else if (id.startsWith("relation")) {
 | |
|             moveDisallowedReason.setData(t.isRelation)
 | |
|         } else if (id.indexOf("-") < 0) {
 | |
|             OsmObject.DownloadReferencingWays(id).then((referencing) => {
 | |
|                 if (referencing.length > 0) {
 | |
|                     console.log("Got a referencing way, move not allowed")
 | |
|                     moveDisallowedReason.setData(t.partOfAWay)
 | |
|                 }
 | |
|             })
 | |
|             OsmObject.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)
 | |
|         )
 | |
|     }
 | |
| }
 |