| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  | import {OsmNode, OsmObject, OsmRelation, OsmWay} from "./OsmObject"; | 
					
						
							| 
									
										
										
										
											2020-10-02 19:00:24 +02:00
										 |  |  | import State from "../../State"; | 
					
						
							| 
									
										
										
										
											2020-10-27 01:01:34 +01:00
										 |  |  | import {UIEventSource} from "../UIEventSource"; | 
					
						
							| 
									
										
										
										
											2021-01-02 19:09:49 +01:00
										 |  |  | import Constants from "../../Models/Constants"; | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  | import OsmChangeAction from "./Actions/OsmChangeAction"; | 
					
						
							|  |  |  | import {ChangeDescription} from "./Actions/ChangeDescription"; | 
					
						
							|  |  |  | import {Utils} from "../../Utils"; | 
					
						
							| 
									
										
										
										
											2021-07-22 14:25:23 +02:00
										 |  |  | import {LocalStorageSource} from "../Web/LocalStorageSource"; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Handles all changes made to OSM. | 
					
						
							|  |  |  |  * Needs an authenticator via OsmConnection | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  | export class Changes { | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-10 13:47:53 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     private static _nextId = -1; // Newly assigned ID's are negative
 | 
					
						
							| 
									
										
										
										
											2021-04-23 12:56:56 +02:00
										 |  |  |     public readonly name = "Newly added features" | 
					
						
							| 
									
										
										
										
											2021-02-20 22:18:42 +01:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |      * All the newly created features as featureSource + all the modified features | 
					
						
							| 
									
										
										
										
											2021-02-20 22:18:42 +01:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-07-10 13:47:53 +02:00
										 |  |  |     public features = new UIEventSource<{ feature: any, freshness: Date }[]>([]); | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 14:25:23 +02:00
										 |  |  |     public readonly pendingChanges = LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", []) | 
					
						
							| 
									
										
										
										
											2021-07-13 00:40:27 +02:00
										 |  |  |     private readonly isUploading = new UIEventSource(false); | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     private readonly previouslyCreated: OsmObject[] = [] | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |     constructor() { | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |     private static createChangesetFor(csId: string, | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |                                       allChanges: { | 
					
						
							|  |  |  |                                           modifiedObjects: OsmObject[], | 
					
						
							|  |  |  |                                           newObjects: OsmObject[], | 
					
						
							|  |  |  |                                           deletedObjects: OsmObject[] | 
					
						
							|  |  |  |                                       }): string { | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const changedElements = allChanges.modifiedObjects ?? [] | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |         const newElements = allChanges.newObjects ?? [] | 
					
						
							|  |  |  |         const deletedElements = allChanges.deletedObjects ?? [] | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         let changes = `<osmChange version='0.6' generator='Mapcomplete ${Constants.vNumber}'>`; | 
					
						
							|  |  |  |         if (newElements.length > 0) { | 
					
						
							|  |  |  |             changes += | 
					
						
							|  |  |  |                 "\n<create>\n" + | 
					
						
							|  |  |  |                 newElements.map(e => e.ChangesetXML(csId)).join("\n") + | 
					
						
							|  |  |  |                 "</create>"; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         if (changedElements.length > 0) { | 
					
						
							|  |  |  |             changes += | 
					
						
							|  |  |  |                 "\n<modify>\n" + | 
					
						
							|  |  |  |                 changedElements.map(e => e.ChangesetXML(csId)).join("\n") + | 
					
						
							|  |  |  |                 "\n</modify>"; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-08-27 18:44:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         if (deletedElements.length > 0) { | 
					
						
							|  |  |  |             changes += | 
					
						
							|  |  |  |                 "\n<deleted>\n" + | 
					
						
							|  |  |  |                 deletedElements.map(e => e.ChangesetXML(csId)).join("\n") + | 
					
						
							|  |  |  |                 "\n</deleted>" | 
					
						
							| 
									
										
										
										
											2020-08-06 19:42:10 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-08-27 11:11:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         changes += "</osmChange>"; | 
					
						
							|  |  |  |         return changes; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |     private static GetNeededIds(changes: ChangeDescription[]) { | 
					
						
							|  |  |  |         return Utils.Dedup(changes.filter(c => c.id >= 0) | 
					
						
							|  |  |  |             .map(c => c.type + "/" + c.id)) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-07-10 13:47:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Returns a new ID and updates the value for the next ID | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public getNewID() { | 
					
						
							|  |  |  |         return Changes._nextId--; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Uploads all the pending changes in one go. | 
					
						
							|  |  |  |      * Triggered by the 'PendingChangeUploader'-actor in Actors | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public flushChanges(flushreason: string = undefined) { | 
					
						
							|  |  |  |         if (this.pendingChanges.data.length === 0) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (this.isUploading.data) { | 
					
						
							|  |  |  |             console.log("Is already uploading... Abort") | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.isUploading.setData(true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         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) | 
					
						
							|  |  |  |                 self.pendingChanges.setData([]) | 
					
						
							|  |  |  |                 self.isUploading.setData(false) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public applyAction(action: OsmChangeAction) { | 
					
						
							|  |  |  |         const changes = action.Perform(this) | 
					
						
							|  |  |  |         console.log("Received changes:", changes) | 
					
						
							|  |  |  |         this.pendingChanges.data.push(...changes); | 
					
						
							|  |  |  |         this.pendingChanges.ping(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |     private CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): { | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         newObjects: OsmObject[], | 
					
						
							|  |  |  |         modifiedObjects: OsmObject[] | 
					
						
							|  |  |  |         deletedObjects: OsmObject[] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } { | 
					
						
							|  |  |  |         const objects: Map<string, OsmObject> = new Map<string, OsmObject>() | 
					
						
							|  |  |  |         const states: Map<string, "unchanged" | "created" | "modified" | "deleted"> = new Map(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const o of downloadedOsmObjects) { | 
					
						
							|  |  |  |             objects.set(o.type + "/" + o.id, o) | 
					
						
							|  |  |  |             states.set(o.type + "/" + o.id, "unchanged") | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-07-10 13:47:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |         for (const o of this.previouslyCreated) { | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |             objects.set(o.type + "/" + o.id, o) | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |             states.set(o.type + "/" + o.id, "unchanged") | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         let changed = false; | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  |         for (const change of changes) { | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             const id = change.type + "/" + change.id | 
					
						
							|  |  |  |             if (!objects.has(id)) { | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |                 if (change.id >= 0) { | 
					
						
							|  |  |  |                     throw "Did not get an object that should be known: " + id | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |                 // This is a new object that should be created
 | 
					
						
							|  |  |  |                 states.set(id, "created") | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |                 console.log("Creating object for changeDescription", change) | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |                 let osmObj: OsmObject = undefined; | 
					
						
							|  |  |  |                 switch (change.type) { | 
					
						
							|  |  |  |                     case "node": | 
					
						
							|  |  |  |                         const n = new OsmNode(change.id) | 
					
						
							|  |  |  |                         n.lat = change.changes["lat"] | 
					
						
							|  |  |  |                         n.lon = change.changes["lon"] | 
					
						
							|  |  |  |                         osmObj = n | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                     case "way": | 
					
						
							|  |  |  |                         const w = new OsmWay(change.id) | 
					
						
							|  |  |  |                         w.nodes = change.changes["nodes"] | 
					
						
							|  |  |  |                         osmObj = w | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                     case "relation": | 
					
						
							|  |  |  |                         const r = new OsmRelation(change.id) | 
					
						
							|  |  |  |                         r.members = change.changes["members"] | 
					
						
							|  |  |  |                         osmObj = r | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (osmObj === undefined) { | 
					
						
							|  |  |  |                     throw "Hmm? This is a bug" | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 objects.set(id, osmObj) | 
					
						
							| 
									
										
										
										
											2021-07-18 14:52:09 +02:00
										 |  |  |                 this.previouslyCreated.push(osmObj) | 
					
						
							| 
									
										
										
										
											2021-01-03 03:09:52 +01:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             const state = states.get(id) | 
					
						
							|  |  |  |             if (change.doDelete) { | 
					
						
							|  |  |  |                 if (state === "created") { | 
					
						
							|  |  |  |                     states.set(id, "unchanged") | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     states.set(id, "deleted") | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-07-14 15:28:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             const obj = objects.get(id) | 
					
						
							|  |  |  |             // Apply tag changes
 | 
					
						
							|  |  |  |             for (const kv of change.tags ?? []) { | 
					
						
							|  |  |  |                 const k = kv.k | 
					
						
							|  |  |  |                 let v = kv.v | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (v === "") { | 
					
						
							|  |  |  |                     v = undefined; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 const oldV = obj.type[k] | 
					
						
							|  |  |  |                 if (oldV === v) { | 
					
						
							|  |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 obj.tags[k] = v; | 
					
						
							|  |  |  |                 changed = true; | 
					
						
							| 
									
										
										
										
											2021-07-14 15:28:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-14 15:28:02 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-07-10 13:47:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             if (change.changes !== undefined) { | 
					
						
							|  |  |  |                 switch (change.type) { | 
					
						
							|  |  |  |                     case "node": | 
					
						
							|  |  |  |                         // @ts-ignore
 | 
					
						
							|  |  |  |                         const nlat = change.changes.lat; | 
					
						
							|  |  |  |                         // @ts-ignore
 | 
					
						
							|  |  |  |                         const nlon = change.changes.lon; | 
					
						
							|  |  |  |                         const n = <OsmNode>obj | 
					
						
							|  |  |  |                         if (n.lat !== nlat || n.lon !== nlon) { | 
					
						
							|  |  |  |                             n.lat = nlat; | 
					
						
							|  |  |  |                             n.lon = nlon; | 
					
						
							|  |  |  |                             changed = true; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                     case "way": | 
					
						
							|  |  |  |                         const nnodes = change.changes["nodes"] | 
					
						
							|  |  |  |                         const w = <OsmWay>obj | 
					
						
							|  |  |  |                         if (!Utils.Identical(nnodes, w.nodes)) { | 
					
						
							|  |  |  |                             w.nodes = nnodes | 
					
						
							|  |  |  |                             changed = true; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                     case "relation": | 
					
						
							|  |  |  |                         const nmembers: { type: "node" | "way" | "relation", ref: number, role: string }[] = change.changes["members"] | 
					
						
							|  |  |  |                         const r = <OsmRelation>obj | 
					
						
							|  |  |  |                         if (!Utils.Identical(nmembers, r.members, (a, b) => { | 
					
						
							|  |  |  |                             return a.role === b.role && a.type === b.type && a.ref === b.ref | 
					
						
							|  |  |  |                         })) { | 
					
						
							|  |  |  |                             r.members = nmembers; | 
					
						
							|  |  |  |                             changed = true; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         break; | 
					
						
							| 
									
										
										
										
											2021-07-10 13:47:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-01-04 22:14:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             if (changed && state === "unchanged") { | 
					
						
							|  |  |  |                 states.set(id, "modified") | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-07-10 13:47:53 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-10 15:52:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         const result = { | 
					
						
							|  |  |  |             newObjects: [], | 
					
						
							|  |  |  |             modifiedObjects: [], | 
					
						
							|  |  |  |             deletedObjects: [] | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         objects.forEach((v, id) => { | 
					
						
							| 
									
										
										
										
											2021-07-15 00:39:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |             const state = states.get(id) | 
					
						
							|  |  |  |             if (state === "created") { | 
					
						
							|  |  |  |                 result.newObjects.push(v) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (state === "modified") { | 
					
						
							|  |  |  |                 result.modifiedObjects.push(v) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (state === "deleted") { | 
					
						
							|  |  |  |                 result.deletedObjects.push(v) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-07-10 13:47:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-10 15:52:52 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 20:47:28 +02:00
										 |  |  |         return result | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | } |