forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			184 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			184 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {Translation} from "../../i18n/Translation";
 | 
						|
import OsmObjectDownloader from "../../../Logic/Osm/OsmObjectDownloader";
 | 
						|
import {UIEventSource} from "../../../Logic/UIEventSource";
 | 
						|
import {OsmId} from "../../../Models/OsmFeature";
 | 
						|
import {OsmConnection} from "../../../Logic/Osm/OsmConnection";
 | 
						|
import {SpecialVisualizationState} from "../../SpecialVisualization";
 | 
						|
import Translations from "../../i18n/Translations";
 | 
						|
import Constants from "../../../Models/Constants";
 | 
						|
 | 
						|
 | 
						|
export class DeleteFlowState {
 | 
						|
    public readonly canBeDeleted: UIEventSource<boolean | undefined> = new UIEventSource<boolean | undefined>(undefined)
 | 
						|
    public readonly canBeDeletedReason: UIEventSource<Translation | undefined> = new UIEventSource<Translation>(undefined)
 | 
						|
    private readonly objectDownloader: OsmObjectDownloader
 | 
						|
    private readonly _id: OsmId
 | 
						|
    private readonly _allowDeletionAtChangesetCount: number
 | 
						|
    private readonly _osmConnection: OsmConnection
 | 
						|
    private readonly state: SpecialVisualizationState
 | 
						|
 | 
						|
    constructor(
 | 
						|
        id: OsmId,
 | 
						|
        state: SpecialVisualizationState,
 | 
						|
        allowDeletionAtChangesetCount?: number
 | 
						|
    ) {
 | 
						|
        this.state = state
 | 
						|
        this.objectDownloader = state.osmObjectDownloader
 | 
						|
        this._id = id
 | 
						|
        this._osmConnection = state.osmConnection
 | 
						|
        this._allowDeletionAtChangesetCount = allowDeletionAtChangesetCount ?? Number.MAX_VALUE
 | 
						|
 | 
						|
        this.CheckDeleteability(false)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Checks if the currently logged in user can delete the current point.
 | 
						|
     * State is written into this._canBeDeleted
 | 
						|
     * @constructor
 | 
						|
     * @private
 | 
						|
     */
 | 
						|
    public CheckDeleteability(useTheInternet: boolean): void {
 | 
						|
        console.log("Checking deleteability (internet?", useTheInternet, ")")
 | 
						|
        const t = Translations.t.delete
 | 
						|
        const id = this._id
 | 
						|
        const self = this
 | 
						|
        if (!id.startsWith("node")) {
 | 
						|
            this.canBeDeleted.setData(false)
 | 
						|
            this.canBeDeletedReason.setData(t.isntAPoint)
 | 
						|
            return
 | 
						|
        }
 | 
						|
 | 
						|
        // Does the currently logged in user have enough experience to delete this point?
 | 
						|
        const deletingPointsOfOtherAllowed = this._osmConnection.userDetails.map((ud) => {
 | 
						|
            if (ud === undefined) {
 | 
						|
                return undefined
 | 
						|
            }
 | 
						|
            if (!ud.loggedIn) {
 | 
						|
                return false
 | 
						|
            }
 | 
						|
            return (
 | 
						|
                ud.csCount >=
 | 
						|
                Math.min(
 | 
						|
                    Constants.userJourney.deletePointsOfOthersUnlock,
 | 
						|
                    this._allowDeletionAtChangesetCount
 | 
						|
                )
 | 
						|
            )
 | 
						|
        })
 | 
						|
 | 
						|
        const previousEditors = new UIEventSource<number[]>(undefined)
 | 
						|
        const allByMyself = previousEditors.map(
 | 
						|
            (previous) => {
 | 
						|
                if (previous === null || previous === undefined) {
 | 
						|
                    // Not yet downloaded
 | 
						|
                    return null
 | 
						|
                }
 | 
						|
                const userId = self._osmConnection.userDetails.data.uid
 | 
						|
                return !previous.some((editor) => editor !== userId)
 | 
						|
            },
 | 
						|
            [self._osmConnection.userDetails]
 | 
						|
        )
 | 
						|
 | 
						|
        // User allowed OR only edited by self?
 | 
						|
        const deletetionAllowed = deletingPointsOfOtherAllowed.map(
 | 
						|
            (isAllowed) => {
 | 
						|
                if (isAllowed === undefined) {
 | 
						|
                    // No logged in user => definitively not allowed to delete!
 | 
						|
                    return false
 | 
						|
                }
 | 
						|
                if (isAllowed === true) {
 | 
						|
                    return true
 | 
						|
                }
 | 
						|
 | 
						|
                // At this point, the logged in user is not allowed to delete points created/edited by _others_
 | 
						|
                // however, we query OSM and if it turns out the current point has only be edited by the current user, deletion is allowed after all!
 | 
						|
 | 
						|
                if (allByMyself.data === null && useTheInternet) {
 | 
						|
                    // We kickoff the download here as it hasn't yet been downloaded. Note that this is mapped onto 'all by myself' above
 | 
						|
                    const hist = this.objectDownloader
 | 
						|
                        .DownloadHistory(id)
 | 
						|
                        .map((versions) =>
 | 
						|
                            versions.map((version) =>
 | 
						|
                                Number(version.tags["_last_edit:contributor:uid"])
 | 
						|
                            )
 | 
						|
                        )
 | 
						|
                    hist.addCallbackAndRunD((hist) => previousEditors.setData(hist))
 | 
						|
                }
 | 
						|
 | 
						|
                if (allByMyself.data === true) {
 | 
						|
                    // Yay! We can download!
 | 
						|
                    return true
 | 
						|
                }
 | 
						|
                if (allByMyself.data === false) {
 | 
						|
                    // Nope, downloading not allowed...
 | 
						|
                    return false
 | 
						|
                }
 | 
						|
 | 
						|
                // At this point, we don't have enough information yet to decide if the user is allowed to delete the current point...
 | 
						|
                return undefined
 | 
						|
            },
 | 
						|
            [allByMyself]
 | 
						|
        )
 | 
						|
 | 
						|
        const hasRelations: UIEventSource<boolean> = new UIEventSource<boolean>(null)
 | 
						|
        const hasWays: UIEventSource<boolean> = new UIEventSource<boolean>(null)
 | 
						|
        deletetionAllowed.addCallbackAndRunD((deletetionAllowed) => {
 | 
						|
            if (deletetionAllowed === false) {
 | 
						|
                // Nope, we are not allowed to delete
 | 
						|
 | 
						|
                this.canBeDeleted.setData(false)
 | 
						|
                this.canBeDeletedReason.setData(t.notEnoughExperience)
 | 
						|
                return true // unregister this caller!
 | 
						|
            }
 | 
						|
 | 
						|
            if (!useTheInternet) {
 | 
						|
                return
 | 
						|
            }
 | 
						|
 | 
						|
            // All right! We have arrived at a point that we should query OSM again to check that the point isn't a part of ways or relations
 | 
						|
            this.objectDownloader.DownloadReferencingRelations(id).then((rels) => {
 | 
						|
                hasRelations.setData(rels.length > 0)
 | 
						|
            })
 | 
						|
 | 
						|
            this.objectDownloader.DownloadReferencingWays(id).then((ways) => {
 | 
						|
                hasWays.setData(ways.length > 0)
 | 
						|
            })
 | 
						|
            return true // unregister to only run once
 | 
						|
        })
 | 
						|
 | 
						|
        const hasWaysOrRelations = hasRelations.map(
 | 
						|
            (hasRelationsData) => {
 | 
						|
                if (hasRelationsData === true) {
 | 
						|
                    return true
 | 
						|
                }
 | 
						|
                if (hasWays.data === true) {
 | 
						|
                    return true
 | 
						|
                }
 | 
						|
                if (hasWays.data === null || hasRelationsData === null) {
 | 
						|
                    return null
 | 
						|
                }
 | 
						|
                if (hasWays.data === false && hasRelationsData === false) {
 | 
						|
                    return false
 | 
						|
                }
 | 
						|
                return null
 | 
						|
            },
 | 
						|
            [hasWays]
 | 
						|
        )
 | 
						|
 | 
						|
        hasWaysOrRelations.addCallbackAndRun((waysOrRelations) => {
 | 
						|
            if (waysOrRelations == null) {
 | 
						|
                // Not yet loaded - we still wait a little bit
 | 
						|
                return
 | 
						|
            }
 | 
						|
            if (waysOrRelations) {
 | 
						|
                // not deleteable by mapcomplete
 | 
						|
                this.canBeDeleted.setData(false)
 | 
						|
                this.canBeDeletedReason.setData(t.partOfOthers)
 | 
						|
            } else {
 | 
						|
                // alright, this point can be safely deleted!
 | 
						|
                this.canBeDeleted.setData(true)
 | 
						|
                this.canBeDeletedReason.setData(allByMyself.data ? t.onlyEditedByLoggedInUser : t.safeDelete)
 | 
						|
            }
 | 
						|
        })
 | 
						|
    }
 | 
						|
 | 
						|
}
 |