forked from MapComplete/MapComplete
		
	First steps for a delete button
This commit is contained in:
		
							parent
							
								
									b7798a470c
								
							
						
					
					
						commit
						985e97d43b
					
				
					 10 changed files with 246 additions and 75 deletions
				
			
		|  | @ -13,6 +13,7 @@ export default class UserDetails { | |||
| 
 | ||||
|     public loggedIn = false; | ||||
|     public name = "Not logged in"; | ||||
|     public uid: number; | ||||
|     public csCount = 0; | ||||
|     public img: string; | ||||
|     public unreadMessages = 0; | ||||
|  | @ -167,6 +168,7 @@ export class OsmConnection { | |||
|             data.loggedIn = true; | ||||
|             console.log("Login completed, userinfo is ", userInfo); | ||||
|             data.name = userInfo.getAttribute('display_name'); | ||||
|             data.uid= Number(userInfo.getAttribute("id")) | ||||
|             data.csCount = userInfo.getElementsByTagName("changesets")[0].getAttribute("count"); | ||||
| 
 | ||||
|             data.img = undefined; | ||||
|  |  | |||
|  | @ -43,11 +43,47 @@ export abstract class OsmObject { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static DownloadHistory(id: string, continuation: (versions: OsmObject[]) => void): void { | ||||
|     /** | ||||
|      * Downloads the ways that are using this node. | ||||
|      * Beware: their geometry will be incomplete! | ||||
|      * @param id | ||||
|      * @param continuation | ||||
|      * @constructor | ||||
|      */ | ||||
|     public static DownloadReferencingWays(id: string, continuation: (referencingWays: OsmWay[]) => void){ | ||||
|         Utils.downloadJson(`https://www.openStreetMap.org/api/0.6/${id}/ways`) | ||||
|             .then(data => { | ||||
|                const ways = data.elements.map(wayInfo => { | ||||
|                     const way = new OsmWay(wayInfo.id) | ||||
|                     way.LoadData(wayInfo) | ||||
|                     return way | ||||
|                 }) | ||||
|                 continuation(ways) | ||||
|             }) | ||||
|     } | ||||
|     /** | ||||
|      * Downloads the relations that are using this feature. | ||||
|      * Beware: their geometry will be incomplete! | ||||
|      * @param id | ||||
|      * @param continuation | ||||
|      * @constructor | ||||
|      */ | ||||
|     public static DownloadReferencingRelations(id: string, continuation: (referencingRelations: OsmRelation[]) => void){ | ||||
|         Utils.downloadJson(`https://www.openStreetMap.org/api/0.6/${id}/relations`) | ||||
|             .then(data => { | ||||
|                 const rels = data.elements.map(wayInfo => { | ||||
|                     const rel = new OsmRelation(wayInfo.id) | ||||
|                     rel.LoadData(wayInfo) | ||||
|                     return rel | ||||
|                 }) | ||||
|                 continuation(rels) | ||||
|             }) | ||||
|     } | ||||
|     public static DownloadHistory(id: string, continuation: (versions: OsmObject[]) => void): void{ | ||||
|         const splitted = id.split("/"); | ||||
|         const type = splitted[0]; | ||||
|         const idN = splitted[1]; | ||||
|         $.getJSON("https://openStreetMap.org/api/0.6/" + type + "/" + idN + "/history", data => { | ||||
|         $.getJSON("https://www.openStreetMap.org/api/0.6/" + type + "/" + idN + "/history", data => { | ||||
|             const elements: any[] = data.elements; | ||||
|             const osmObjects: OsmObject[] = [] | ||||
|             for (const element of elements) { | ||||
|  |  | |||
|  | @ -7,11 +7,13 @@ export default class Constants { | |||
|     // The user journey states thresholds when a new feature gets unlocked
 | ||||
|     public static userJourney = { | ||||
|         moreScreenUnlock: 1, | ||||
|         personalLayoutUnlock: 15, | ||||
|         historyLinkVisible: 20, | ||||
|         personalLayoutUnlock: 5, | ||||
|         historyLinkVisible: 10, | ||||
|         deletePointsOfOthersUnlock: 15, | ||||
|         tagsVisibleAt: 25, | ||||
|         mapCompleteHelpUnlock: 50, | ||||
|         tagsVisibleAndWikiLinked: 30, | ||||
|          | ||||
|         mapCompleteHelpUnlock: 50, | ||||
|         themeGeneratorReadOnlyUnlock: 50, | ||||
|         themeGeneratorFullUnlock: 500, | ||||
|         addNewPointWithUnreadMessagesUnlock: 500, | ||||
|  |  | |||
							
								
								
									
										7
									
								
								UI/Base/Loading.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								UI/Base/Loading.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| import {FixedUiElement} from "./FixedUiElement"; | ||||
| 
 | ||||
| export default class Loading extends FixedUiElement { | ||||
|     constructor() { | ||||
|         super("Loading..."); // TODO to be improved
 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										72
									
								
								UI/Popup/DeleteButton.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								UI/Popup/DeleteButton.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,72 @@ | |||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {OsmObject} from "../../Logic/Osm/OsmObject"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {Translation} from "../i18n/Translation"; | ||||
| import State from "../../State"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import Loading from "../Base/Loading"; | ||||
| import UserDetails from "../../Logic/Osm/OsmConnection"; | ||||
| import Constants from "../../Models/Constants"; | ||||
| import {SubtleButton} from "../Base/SubtleButton"; | ||||
| import Svg from "../../Svg"; | ||||
| import {Utils} from "../../Utils"; | ||||
| 
 | ||||
| 
 | ||||
| export default class DeleteButton extends Toggle { | ||||
|     constructor(id: string) { | ||||
| 
 | ||||
|         const hasRelations: UIEventSource<boolean> = new UIEventSource<boolean>(null) | ||||
|         OsmObject.DownloadReferencingRelations(id, (rels) => { | ||||
|             hasRelations.setData(rels.length > 0) | ||||
|         }) | ||||
| 
 | ||||
|         const hasWays: UIEventSource<boolean> = new UIEventSource<boolean>(null) | ||||
|         OsmObject.DownloadReferencingWays(id, (ways) => { | ||||
|             hasWays.setData(ways.length > 0) | ||||
|         }) | ||||
| 
 | ||||
|         const previousEditors = new UIEventSource<number[]>(null) | ||||
|         OsmObject.DownloadHistory(id, versions => { | ||||
|             const uids = versions.map(version => version.tags["_last_edit:contributor:uid"]) | ||||
|             previousEditors.setData(uids) | ||||
|         }) | ||||
|         const allByMyself = previousEditors.map(previous => { | ||||
|             if (previous === null) { | ||||
|                 return null; | ||||
|             } | ||||
|             const userId = State.state.osmConnection.userDetails.data.uid; | ||||
|             return !previous.some(editor => editor !== userId) | ||||
|         }, [State.state.osmConnection.userDetails]) | ||||
| 
 | ||||
|         const t = Translations.t.deleteButton | ||||
| 
 | ||||
|         super( | ||||
|             new Toggle( | ||||
|                 new VariableUiElement( | ||||
|                     hasRelations.map(hasRelations => { | ||||
|                         if (hasRelations === null || hasWays.data === null) { | ||||
|                             return new Loading() | ||||
|                         } | ||||
|                         if (hasWays.data || hasRelations) { | ||||
|                             return t.partOfOthers.Clone() | ||||
|                         } | ||||
| 
 | ||||
|                         return new Toggle( | ||||
|                             new SubtleButton(Svg.delete_icon_svg(), t.delete.Clone()), | ||||
|                             t.notEnoughExperience.Clone(), | ||||
|                             State.state.osmConnection.userDetails.map(userinfo => | ||||
|                                 allByMyself.data || | ||||
|                                 userinfo.csCount >= Constants.userJourney.deletePointsOfOthersUnlock, | ||||
|                                 [allByMyself]) | ||||
|                         ) | ||||
| 
 | ||||
|                     }, [hasWays]) | ||||
|                 ), | ||||
|                 t.onlyEditedByLoggedInUser.Clone().onClick(State.state.osmConnection.AttemptLogin), | ||||
|                 State.state.osmConnection.isLoggedIn), | ||||
|             t.isntAPoint, | ||||
|             new UIEventSource<boolean>(id.startsWith("node")) | ||||
|         ); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								Utils.ts
									
										
									
									
									
								
							|  | @ -343,6 +343,7 @@ export class Utils { | |||
|                         } | ||||
|                     }; | ||||
|                     xhr.open('GET', url); | ||||
|                     xhr.setRequestHeader("accept","application/json") | ||||
|                     xhr.send(); | ||||
|                 }catch(e){ | ||||
|                     reject(e) | ||||
|  |  | |||
|  | @ -27,6 +27,15 @@ | |||
|     "intro": "MapComplete is an OpenStreetMap-viewer and editor, which shows you information about a specific theme.", | ||||
|     "pickTheme": "Pick a theme below to get started." | ||||
|   }, | ||||
|   "deleteButton": { | ||||
|     "delete": "Delete", | ||||
|     "loginToDelete": "You must be logged in to delete a point", | ||||
|     "checkingDeletability": "Inspecting properties to check if this feature can be deleted", | ||||
|     "isntAPoint": "Only points can be deleted", | ||||
|     "onlyEditedByLoggedInUser": "This point has only be edited by yourself, you can safely delete it", | ||||
|     "notEnoughExperience": "You don't have enough experience to delete points made by other people. Make more edits to improve your skills", | ||||
|     "partOfOthers": "This point is part of some way or relation, so you can not delete it" | ||||
|   }, | ||||
|   "general": { | ||||
|     "loginWithOpenStreetMap": "Login with OpenStreetMap", | ||||
|     "welcomeBack": "You are logged in, welcome back!", | ||||
|  |  | |||
							
								
								
									
										138
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										138
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -1,4 +1,8 @@ | |||
| import ValidatedTextField from "./UI/Input/ValidatedTextField"; | ||||
| import {OsmObject} from "./Logic/Osm/OsmObject"; | ||||
| import DeleteButton from "./UI/Popup/DeleteButton"; | ||||
| import Combine from "./UI/Base/Combine"; | ||||
| import State from "./State"; | ||||
| /*import ValidatedTextField from "./UI/Input/ValidatedTextField"; | ||||
| import Combine from "./UI/Base/Combine"; | ||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement"; | ||||
| import {UIEventSource} from "./Logic/UIEventSource"; | ||||
|  | @ -69,75 +73,77 @@ function TestAllInputMethods() { | |||
|     })).AttachTo("maindiv") | ||||
| } | ||||
| 
 | ||||
| function TestMiniMap() { | ||||
| 
 | ||||
| const location = new UIEventSource<Loc>({ | ||||
|     lon: 4.84771728515625, | ||||
|     lat: 51.17920846421931, | ||||
|     zoom: 14 | ||||
| }) | ||||
| const map0 = new Minimap({ | ||||
|     location: location, | ||||
|     allowMoving: true, | ||||
|     background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[2]) | ||||
| }) | ||||
| map0.SetStyle("width: 500px; height: 250px; overflow: hidden; border: 2px solid red") | ||||
|     .AttachTo("maindiv") | ||||
| 
 | ||||
| const layout = AllKnownLayouts.layoutsList[1] | ||||
| State.state = new State(layout) | ||||
| console.log("LAYOUT is", layout.id) | ||||
| 
 | ||||
| const feature = { | ||||
|         "type": "Feature", | ||||
|         _matching_layer_id: "bike_repair_station", | ||||
|         "properties": { | ||||
|             id: "node/-1", | ||||
|             "amenity": "bicycle_repair_station" | ||||
|         }, | ||||
|         "geometry": { | ||||
|             "type": "Point", | ||||
|             "coordinates": [ | ||||
|                 4.84771728515625, | ||||
|                 51.17920846421931 | ||||
|             ] | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| ; | ||||
| 
 | ||||
| State.state.allElements.addOrGetElement(feature) | ||||
| 
 | ||||
| const featureSource = new UIEventSource([{ | ||||
|     freshness: new Date(), | ||||
|     feature: feature | ||||
| }]) | ||||
| 
 | ||||
| new ShowDataLayer( | ||||
|     featureSource, | ||||
|     map0.leafletMap, | ||||
|     new UIEventSource<LayoutConfig>(layout) | ||||
| ) | ||||
| 
 | ||||
| const map1 = new Minimap({ | ||||
|     const location = new UIEventSource<Loc>({ | ||||
|         lon: 4.84771728515625, | ||||
|         lat: 51.17920846421931, | ||||
|         zoom: 14 | ||||
|     }) | ||||
|     const map0 = new Minimap({ | ||||
|         location: location, | ||||
|         allowMoving: true, | ||||
|         background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[5]) | ||||
|     }, | ||||
| ) | ||||
|         background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[2]) | ||||
|     }) | ||||
|     map0.SetStyle("width: 500px; height: 250px; overflow: hidden; border: 2px solid red") | ||||
|         .AttachTo("maindiv") | ||||
| 
 | ||||
| map1.SetStyle("width: 500px; height: 250px; overflow: hidden; border : 2px solid black") | ||||
|     .AttachTo("extradiv") | ||||
|     const layout = AllKnownLayouts.layoutsList[1] | ||||
|     State.state = new State(layout) | ||||
|     console.log("LAYOUT is", layout.id) | ||||
| 
 | ||||
|     const feature = { | ||||
|             "type": "Feature", | ||||
|             _matching_layer_id: "bike_repair_station", | ||||
|             "properties": { | ||||
|                 id: "node/-1", | ||||
|                 "amenity": "bicycle_repair_station" | ||||
|             }, | ||||
|             "geometry": { | ||||
|                 "type": "Point", | ||||
|                 "coordinates": [ | ||||
|                     4.84771728515625, | ||||
|                     51.17920846421931 | ||||
|                 ] | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     ; | ||||
| 
 | ||||
|     State.state.allElements.addOrGetElement(feature) | ||||
| 
 | ||||
|     const featureSource = new UIEventSource([{ | ||||
|         freshness: new Date(), | ||||
|         feature: feature | ||||
|     }]) | ||||
| 
 | ||||
|     new ShowDataLayer( | ||||
|         featureSource, | ||||
|         map0.leafletMap, | ||||
|         new UIEventSource<LayoutConfig>(layout) | ||||
|     ) | ||||
| 
 | ||||
|     const map1 = new Minimap({ | ||||
|             location: location, | ||||
|             allowMoving: true, | ||||
|             background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[5]) | ||||
|         }, | ||||
|     ) | ||||
| 
 | ||||
|     map1.SetStyle("width: 500px; height: 250px; overflow: hidden; border : 2px solid black") | ||||
|         .AttachTo("extradiv") | ||||
| 
 | ||||
| 
 | ||||
|     new ShowDataLayer( | ||||
|         featureSource, | ||||
|         map1.leafletMap, | ||||
|         new UIEventSource<LayoutConfig>(layout) | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| new ShowDataLayer( | ||||
|     featureSource, | ||||
|     map1.leafletMap, | ||||
|     new UIEventSource<LayoutConfig>(layout) | ||||
| ) | ||||
| 
 | ||||
| featureSource.ping() | ||||
| 
 | ||||
| // */
 | ||||
|     featureSource.ping() | ||||
| } | ||||
| //*/
 | ||||
| State.state= new State(undefined) | ||||
| new Combine([ | ||||
|     new DeleteButton("node/8598664388"), | ||||
| ]).AttachTo("maindiv") | ||||
|  |  | |||
							
								
								
									
										32
									
								
								test/OsmObject.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								test/OsmObject.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| import T from "./TestHelper"; | ||||
| import {OsmObject} from "../Logic/Osm/OsmObject"; | ||||
| import ScriptUtils from "../scripts/ScriptUtils"; | ||||
| 
 | ||||
| export default class OsmObjectSpec extends T { | ||||
|     constructor() { | ||||
|         super("OsmObject", [ | ||||
|             [ | ||||
|                 "Download referencing ways", | ||||
|                 () => { | ||||
|                     let downloaded = false; | ||||
|                     OsmObject.DownloadReferencingWays("node/1124134958", ways => { | ||||
|                         downloaded = true; | ||||
|                         console.log(ways) | ||||
|                     }) | ||||
|                     let timeout = 10 | ||||
|                     while (!downloaded && timeout >= 0) { | ||||
|                         ScriptUtils.sleep(1000) | ||||
| 
 | ||||
|                         timeout--; | ||||
|                     } | ||||
|                     if(!downloaded){ | ||||
|                         throw "Timeout: referencing ways not found" | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|             ] | ||||
| 
 | ||||
| 
 | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
|  | @ -1,5 +1,4 @@ | |||
| import {Utils} from "../Utils"; | ||||
| Utils.runningFromConsole = true; | ||||
| import {Utils} from "../Utils";Utils.runningFromConsole = true; | ||||
| import TagSpec from "./Tag.spec"; | ||||
| import ImageAttributionSpec from "./ImageAttribution.spec"; | ||||
| import GeoOperationsSpec from "./GeoOperations.spec"; | ||||
|  | @ -10,6 +9,10 @@ import OsmConnectionSpec from "./OsmConnection.spec"; | |||
| import T from "./TestHelper"; | ||||
| import {FixedUiElement} from "../UI/Base/FixedUiElement"; | ||||
| import Combine from "../UI/Base/Combine"; | ||||
| import OsmObjectSpec from "./OsmObject.spec"; | ||||
| import ScriptUtils from "../scripts/ScriptUtils"; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| export default class TestAll { | ||||
|     private needsBrowserTests: T[] = [new OsmConnectionSpec()] | ||||
|  | @ -26,8 +29,9 @@ export default class TestAll { | |||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ScriptUtils.fixUtils() | ||||
| const allTests = [ | ||||
|     new OsmObjectSpec(), | ||||
|     new TagSpec(), | ||||
|     new ImageAttributionSpec(), | ||||
|     new GeoOperationsSpec(), | ||||
|  | @ -39,6 +43,6 @@ const allTests = [ | |||
| 
 | ||||
| for (const test of allTests) { | ||||
|     if (test.failures.length > 0) { | ||||
|         throw "Some test failed" | ||||
|         throw "Some test failed: "+test.failures.join(", ") | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue