forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			227 lines
		
	
	
		
			No EOL
		
	
	
		
			8.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			No EOL
		
	
	
		
			8.7 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)
 | 
						|
        )
 | 
						|
    }
 | 
						|
} |