forked from MapComplete/MapComplete
		
	Stabilize adding new points
This commit is contained in:
		
							parent
							
								
									d3c26c4f0e
								
							
						
					
					
						commit
						a3c16d6297
					
				
					 11 changed files with 249 additions and 257 deletions
				
			
		|  | @ -56,8 +56,6 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour | |||
|             readonly overpassTimeout: UIEventSource<number>; | ||||
|             readonly overpassMaxZoom: UIEventSource<number> | ||||
|         }) { | ||||
|         console.trace("Initializing an overpass FS") | ||||
| 
 | ||||
| 
 | ||||
|         this.state = state | ||||
|         this.relationsTracker = new RelationsTracker() | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import {OsmNode, OsmRelation, OsmWay} from "../../Osm/OsmObject"; | |||
| import FeatureSource from "../FeatureSource"; | ||||
| import {UIEventSource} from "../../UIEventSource"; | ||||
| import {ChangeDescription} from "../../Osm/Actions/ChangeDescription"; | ||||
| import State from "../../../State"; | ||||
| 
 | ||||
| export class NewGeometryFromChangesFeatureSource implements FeatureSource { | ||||
|     // This class name truly puts the 'Java' into 'Javascript'
 | ||||
|  | @ -54,6 +55,9 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { | |||
|                             tags[kv.k] = kv.v | ||||
|                         } | ||||
|                         tags["id"] = change.type+"/"+change.id | ||||
|                          | ||||
|                         tags["_backend"] = State.state.osmConnection._oauth_config.url | ||||
|                          | ||||
|                         switch (change.type) { | ||||
|                             case "node": | ||||
|                                 const n = new OsmNode(change.id) | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ import {LocalStorageSource} from "../Web/LocalStorageSource"; | |||
| export class Changes { | ||||
| 
 | ||||
| 
 | ||||
|     private _nextId : number = -1; // Newly assigned ID's are negative
 | ||||
|     private _nextId: number = -1; // Newly assigned ID's are negative
 | ||||
|     public readonly name = "Newly added features" | ||||
|     /** | ||||
|      * All the newly created features as featureSource + all the modified features | ||||
|  | @ -31,7 +31,10 @@ export class Changes { | |||
|         // We keep track of all changes just as well
 | ||||
|         this.allChanges.setData([...this.pendingChanges.data]) | ||||
|         // If a pending change contains a negative ID, we save that
 | ||||
|        this._nextId = Math.min(-1, ...this.pendingChanges.data?.map(pch => pch.id) ?? []) | ||||
|         this._nextId = Math.min(-1, ...this.pendingChanges.data?.map(pch => pch.id) ?? []) | ||||
| 
 | ||||
|         // Note: a changeset might be reused which was opened just before and might have already used some ids
 | ||||
|         // This doesn't matter however, as the '-1' is per piecewise upload, not global per changeset
 | ||||
|     } | ||||
| 
 | ||||
|     private static createChangesetFor(csId: string, | ||||
|  | @ -90,62 +93,58 @@ export class Changes { | |||
|         if (this.pendingChanges.data.length === 0) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (this.isUploading.data) { | ||||
|             console.log("Is already uploading... Abort") | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         this.isUploading.setData(true) | ||||
| 
 | ||||
|         this.flushChangesAsync(flushreason) | ||||
|             .then(_ => { | ||||
|                 this.isUploading.setData(false) | ||||
|                 console.log("Changes flushed!"); | ||||
|             }) | ||||
|             .catch(e => { | ||||
|                 this.isUploading.setData(false) | ||||
|                 console.error("Flushing changes failed due to", e); | ||||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     private async flushChangesAsync(flushreason: string = undefined): Promise<void> { | ||||
|         console.log("Beginning upload... " + flushreason ?? ""); | ||||
|         // At last, we build the changeset and upload
 | ||||
|         const self = this; | ||||
|         const pending = self.pendingChanges.data; | ||||
|         const neededIds = Changes.GetNeededIds(pending) | ||||
|         console.log("Needed ids", neededIds) | ||||
|         OsmObject.DownloadAll(neededIds, true).addCallbackAndRunD(osmObjects => { | ||||
|             console.log("Got the fresh objects!", osmObjects, "pending: ", pending) | ||||
|             try { | ||||
| 
 | ||||
| 
 | ||||
|                 const changes: { | ||||
|                     newObjects: OsmObject[], | ||||
|                     modifiedObjects: OsmObject[] | ||||
|                     deletedObjects: OsmObject[] | ||||
| 
 | ||||
|                 } = self.CreateChangesetObjects(pending, osmObjects) | ||||
|                 if (changes.newObjects.length + changes.deletedObjects.length + changes.modifiedObjects.length === 0) { | ||||
|                     console.log("No changes to be made") | ||||
|                     self.pendingChanges.setData([]) | ||||
|                     self.isUploading.setData(false) | ||||
|                     return true; // Unregister the callback
 | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
|                 State.state.osmConnection.UploadChangeset( | ||||
|                     State.state.layoutToUse.data, | ||||
|                     State.state.allElements, | ||||
|                     (csId) => Changes.createChangesetFor(csId, changes), | ||||
|                     () => { | ||||
|                         console.log("Upload successfull!") | ||||
|                         self.pendingChanges.setData([]); | ||||
|                         self.isUploading.setData(false) | ||||
|                     }, | ||||
|                     () => { | ||||
|                         console.log("Upload failed - trying again later") | ||||
|                         return self.isUploading.setData(false); | ||||
|                     } // Failed - mark to try again
 | ||||
|                 ) | ||||
|             } catch (e) { | ||||
|                 console.error("Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", e) | ||||
|         const osmObjects = await Promise.all(neededIds.map(id => OsmObject.DownloadObjectAsync(id))); | ||||
|         console.log("Got the fresh objects!", osmObjects, "pending: ", pending) | ||||
|         try { | ||||
|             const changes: { | ||||
|                 newObjects: OsmObject[], | ||||
|                 modifiedObjects: OsmObject[] | ||||
|                 deletedObjects: OsmObject[] | ||||
|             } = self.CreateChangesetObjects(pending, osmObjects) | ||||
|             if (changes.newObjects.length + changes.deletedObjects.length + changes.modifiedObjects.length === 0) { | ||||
|                 console.log("No changes to be made") | ||||
|                 self.pendingChanges.setData([]) | ||||
|                 self.isUploading.setData(false) | ||||
|             } | ||||
|             return true; | ||||
| 
 | ||||
|         }); | ||||
|             await State.state.osmConnection.UploadChangeset( | ||||
|                 State.state.layoutToUse.data, | ||||
|                 State.state.allElements, | ||||
|                 (csId) => Changes.createChangesetFor(csId, changes), | ||||
|             ) | ||||
| 
 | ||||
|             console.log("Upload successfull!") | ||||
|             this.pendingChanges.setData([]); | ||||
|             this.isUploading.setData(false) | ||||
| 
 | ||||
|         } catch (e) { | ||||
|             console.error("Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", e) | ||||
|             self.pendingChanges.setData([]) | ||||
|             self.isUploading.setData(false) | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
|  | @ -311,4 +310,8 @@ export class Changes { | |||
| 
 | ||||
|         return result | ||||
|     } | ||||
| 
 | ||||
|     public registerIdRewrites(mappings: Map<string, string>): void { | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -8,15 +8,23 @@ import Locale from "../../UI/i18n/Locale"; | |||
| import Constants from "../../Models/Constants"; | ||||
| import {OsmObject} from "./OsmObject"; | ||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||
| import {Changes} from "./Changes"; | ||||
| 
 | ||||
| export class ChangesetHandler { | ||||
| 
 | ||||
|     public readonly currentChangeset: UIEventSource<string>; | ||||
|     private readonly allElements: ElementStorage; | ||||
|     private readonly changes: Changes; | ||||
|     private readonly _dryRun: boolean; | ||||
|     private readonly userDetails: UIEventSource<UserDetails>; | ||||
|     private readonly auth: any; | ||||
| 
 | ||||
|     constructor(layoutName: string, dryRun: boolean, osmConnection: OsmConnection, auth) { | ||||
|     constructor(layoutName: string, dryRun: boolean, osmConnection: OsmConnection, | ||||
|                 allElements: ElementStorage, | ||||
|                 changes: Changes, | ||||
|                 auth) { | ||||
|         this.allElements = allElements; | ||||
|         this.changes = changes; | ||||
|         this._dryRun = dryRun; | ||||
|         this.userDetails = osmConnection.userDetails; | ||||
|         this.auth = auth; | ||||
|  | @ -27,35 +35,55 @@ export class ChangesetHandler { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage): void { | ||||
|     private handleIdRewrite(node: any, type: string): [string, string] { | ||||
|         const oldId = parseInt(node.attributes.old_id.value); | ||||
|         if (node.attributes.new_id === undefined) { | ||||
|             // We just removed this point!
 | ||||
|             const element =this. allElements.getEventSourceById("node/" + oldId); | ||||
|             element.data._deleted = "yes" | ||||
|             element.ping(); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const newId = parseInt(node.attributes.new_id.value); | ||||
|         const result: [string, string] = [type + "/" + oldId, type + "/" + newId] | ||||
|         if (!(oldId !== undefined && newId !== undefined && | ||||
|             !isNaN(oldId) && !isNaN(newId))) { | ||||
|             return undefined; | ||||
|         } | ||||
|         if (oldId == newId) { | ||||
|             return undefined; | ||||
|         } | ||||
|         console.log("Rewriting id: ", type + "/" + oldId, "-->", type + "/" + newId); | ||||
|         const element = this.allElements.getEventSourceById("node/" + oldId); | ||||
|         element.data.id = type + "/" + newId; | ||||
|         this.allElements.addElementById(type + "/" + newId, element); | ||||
|         this.allElements.ContainingFeatures.set(type + "/" + newId, this.allElements.ContainingFeatures.get(type + "/" + oldId)) | ||||
|         element.ping(); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     private parseUploadChangesetResponse(response: XMLDocument): void { | ||||
|         const nodes = response.getElementsByTagName("node"); | ||||
|         const mappings = new Map<string, string>() | ||||
|         // @ts-ignore
 | ||||
|         for (const node of nodes) { | ||||
|             const oldId = parseInt(node.attributes.old_id.value); | ||||
|             if (node.attributes.new_id === undefined) { | ||||
|                 // We just removed this point!
 | ||||
|                 const element = allElements.getEventSourceById("node/" + oldId); | ||||
|                 element.data._deleted = "yes" | ||||
|                 element.ping(); | ||||
|                 continue; | ||||
|             const mapping = this.handleIdRewrite(node, "node") | ||||
|             if (mapping !== undefined) { | ||||
|                 mappings.set(mapping[0], mapping[1]) | ||||
|             } | ||||
| 
 | ||||
|             const newId = parseInt(node.attributes.new_id.value); | ||||
|             if (oldId !== undefined && newId !== undefined && | ||||
|                 !isNaN(oldId) && !isNaN(newId)) { | ||||
|                 if (oldId == newId) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 console.log("Rewriting id: ", oldId, "-->", newId); | ||||
|                 const element = allElements.getEventSourceById("node/" + oldId); | ||||
|                 element.data.id = "node/" + newId; | ||||
|                 allElements.addElementById("node/" + newId, element); | ||||
|                 element.ping(); | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         const ways = response.getElementsByTagName("way"); | ||||
|         // @ts-ignore
 | ||||
|         for (const way of ways) { | ||||
|             const mapping = this.handleIdRewrite(way, "way") | ||||
|             if (mapping !== undefined) { | ||||
|                 mappings.set(mapping[0], mapping[1]) | ||||
|             } | ||||
|         } | ||||
|         this.changes.registerIdRewrites(mappings) | ||||
|          | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -68,13 +96,9 @@ export class ChangesetHandler { | |||
|      * If 'dryrun' is specified, the changeset XML will be printed to console instead of being uploaded | ||||
|      * | ||||
|      */ | ||||
|     public UploadChangeset( | ||||
|     public async UploadChangeset( | ||||
|         layout: LayoutConfig, | ||||
|         allElements: ElementStorage, | ||||
|         generateChangeXML: (csid: string) => string, | ||||
|         whenDone: (csId: string) => void, | ||||
|         onFail: () => void) { | ||||
| 
 | ||||
|         generateChangeXML: (csid: string) => string): Promise<void> { | ||||
|         if (this.userDetails.data.csCount == 0) { | ||||
|             // The user became a contributor!
 | ||||
|             this.userDetails.data.csCount = 1; | ||||
|  | @ -84,46 +108,36 @@ export class ChangesetHandler { | |||
|         if (this._dryRun) { | ||||
|             const changesetXML = generateChangeXML("123456"); | ||||
|             console.log(changesetXML); | ||||
|             whenDone("123456") | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const self = this; | ||||
| 
 | ||||
|         if (this.currentChangeset.data === undefined || this.currentChangeset.data === "") { | ||||
|             // We have to open a new changeset
 | ||||
|             this.OpenChangeset(layout, (csId) => { | ||||
|             try { | ||||
|                 const csId = await this.OpenChangeset(layout) | ||||
|                 this.currentChangeset.setData(csId); | ||||
|                 const changeset = generateChangeXML(csId); | ||||
|                 console.log(changeset); | ||||
|                 self.AddChange(csId, changeset, | ||||
|                     allElements, | ||||
|                     whenDone, | ||||
|                     (e) => { | ||||
|                         console.error("UPLOADING FAILED!", e) | ||||
|                         onFail() | ||||
|                     } | ||||
|                 ) | ||||
|             }, { | ||||
|                 onFail: onFail | ||||
|             }) | ||||
|                 console.log("Current changeset is:", changeset); | ||||
|                 await this.AddChange(csId, changeset) | ||||
|             } catch (e) { | ||||
|                 console.error("Could not open/upload changeset due to ", e) | ||||
|                 this.currentChangeset.setData("") | ||||
|             } | ||||
|         } else { | ||||
|             // There still exists an open changeset (or at least we hope so)
 | ||||
|             const csId = this.currentChangeset.data; | ||||
|             self.AddChange( | ||||
|                 csId, | ||||
|                 generateChangeXML(csId), | ||||
|                 allElements, | ||||
|                 whenDone, | ||||
|                 (e) => { | ||||
|                     console.warn("Could not upload, changeset is probably closed: ", e); | ||||
|                     // Mark the CS as closed...
 | ||||
|                     this.currentChangeset.setData(""); | ||||
|                     // ... and try again. As the cs is closed, no recursive loop can exist  
 | ||||
|                     self.UploadChangeset(layout, allElements, generateChangeXML, whenDone, onFail); | ||||
|                 } | ||||
|             ) | ||||
|             try { | ||||
| 
 | ||||
|                 await this.AddChange( | ||||
|                     csId, | ||||
|                     generateChangeXML(csId)) | ||||
|             } catch (e) { | ||||
|                 console.warn("Could not upload, changeset is probably closed: ", e); | ||||
|                 // Mark the CS as closed...
 | ||||
|                 this.currentChangeset.setData(""); | ||||
|                 // ... and try again. As the cs is closed, no recursive loop can exist  
 | ||||
|                 await this.UploadChangeset(layout, generateChangeXML) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -143,6 +157,13 @@ export class ChangesetHandler { | |||
|                          reason: string, | ||||
|                          allElements: ElementStorage, | ||||
|                          continuation: () => void) { | ||||
|         return this.DeleteElementAsync(object, layout, reason, allElements).then(continuation) | ||||
|     } | ||||
| 
 | ||||
|     public async DeleteElementAsync(object: OsmObject, | ||||
|                                     layout: LayoutConfig, | ||||
|                                     reason: string, | ||||
|                                     allElements: ElementStorage): Promise<void> { | ||||
| 
 | ||||
|         function generateChangeXML(csId: string) { | ||||
|             let [lat, lon] = object.centerpoint(); | ||||
|  | @ -151,9 +172,7 @@ export class ChangesetHandler { | |||
|             changes += | ||||
|                 `<delete><${object.type} id="${object.id}" version="${object.version}" changeset="${csId}" lat="${lat}" lon="${lon}" /></delete>`; | ||||
|             changes += "</osmChange>"; | ||||
|             continuation() | ||||
|             return changes; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -163,143 +182,122 @@ export class ChangesetHandler { | |||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const self = this; | ||||
|         this.OpenChangeset(layout, (csId: string) => { | ||||
| 
 | ||||
|                 // The cs is open - let us actually upload!
 | ||||
|                 const changes = generateChangeXML(csId) | ||||
| 
 | ||||
|                 self.AddChange(csId, changes, allElements, (csId) => { | ||||
|                     console.log("Successfully deleted ", object.id) | ||||
|                     self.CloseChangeset(csId, continuation) | ||||
|                 }, (csId) => { | ||||
|                     alert("Deletion failed... Should not happend") | ||||
|                     // FAILED
 | ||||
|                     self.CloseChangeset(csId, continuation) | ||||
|                 }) | ||||
|             }, { | ||||
|                 isDeletionCS: true, | ||||
|                 deletionReason: reason | ||||
|             } | ||||
|         ) | ||||
|         const csId = await this.OpenChangeset(layout, { | ||||
|             isDeletionCS: true, | ||||
|             deletionReason: reason | ||||
|         }) | ||||
|         // The cs is open - let us actually upload!
 | ||||
|         const changes = generateChangeXML(csId) | ||||
|         await this.AddChange(csId, changes) | ||||
|         await this.CloseChangeset(csId) | ||||
|     } | ||||
| 
 | ||||
|     private CloseChangeset(changesetId: string = undefined, continuation: (() => void) = () => { | ||||
|     }) { | ||||
|         if (changesetId === undefined) { | ||||
|             changesetId = this.currentChangeset.data; | ||||
|         } | ||||
|         if (changesetId === undefined) { | ||||
|             return; | ||||
|         } | ||||
|         console.log("closing changeset", changesetId); | ||||
|         this.currentChangeset.setData(""); | ||||
|         this.auth.xhr({ | ||||
|             method: 'PUT', | ||||
|             path: '/api/0.6/changeset/' + changesetId + '/close', | ||||
|         }, function (err, response) { | ||||
|             if (response == null) { | ||||
| 
 | ||||
|                 console.log("err", err); | ||||
|     private async CloseChangeset(changesetId: string = undefined): Promise<void> { | ||||
|         const self = this | ||||
|         return new Promise<void>(function (resolve, reject) { | ||||
|             if (changesetId === undefined) { | ||||
|                 changesetId = self.currentChangeset.data; | ||||
|             } | ||||
|             console.log("Closed changeset ", changesetId) | ||||
| 
 | ||||
|             if (continuation !== undefined) { | ||||
|                 continuation(); | ||||
|             if (changesetId === undefined) { | ||||
|                 return; | ||||
|             } | ||||
|         }); | ||||
|             console.log("closing changeset", changesetId); | ||||
|             self.currentChangeset.setData(""); | ||||
|             self.auth.xhr({ | ||||
|                 method: 'PUT', | ||||
|                 path: '/api/0.6/changeset/' + changesetId + '/close', | ||||
|             }, function (err, response) { | ||||
|                 if (response == null) { | ||||
| 
 | ||||
|                     console.log("err", err); | ||||
|                 } | ||||
|                 console.log("Closed changeset ", changesetId) | ||||
|                 resolve() | ||||
|             }); | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     private OpenChangeset( | ||||
|         layout: LayoutConfig, | ||||
|         continuation: (changesetId: string) => void, | ||||
|         options?: { | ||||
|             isDeletionCS?: boolean, | ||||
|             deletionReason?: string, | ||||
|             onFail?: () => void | ||||
|         } | ||||
|     ) { | ||||
|         options = options ?? {} | ||||
|         options.isDeletionCS = options.isDeletionCS ?? false | ||||
|         const commentExtra = layout.changesetmessage !== undefined ? " - " + layout.changesetmessage : ""; | ||||
|         let comment = `Adding data with #MapComplete for theme #${layout.id}${commentExtra}` | ||||
|         if (options.isDeletionCS) { | ||||
|             comment = `Deleting a point with #MapComplete for theme #${layout.id}${commentExtra}` | ||||
|             if (options.deletionReason) { | ||||
|                 comment += ": " + options.deletionReason; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let path = window.location.pathname; | ||||
|         path = path.substr(1, path.lastIndexOf("/")); | ||||
|         const metadata = [ | ||||
|             ["created_by", `MapComplete ${Constants.vNumber}`], | ||||
|             ["comment", comment], | ||||
|             ["deletion", options.isDeletionCS ? "yes" : undefined], | ||||
|             ["theme", layout.id], | ||||
|             ["language", Locale.language.data], | ||||
|             ["host", window.location.host], | ||||
|             ["path", path], | ||||
|             ["source", State.state.currentGPSLocation.data !== undefined ? "survey" : undefined], | ||||
|             ["imagery", State.state.backgroundLayer.data.id], | ||||
|             ["theme-creator", layout.maintainer] | ||||
|         ] | ||||
|             .filter(kv => (kv[1] ?? "") !== "") | ||||
|             .map(kv => `<tag k="${kv[0]}" v="${escapeHtml(kv[1])}"/>`) | ||||
|             .join("\n") | ||||
| 
 | ||||
|         this.auth.xhr({ | ||||
|             method: 'PUT', | ||||
|             path: '/api/0.6/changeset/create', | ||||
|             options: {header: {'Content-Type': 'text/xml'}}, | ||||
|             content: [`<osm><changeset>`, | ||||
|                 metadata, | ||||
|                 `</changeset></osm>`].join("") | ||||
|         }, function (err, response) { | ||||
|             if (response === undefined) { | ||||
|                 console.log("err", err); | ||||
|                 if (options.onFail) { | ||||
|                     options.onFail() | ||||
|     ): Promise<string> { | ||||
|         const self = this; | ||||
|         return new Promise<string>(function (resolve, reject) { | ||||
|             options = options ?? {} | ||||
|             options.isDeletionCS = options.isDeletionCS ?? false | ||||
|             const commentExtra = layout.changesetmessage !== undefined ? " - " + layout.changesetmessage : ""; | ||||
|             let comment = `Adding data with #MapComplete for theme #${layout.id}${commentExtra}` | ||||
|             if (options.isDeletionCS) { | ||||
|                 comment = `Deleting a point with #MapComplete for theme #${layout.id}${commentExtra}` | ||||
|                 if (options.deletionReason) { | ||||
|                     comment += ": " + options.deletionReason; | ||||
|                 } | ||||
|                 return; | ||||
|             } else { | ||||
|                 continuation(response); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|             let path = window.location.pathname; | ||||
|             path = path.substr(1, path.lastIndexOf("/")); | ||||
|             const metadata = [ | ||||
|                 ["created_by", `MapComplete ${Constants.vNumber}`], | ||||
|                 ["comment", comment], | ||||
|                 ["deletion", options.isDeletionCS ? "yes" : undefined], | ||||
|                 ["theme", layout.id], | ||||
|                 ["language", Locale.language.data], | ||||
|                 ["host", window.location.host], | ||||
|                 ["path", path], | ||||
|                 ["source", State.state.currentGPSLocation.data !== undefined ? "survey" : undefined], | ||||
|                 ["imagery", State.state.backgroundLayer.data.id], | ||||
|                 ["theme-creator", layout.maintainer] | ||||
|             ] | ||||
|                 .filter(kv => (kv[1] ?? "") !== "") | ||||
|                 .map(kv => `<tag k="${kv[0]}" v="${escapeHtml(kv[1])}"/>`) | ||||
|                 .join("\n") | ||||
| 
 | ||||
| 
 | ||||
|             self.auth.xhr({ | ||||
|                 method: 'PUT', | ||||
|                 path: '/api/0.6/changeset/create', | ||||
|                 options: {header: {'Content-Type': 'text/xml'}}, | ||||
|                 content: [`<osm><changeset>`, | ||||
|                     metadata, | ||||
|                     `</changeset></osm>`].join("") | ||||
|             }, function (err, response) { | ||||
|                 if (response === undefined) { | ||||
|                     console.log("err", err); | ||||
|                     reject(err) | ||||
|                 } else { | ||||
|                     resolve(response); | ||||
|                 } | ||||
|             }); | ||||
|         }) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Upload a changesetXML | ||||
|      * @param changesetId | ||||
|      * @param changesetXML | ||||
|      * @param allElements | ||||
|      * @param continuation | ||||
|      * @param onFail | ||||
|      * @constructor | ||||
|      * @private | ||||
|      */ | ||||
|     private AddChange(changesetId: string, | ||||
|                       changesetXML: string, | ||||
|                       allElements: ElementStorage, | ||||
|                       continuation: ((changesetId: string) => void), | ||||
|                       onFail: ((changesetId: string, reason: string) => void) = undefined) { | ||||
|         this.auth.xhr({ | ||||
|             method: 'POST', | ||||
|             options: {header: {'Content-Type': 'text/xml'}}, | ||||
|             path: '/api/0.6/changeset/' + changesetId + '/upload', | ||||
|             content: changesetXML | ||||
|         }, function (err, response) { | ||||
|             if (response == null) { | ||||
|                 console.log("err", err); | ||||
|                 if (onFail) { | ||||
|                     onFail(changesetId, err); | ||||
|                       changesetXML: string): Promise<string> { | ||||
|         const self = this; | ||||
|         return new Promise(function (resolve, reject) { | ||||
|             self.auth.xhr({ | ||||
|                 method: 'POST', | ||||
|                 options: {header: {'Content-Type': 'text/xml'}}, | ||||
|                 path: '/api/0.6/changeset/' + changesetId + '/upload', | ||||
|                 content: changesetXML | ||||
|             }, function (err, response) { | ||||
|                 if (response == null) { | ||||
|                     console.log("err", err); | ||||
|                     reject(err); | ||||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|             ChangesetHandler.parseUploadChangesetResponse(response, allElements); | ||||
|             console.log("Uploaded changeset ", changesetId); | ||||
|             continuation(changesetId); | ||||
|         }); | ||||
|                 self.parseUploadChangesetResponse(response); | ||||
|                 console.log("Uploaded changeset ", changesetId); | ||||
|                 resolve(changesetId); | ||||
|             }); | ||||
|         }) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import Img from "../../UI/Base/Img"; | |||
| import {Utils} from "../../Utils"; | ||||
| import {OsmObject} from "./OsmObject"; | ||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||
| import {Changes} from "./Changes"; | ||||
| 
 | ||||
| export default class UserDetails { | ||||
| 
 | ||||
|  | @ -54,7 +55,7 @@ export class OsmConnection { | |||
|     private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []; | ||||
|     private readonly _iframeMode: Boolean | boolean; | ||||
|     private readonly _singlePage: boolean; | ||||
|     private readonly _oauth_config: { | ||||
|     public readonly _oauth_config: { | ||||
|         oauth_consumer_key: string, | ||||
|         oauth_secret: string, | ||||
|         url: string | ||||
|  | @ -63,6 +64,8 @@ export class OsmConnection { | |||
| 
 | ||||
|     constructor(dryRun: boolean, | ||||
|                 fakeUser: boolean, | ||||
|                 allElements: ElementStorage, | ||||
|                 changes: Changes, | ||||
|                 oauth_token: UIEventSource<string>, | ||||
|                 // Used to keep multiple changesets open and to write to the correct changeset
 | ||||
|                 layoutName: string, | ||||
|  | @ -101,7 +104,7 @@ export class OsmConnection { | |||
| 
 | ||||
|         this.preferencesHandler = new OsmPreferences(this.auth, this); | ||||
| 
 | ||||
|         this.changesetHandler = new ChangesetHandler(layoutName, dryRun, this, this.auth); | ||||
|         this.changesetHandler = new ChangesetHandler(layoutName, dryRun, this, allElements, changes, this.auth); | ||||
|         if (oauth_token.data !== undefined) { | ||||
|             console.log(oauth_token.data) | ||||
|             const self = this; | ||||
|  | @ -124,10 +127,8 @@ export class OsmConnection { | |||
|     public UploadChangeset( | ||||
|         layout: LayoutConfig, | ||||
|         allElements: ElementStorage, | ||||
|         generateChangeXML: (csid: string) => string, | ||||
|         whenDone: (csId: string) => void, | ||||
|         onFail: () => {}) { | ||||
|         this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML, whenDone, onFail); | ||||
|         generateChangeXML: (csid: string) => string): Promise<void> { | ||||
|         return this.changesetHandler.UploadChangeset(layout, generateChangeXML); | ||||
|     } | ||||
| 
 | ||||
|     public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> { | ||||
|  |  | |||
|  | @ -157,23 +157,6 @@ export abstract class OsmObject { | |||
|         const elements: any[] = data.elements; | ||||
|         return OsmObject.ParseObjects(elements); | ||||
|     } | ||||
| 
 | ||||
|     public static DownloadAll(neededIds, forceRefresh = true): UIEventSource<OsmObject[]> { | ||||
|         // local function which downloads all the objects one by one
 | ||||
|         // this is one big loop, running one download, then rerunning the entire function
 | ||||
| 
 | ||||
|         const allSources: UIEventSource<OsmObject> [] = neededIds.map(id => OsmObject.DownloadObject(id, forceRefresh)) | ||||
|         const allCompleted = new UIEventSource(undefined).map(_ => { | ||||
|             return !allSources.some(uiEventSource => uiEventSource.data === undefined) | ||||
|         }, allSources) | ||||
|         return allCompleted.map(completed => { | ||||
|             if (completed) { | ||||
|                 return allSources.map(src => src.data) | ||||
|             } | ||||
|             return undefined | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     protected static isPolygon(tags: any): boolean { | ||||
|         for (const tagsKey in tags) { | ||||
|             if (!tags.hasOwnProperty(tagsKey)) { | ||||
|  |  | |||
							
								
								
									
										11
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -32,11 +32,11 @@ export default class State { | |||
|     /** | ||||
|      The mapping from id -> UIEventSource<properties> | ||||
|      */ | ||||
|     public allElements: ElementStorage; | ||||
|     public allElements: ElementStorage = new ElementStorage(); | ||||
|     /** | ||||
|      THe change handler | ||||
|      */ | ||||
|     public changes: Changes; | ||||
|     public changes: Changes = new Changes(); | ||||
|     /** | ||||
|      The leaflet instance of the big basemap | ||||
|      */ | ||||
|  | @ -155,7 +155,6 @@ export default class State { | |||
| 
 | ||||
|     constructor(layoutToUse: LayoutConfig) { | ||||
|         const self = this; | ||||
| 
 | ||||
|         this.layoutToUse.setData(layoutToUse); | ||||
| 
 | ||||
|         // -- Location control initialization
 | ||||
|  | @ -376,6 +375,8 @@ export default class State { | |||
|         this.osmConnection = new OsmConnection( | ||||
|             this.featureSwitchIsTesting.data, | ||||
|             this.featureSwitchFakeUser.data, | ||||
|             this.allElements,  | ||||
|             this.changes, | ||||
|             QueryParameters.GetQueryParameter( | ||||
|                 "oauth_token", | ||||
|                 undefined, | ||||
|  | @ -387,9 +388,7 @@ export default class State { | |||
|             this.featureSwitchApiURL.data | ||||
|         ); | ||||
| 
 | ||||
|         this.allElements = new ElementStorage(); | ||||
|         this.changes = new Changes(); | ||||
| 
 | ||||
|        | ||||
|         new ChangeToElementsActor(this.changes, this.allElements) | ||||
| 
 | ||||
|         new PendingChangesUploader(this.changes, this.selectedElement); | ||||
|  |  | |||
|  | @ -57,7 +57,6 @@ export default class SimpleAddUI extends Toggle { | |||
| 
 | ||||
| 
 | ||||
|         function createNewPoint(tags: any[], location: { lat: number, lon: number }, snapOntoWay?: OsmWay) { | ||||
|             console.trace("Creating a new point") | ||||
|             const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, {snapOnto: snapOntoWay}) | ||||
|             State.state.changes.applyAction(newElementAction) | ||||
|             selectedPreset.setData(undefined) | ||||
|  |  | |||
|  | @ -54,13 +54,16 @@ | |||
|       }, | ||||
|       "tagRenderings": [ | ||||
|         { | ||||
|           "id": "uk_addresses_explanation", | ||||
|           "render": "There probably is an address here" | ||||
|         }, | ||||
|         { | ||||
|           "id": "uk_addresses_embedding_outline", | ||||
|           "render": "An outline embedding this point with an address already exists in OpenStreetMap.<br>This <a href='https://openstreetmap.org/{_embedding_object:id}' target='blank'>object</a> has address <b>{_embedding_object:addr:street} {_embedding_object:addr:housenumber}</b>", | ||||
|           "condition": "_embedding_object:id~*" | ||||
|         }, | ||||
|         { | ||||
|           "id": "uk_addresses_import_button", | ||||
|           "render": "{import_button(ref:inspireid=$inspireid, Add this address, ./assets/themes/uk_addresses/housenumber_add.svg)}" | ||||
|         }, | ||||
|         "all_tags" | ||||
|  | @ -109,11 +112,13 @@ | |||
|       }, | ||||
|       "tagRenderings": [ | ||||
|         { | ||||
|           "id": "uk_addresses_explanation_osm", | ||||
|           "render": { | ||||
|             "en": "This address is saved in OpenStreetMap" | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": "uk_addresses_housenumber", | ||||
|           "render": { | ||||
|             "en": "The housenumber is <b>{addr:housenumber}</b>" | ||||
|           }, | ||||
|  | @ -137,6 +142,7 @@ | |||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "id": "uk_addresses_street", | ||||
|           "render": { | ||||
|             "en": "This address is in street <b>{addr:street}</b>" | ||||
|           }, | ||||
|  |  | |||
|  | @ -13,7 +13,8 @@ | |||
|     "uploadDone": "<span class='thanks'>Your picture has been added. Thanks for helping out!</span>", | ||||
|     "dontDelete": "Cancel", | ||||
|     "doDelete": "Remove image", | ||||
|     "isDeleted": "Deleted" | ||||
|     "isDeleted": "Deleted", | ||||
|     "hasBeenImported": "This feature has been imported" | ||||
|   }, | ||||
|   "centerMessage": { | ||||
|     "loadingData": "Loading data…", | ||||
|  |  | |||
|  | @ -1326,10 +1326,10 @@ | |||
|                 "description": "Addresses", | ||||
|                 "name": "Known addresses in OSM", | ||||
|                 "tagRenderings": { | ||||
|                     "0": { | ||||
|                     "uk_addresses_explanation_osm": { | ||||
|                         "render": "This address is saved in OpenStreetMap" | ||||
|                     }, | ||||
|                     "1": { | ||||
|                     "uk_addresses_housenumber": { | ||||
|                         "mappings": { | ||||
|                             "0": { | ||||
|                                 "then": "This building has no house number" | ||||
|  | @ -1338,7 +1338,7 @@ | |||
|                         "question": "What is the number of this house?", | ||||
|                         "render": "The housenumber is <b>{addr:housenumber}</b>" | ||||
|                     }, | ||||
|                     "2": { | ||||
|                     "uk_addresses_street": { | ||||
|                         "question": "What street is this address located in?", | ||||
|                         "render": "This address is in street <b>{addr:street}</b>" | ||||
|                     } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue