forked from MapComplete/MapComplete
		
	
		
			
	
	
		
			185 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			185 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) | ||
|  |             } | ||
|  |         }) | ||
|  |     } | ||
|  | 
 | ||
|  | } |